Skip to content

Commit a3f5206

Browse files
committed
fix(progress-bar): incorrectly handling current path when using hash location strategy
Fixes the progress bar prefixing the references incorrectly when the consumer is using the router's hash location strategy. The issue comes from the fact that the router normalizes the `location.pathname` between the regular strategy and the hash one, whereas we need to use the exact same path as the `window.location`. Fixes #12710.
1 parent b2247f8 commit a3f5206

File tree

2 files changed

+79
-39
lines changed

2 files changed

+79
-39
lines changed

src/lib/progress-bar/progress-bar.spec.ts

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,49 @@
1-
import {TestBed, async, ComponentFixture} from '@angular/core/testing';
2-
import {Component} from '@angular/core';
1+
import {TestBed, ComponentFixture} from '@angular/core/testing';
2+
import {Component, Type} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4-
import {Location} from '@angular/common';
5-
import {MatProgressBarModule} from './index';
4+
import {MatProgressBarModule, MAT_PROGRESS_BAR_LOCATION} from './index';
65

76

87
describe('MatProgressBar', () => {
98
let fakePath = '/fake-path';
109

11-
beforeEach(async(() => {
10+
function createComponent<T>(componentType: Type<T>): ComponentFixture<T> {
1211
TestBed.configureTestingModule({
1312
imports: [MatProgressBarModule],
14-
declarations: [
15-
BasicProgressBar,
16-
BufferProgressBar,
17-
],
13+
declarations: [componentType],
1814
providers: [{
19-
provide: Location,
20-
useValue: {path: () => fakePath}
15+
provide: MAT_PROGRESS_BAR_LOCATION,
16+
useValue: {pathname: fakePath}
2117
}]
22-
});
23-
24-
TestBed.compileComponents();
25-
}));
18+
}).compileComponents();
2619

20+
return TestBed.createComponent<T>(componentType);
21+
}
2722

