Skip to content

Commit da3b5e0

Browse files
crisbetojelbourn
authored andcommitted
fix(progress-bar): incorrectly handling current path when using hash location strategy (#12713)
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 d349b9e commit da3b5e0

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)