Skip to content

Commit 6a1a707

Browse files
glouischandrajelbourn
authored andcommitted
feat(progress-bar): add transitionend output for value animation (#12409)
1 parent f82a94b commit 6a1a707

File tree

5 files changed

+254
-88
lines changed

5 files changed

+254
-88
lines changed

src/demo-app/progress-bar/progress-bar-demo.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@ <h1>Determinate</h1>
88

99
<div class="demo-progress-bar-controls">
1010
<span>Value: {{determinateProgressValue}}</span>
11+
<br/>
12+
<span>Last animation complete value: {{determinateAnimationEndValue}}</span>
1113
<button mat-raised-button (click)="stepDeterminateProgressVal(10)">Increase</button>
1214
<button mat-raised-button (click)="stepDeterminateProgressVal(-10)">Decrease</button>
1315
</div>
1416

1517
<div class="demo-progress-bar-container">
16-
<mat-progress-bar mode="determinate" [value]="determinateProgressValue" [color]="color">
18+
<mat-progress-bar mode="determinate" [value]="determinateProgressValue" [color]="color"
19+
(animationEnd)="determinateAnimationEndValue = $event.value">
1720
</mat-progress-bar>
1821
</div>
1922

2023
<h1>Buffer</h1>
2124

2225
<div class="demo-progress-bar-controls">
2326
<span>Value: {{bufferProgressValue}}</span>
27+
<br/>
28+
<span>Last animation complete value: {{bufferAnimationEndValue}}</span>
2429
<button mat-raised-button (click)="stepBufferProgressVal(10)">Increase</button>
2530
<button mat-raised-button (click)="stepBufferProgressVal(-10)">Decrease</button>
2631
<span class="demo-progress-bar-spacer"></span>
@@ -31,7 +36,8 @@ <h1>Buffer</h1>
3136

3237
<div class="demo-progress-bar-container">
3338
<mat-progress-bar [value]="bufferProgressValue" [bufferValue]="bufferBufferValue" mode="buffer"
34-
[color]="color"></mat-progress-bar>
39+
[color]="color" (animationEnd)="bufferAnimationEndValue = $event.value">
40+
</mat-progress-bar>
3541
</div>
3642

3743
<h1>Indeterminate</h1>

src/demo-app/progress-bar/progress-bar-demo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {Component} from '@angular/core';
2020
export class ProgressBarDemo {
2121
color: string = 'primary';
2222
determinateProgressValue: number = 30;
23+
determinateAnimationEndValue: number;
24+
bufferAnimationEndValue: number;
2325
bufferProgressValue: number = 30;
2426
bufferBufferValue: number = 40;
2527

src/lib/progress-bar/progress-bar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
<rect [attr.fill]="_rectangleFillValue" width="100%" height="100%"/>
1212
</svg>
1313
<div class="mat-progress-bar-buffer mat-progress-bar-element" [ngStyle]="_bufferTransform()"></div>
14-
<div class="mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element" [ngStyle]="_primaryTransform()"></div>
14+
<div class="mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element" [ngStyle]="_primaryTransform()" #primaryValueBar></div>
1515
<div class="mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element"></div>

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

Lines changed: 174 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import {TestBed, ComponentFixture} from '@angular/core/testing';
2-
import {Component, Type} from '@angular/core';
1+
import {TestBed, async, ComponentFixture} from '@angular/core/testing';
2+
import {Component, DebugElement, Type} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4+
import {dispatchFakeEvent} from '@angular/cdk/testing';
5+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
46
import {MatProgressBarModule, MAT_PROGRESS_BAR_LOCATION} from './index';
7+
import {MatProgressBar} from './progress-bar';
58

69

710
describe('MatProgressBar', () => {
811
let fakePath = '/fake-path';
912

10-
function createComponent<T>(componentType: Type<T>): ComponentFixture<T> {
13+
function createComponent<T>(componentType: Type<T>,
14+
imports?: Array<Type<{}>>): ComponentFixture<T> {
1115
TestBed.configureTestingModule({
12-
imports: [MatProgressBarModule],
16+
imports: imports || [MatProgressBarModule],
1317
declarations: [componentType],
1418
providers: [{
1519
provide: MAT_PROGRESS_BAR_LOCATION,
@@ -20,119 +24,210 @@ describe('MatProgressBar', () => {
2024
return TestBed.createComponent<T>(componentType);
2125
}
2226

23-
describe('basic progress-bar', () => {
24-
it('should apply a mode of "determinate" if no mode is provided.', () => {
25-
const fixture = createComponent(BasicProgressBar);
26-
fixture.detectChanges();
27+
describe('with animation', () => {
28+
describe('basic progress-bar', () => {
29+
it('should apply a mode of "determinate" if no mode is provided.', () => {
30+
const fixture = createComponent(BasicProgressBar);
31+
fixture.detectChanges();
32+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
33+
expect(progressElement.componentInstance.mode).toBe('determinate');
34+
});
2735

28-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
29-
expect(progressElement.componentInstance.mode).toBe('determinate');
30-
});
36+
it('should define default values for value and bufferValue attributes', () => {
37+
const fixture = createComponent(BasicProgressBar);
38+
fixture.detectChanges();
39+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
40+
expect(progressElement.componentInstance.value).toBe(0);
41+
expect(progressElement.componentInstance.bufferValue).toBe(0);
42+
});
3143

32-
it('should define default values for value and bufferValue attributes', () => {
33-
const fixture = createComponent(BasicProgressBar);
34-
fixture.detectChanges();
44+
it('should clamp value and bufferValue between 0 and 100', () => {
45+
const fixture = createComponent(BasicProgressBar);
46+
fixture.detectChanges();
3547

36-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
37-
expect(progressElement.componentInstance.value).toBe(0);
38-
expect(progressElement.componentInstance.bufferValue).toBe(0);
39-
});
48+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
49+
const progressComponent = progressElement.componentInstance;
4050

41-
it('should clamp value and bufferValue between 0 and 100', () => {
42-
const fixture = createComponent(BasicProgressBar);
43-
fixture.detectChanges();
51+
progressComponent.value = 50;
52+
expect(progressComponent.value).toBe(50);
4453

45-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
46-
const progressComponent = progressElement.componentInstance;
54+
progressComponent.value = 999;
55+
expect(progressComponent.value).toBe(100);
4756

48-
progressComponent.value = 50;
49-
expect(progressComponent.value).toBe(50);
57+
progressComponent.value = -10;
58+
expect(progressComponent.value).toBe(0);
5059

51-
progressComponent.value = 999;
52-
expect(progressComponent.value).toBe(100);
60+
progressComponent.bufferValue = -29;
61+
expect(progressComponent.bufferValue).toBe(0);
5362

54-
progressComponent.value = -10;
55-
expect(progressComponent.value).toBe(0);
63+
progressComponent.bufferValue = 9;
64+
expect(progressComponent.bufferValue).toBe(9);
5665

57-
progressComponent.bufferValue = -29;
58-
expect(progressComponent.bufferValue).toBe(0);
66+
progressComponent.bufferValue = 1320;
67+
expect(progressComponent.bufferValue).toBe(100);
68+
});
5969

60-
progressComponent.bufferValue = 9;
61-
expect(progressComponent.bufferValue).toBe(9);
70+
it('should return the transform attribute for bufferValue and mode', () => {
71+
const fixture = createComponent(BasicProgressBar);
72+
fixture.detectChanges();
6273

63-
progressComponent.bufferValue = 1320;
64-
expect(progressComponent.bufferValue).toBe(100);
65-
});
74+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
75+
const progressComponent = progressElement.componentInstance;
6676

67-
it('should return the transform attribute for bufferValue and mode', () => {
68-
const fixture = createComponent(BasicProgressBar);
69-
fixture.detectChanges();
77+
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0)'});
78+
expect(progressComponent._bufferTransform()).toBe(undefined);
7079

71-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
72-
const progressComponent = progressElement.componentInstance;
80+
progressComponent.value = 40;
81+
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.4)'});
82+
expect(progressComponent._bufferTransform()).toBe(undefined);
7383

74-
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0)'});
75-
expect(progressComponent._bufferTransform()).toBe(undefined);
84+
progressComponent.value = 35;
85+
progressComponent.bufferValue = 55;
86+
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.35)'});
87+
expect(progressComponent._bufferTransform()).toBe(undefined);
7688

77-
progressComponent.value = 40;
78-
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.4)'});
79-
expect(progressComponent._bufferTransform()).toBe(undefined);
89+
progressComponent.mode = 'buffer';
90+
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.35)'});
91+
expect(progressComponent._bufferTransform()).toEqual({transform: 'scaleX(0.55)'});
92+
93+
94+
progressComponent.value = 60;
95+
progressComponent.bufferValue = 60;
96+
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.6)'});
97+
expect(progressComponent._bufferTransform()).toEqual({transform: 'scaleX(0.6)'});
98+
});
99+
100+
it('should prefix SVG references with the current path', () => {
101+
const fixture = createComponent(BasicProgressBar);
102+
fixture.detectChanges();
80103

81-
progressComponent.value = 35;
82-
progressComponent.bufferValue = 55;
83-
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.35)'});
84-
expect(progressComponent._bufferTransform()).toBe(undefined);
104+
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
105+
expect(rect.getAttribute('fill')).toMatch(/^url\(['"]?\/fake-path#.*['"]?\)$/);
106+
});
85107

86-
progressComponent.mode = 'buffer';
87-
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.35)'});
88-
expect(progressComponent._bufferTransform()).toEqual({transform: 'scaleX(0.55)'});
108+
it('should account for location hash when prefixing the SVG references', () => {
109+
fakePath = '/fake-path#anchor';
89110

111+
const fixture = createComponent(BasicProgressBar);
112+
fixture.detectChanges();
90113

91-
progressComponent.value = 60;
92-
progressComponent.bufferValue = 60;
93-
expect(progressComponent._primaryTransform()).toEqual({transform: 'scaleX(0.6)'});
94-
expect(progressComponent._bufferTransform()).toEqual({transform: 'scaleX(0.6)'});
114+
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
115+
expect(rect.getAttribute('fill')).not.toContain('#anchor#');
116+
});
117+
118+
it('should not be able to tab into the underlying SVG element', () => {
119+
const fixture = createComponent(BasicProgressBar);
120+
fixture.detectChanges();
121+
122+
const svg = fixture.debugElement.query(By.css('svg')).nativeElement;
123+
expect(svg.getAttribute('focusable')).toBe('false');
124+
});
95125
});
96126

97-
it('should prefix SVG references with the current path', () => {
98-
const fixture = createComponent(BasicProgressBar);
99-
fixture.detectChanges();
127+
describe('animation trigger on determinate setting', () => {
128+
let fixture: ComponentFixture<BasicProgressBar>;
129+
let progressComponent: MatProgressBar;
130+
let primaryValueBar: DebugElement;
131+
132+
beforeEach(() => {
133+
fixture = createComponent(BasicProgressBar);
134+
135+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
136+
progressComponent = progressElement.componentInstance;
137+
primaryValueBar = progressElement.query(By.css('.mat-progress-bar-primary'));
138+
});
100139

101-
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
102-
expect(rect.getAttribute('fill')).toMatch(/^url\(['"]?\/fake-path#.*['"]?\)$/);
140+
it('should trigger output event on primary value bar animation end', () => {
141+
fixture.detectChanges();
142+
spyOn(progressComponent.animationEnd, 'next');
143+
144+
progressComponent.value = 40;
145+
expect(progressComponent.animationEnd.next).not.toHaveBeenCalled();
146+
147+
// On animation end, output should be emitted.
148+
dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
149+
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
150+
});
103151
});
104152

105-
it('should account for location hash when prefixing the SVG references', () => {
106-
fakePath = '/fake-path#anchor';
153+
describe('animation trigger on buffer setting', () => {
154+
let fixture: ComponentFixture<BufferProgressBar>;
155+
let progressComponent: MatProgressBar;
156+
let primaryValueBar: DebugElement;
107157

108-
const fixture = createComponent(BasicProgressBar);
109-
fixture.detectChanges();
158+
beforeEach(() => {
159+
fixture = createComponent(BufferProgressBar);
160+
161+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
162+
progressComponent = progressElement.componentInstance;
163+
primaryValueBar = progressElement.query(By.css('.mat-progress-bar-primary'));
164+
});
165+
166+
it('should bind on transitionend eventListener on primaryBarValue', () => {
167+
spyOn(primaryValueBar.nativeElement, 'addEventListener');
168+
fixture.detectChanges();
169+
170+
expect(primaryValueBar.nativeElement.addEventListener).toHaveBeenCalled();
171+
expect(primaryValueBar.nativeElement.addEventListener
172+
.calls.mostRecent().args[0]).toBe('transitionend');
173+
});
174+
175+
it('should trigger output event on primary value bar animation end', () => {
176+
fixture.detectChanges();
177+
spyOn(progressComponent.animationEnd, 'next');
178+
179+
progressComponent.value = 40;
180+
expect(progressComponent.animationEnd.next).not.toHaveBeenCalled();
181+
182+
// On animation end, output should be emitted.
183+
dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
184+
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
185+
});
186+
187+
it('should trigger output event with value not bufferValue', () => {
188+
fixture.detectChanges();
189+
spyOn(progressComponent.animationEnd, 'next');
110190

111-
const rect = fixture.debugElement.query(By.css('rect')).nativeElement;
112-
expect(rect.getAttribute('fill')).not.toContain('#anchor#');
191+
progressComponent.value = 40;
192+
progressComponent.bufferValue = 70;
193+
expect(progressComponent.animationEnd.next).not.toHaveBeenCalled();
194+
195+
// On animation end, output should be emitted.
196+
dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
197+
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
198+
});
113199
});
200+
});
201+
202+
describe('With NoopAnimations', () => {
203+
let progressComponent: MatProgressBar;
204+
let primaryValueBar: DebugElement;
205+
let fixture: ComponentFixture<BasicProgressBar>;
206+
207+
beforeEach(async(() => {
208+
fixture = createComponent(BasicProgressBar, [MatProgressBarModule, NoopAnimationsModule]);
209+
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
210+
progressComponent = progressElement.componentInstance;
211+
primaryValueBar = progressElement.query(By.css('.mat-progress-bar-primary'));
212+
}));
114213

115-
it('should not be able to tab into the underlying SVG element', () => {
116-
const fixture = createComponent(BasicProgressBar);
214+
it('should not bind transition end listener', () => {
215+
spyOn(primaryValueBar.nativeElement, 'addEventListener');
117216
fixture.detectChanges();
118217

119-
const svg = fixture.debugElement.query(By.css('svg')).nativeElement;
120-
expect(svg.getAttribute('focusable')).toBe('false');
218+
expect(primaryValueBar.nativeElement.addEventListener).not.toHaveBeenCalled();
121219
});
122-
});
123220

124-
describe('buffer progress-bar', () => {
125-
it('should not modify the mode if a valid mode is provided.', () => {
126-
const fixture = createComponent(BufferProgressBar);
221+
it('should trigger the animationEnd output on value set', () => {
127222
fixture.detectChanges();
223+
spyOn(progressComponent.animationEnd, 'next');
128224

129-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'));
130-
expect(progressElement.componentInstance.mode).toBe('buffer');
225+
progressComponent.value = 40;
226+
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
131227
});
132228
});
133229
});
134230

135-
136231
@Component({template: '<mat-progress-bar></mat-progress-bar>'})
137232
class BasicProgressBar { }
138233

0 commit comments

Comments
 (0)