2823
describe('basic progress-bar', () => {
29-
let fixture: ComponentFixture<BasicProgressBar>;
30-
31-
beforeEach(() => {
32-
fixture = TestBed.createComponent(BasicProgressBar);
24+
it('should apply a mode of "determinate" if no mode is provided.', () => {
25+
const fixture = createComponent(BasicProgressBar);
3326
fixture.detectChanges();
34-
});
3527

36-
it('should apply a mode of "determinate" if no mode is provided.', () => {
37-
let progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
28+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
3829
expect(progressElement.componentInstance.mode).toBe('determinate');
3930
});
4031

4132
it('should define default values for value and bufferValue attributes', () => {
42-
let progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
33+
const fixture = createComponent(BasicProgressBar);
34+
fixture.detectChanges();
35+
36+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
4337
expect(progressElement.componentInstance.value).toBe(0);
4438
expect(progressElement.componentInstance.bufferValue).toBe(0);
4539
});
4640

4741
it('should clamp value and bufferValue between 0 and 100', () => {
48-
let progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
49-
let progressComponent = progressElement.componentInstance;
42+
const fixture = createComponent(BasicProgressBar);
43+
fixture.detectChanges();
44+
45+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
46+
const progressComponent = progressElement.componentInstance;
5047

5148
progressComponent.value = 50;
5249
expect(progressComponent.value).toBe(50);
@@ -68,8 +65,11 @@ describe('MatProgressBar', () => {
6865
});
6966

7067
it('should return the transform attribute for bufferValue and mode', () => {
71-
let progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
72-
let progressComponent = progressElement.componentInstance;
68+
const fixture = createComponent(BasicProgressBar);
69+
fixture.detectChanges();
70+
71+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
72+
const progressComponent = progressElement.componentInstance;
7373

7474
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0)'});
7575
expect(progressComponent._bufferTransform()).toBe(undefined);
@@ -95,26 +95,38 @@ describe('MatProgressBar', () => {
9595
});
9696

9797
it('should prefix SVG references with the current path', () => {
98+
const fixture = createComponent(BasicProgressBar);
99+
fixture.detectChanges();
100+
98101
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
99102
expect(rect.getAttribute('fill')).toMatch(/^url\(['"]?\/fake-path#.*['"]?\)$/);
100103
});
101104

105+
it('should account for location hash when prefixing the SVG references', () => {
106+
fakePath = '/fake-path#anchor';
107+
108+
const fixture = createComponent(BasicProgressBar);
109+
fixture.detectChanges();
110+
111+
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
112+
expect(rect.getAttribute('fill')).not.toContain('#anchor#');
113+
});
114+
102115
it('should not be able to tab into the underlying SVG element', () => {
116+
const fixture = createComponent(BasicProgressBar);
117+
fixture.detectChanges();
118+
103119
const svg = fixture.debugElement.query(By.css('svg')).nativeElement;
104120
expect(svg.getAttribute('focusable')).toBe('false');
105121
});
106122
});
107123

108124
describe('buffer progress-bar', () => {
109-
let fixture: ComponentFixture<BufferProgressBar>;
110-
111-
beforeEach(() => {
112-
fixture = TestBed.createComponent(BufferProgressBar);
125+
it('should not modify the mode if a valid mode is provided.', () => {
126+
const fixture = createComponent(BufferProgressBar);
113127
fixture.detectChanges();
114-
});
115128

116-
it('should not modify the mode if a valid mode is provided.', () => {
117-
let progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
129+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
118130
expect(progressElement.componentInstance.mode).toBe('buffer');
119131
});
120132
});

src/lib/progress-bar/progress-bar.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
Inject,
1313
Input,
1414
Optional,
15-
ViewEncapsulation
15+
ViewEncapsulation,
16+
InjectionToken
1617
} from '@angular/core';
17-
import {Location} from '@angular/common';
1818
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
1919
import {CanColor, mixinColor} from '@angular/material/core';
2020

@@ -29,6 +29,30 @@ export class MatProgressBarBase {
2929

3030
export const _MatProgressBarMixinBase = mixinColor(MatProgressBarBase, 'primary');
3131

32+
/**
33+
* Injection token used to provide the current location to `MatProgressBar`.
34+
* Used to handle server-side rendering and to stub out during unit tests.
35+
* @docs-private
36+
*/
37+
export const MAT_PROGRESS_BAR_LOCATION = new InjectionToken<MatProgressBarLocation>(
38+
'mat-progress-bar-location',
39+
{providedIn: 'root', factory: MAT_PROGRESS_BAR_LOCATION_FACTORY}
40+
);
41+
42+
/**
43+
* Stubbed out location for `MatProgressBar`.
44+
* @docs-private
45+
*/
46+
export interface MatProgressBarLocation {
47+
pathname: string;
48+
}
49+
50+
/** @docs-private */
51+
export function MAT_PROGRESS_BAR_LOCATION_FACTORY(): MatProgressBarLocation {
52+
return typeof window !== 'undefined' ? window.location : {pathname: ''};
53+
}
54+
55+
3256
/** Counter used to generate unique IDs for progress bars. */
3357
let progressbarId = 0;
3458

@@ -61,13 +85,17 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor
6185
* @deprecated `location` parameter to be made required.
6286
* @breaking-change 8.0.0
6387
*/
64-
@Optional() location?: Location) {
88+
@Optional() @Inject(MAT_PROGRESS_BAR_LOCATION) location?: MatProgressBarLocation) {
6589
super(_elementRef);
6690

6791
// We need to prefix the SVG reference with the current path, otherwise they won't work
6892
// in Safari if the page has a `<base>` tag. Note that we need quotes inside the `url()`,
69-
// because named route URLs can contain parentheses (see #12338).
70-
this._rectangleFillValue = `url('${location ? location.path() : ''}#${this.progressbarId}')`;
93+
// because named route URLs can contain parentheses (see #12338). Also we don't use
94+
// `Location` from `@angular/common` since we can't tell the difference between whether
95+
// the consumer is using the hash location strategy or not, because `Location` normalizes
96+
// both `/#/foo/bar` and `/foo/bar` to the same thing.
97+
const path = location ? location.pathname.split('#')[0] : '';
98+
this._rectangleFillValue = `url('${path}#${this.progressbarId}')`;
7199
}
72100

73101
/** Value of the progress bar. Defaults to zero. Mirrored to aria-valuenow. */

0 commit comments

Comments
 (0)