Skip to content

Commit 54a6e4f

Browse files
matskojasonaden
authored andcommitted
refactor(animations): make animation testing work with fixture.whenRenderingDone
1 parent 8a6eb1a commit 54a6e4f

File tree

9 files changed

+149
-23
lines changed

9 files changed

+149
-23
lines changed

packages/animations/browser/src/render/animation_engine_next.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,6 @@ export class AnimationEngine {
9292
return (this._transitionEngine.players as AnimationPlayer[])
9393
.concat(this._timelineEngine.players as AnimationPlayer[]);
9494
}
95+
96+
whenRenderingDone(): Promise<any> { return this._transitionEngine.whenRenderingDone(); }
9597
}

packages/animations/browser/src/render/transition_animation_engine.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,16 @@ export class TransitionAnimationEngine {
617617
});
618618
}
619619

620+
whenRenderingDone(): Promise<any> {
621+
return new Promise(resolve => {
622+
if (this.players.length) {
623+
return optimizeGroupPlayer(this.players).onDone(() => resolve());
624+
} else {
625+
resolve();
626+
}
627+
});
628+
}
629+
620630
flush() {
621631
let players: AnimationPlayer[] = [];
622632
if (this.newHostElements.size) {

packages/core/src/render/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export abstract class RendererFactory2 {
130130
abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2;
131131
abstract begin?(): void;
132132
abstract end?(): void;
133+
abstract whenRenderingDone?(): Promise<any>;
133134
}
134135

135136
/**

packages/core/src/view/services.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,13 @@ class DebugRendererFactory2 implements RendererFactory2 {
607607
this.delegate.end();
608608
}
609609
}
610+
611+
whenRenderingDone(): Promise<any> {
612+
if (this.delegate.whenRenderingDone) {
613+
return this.delegate.whenRenderingDone();
614+
}
615+
return Promise.resolve(null);
616+
}
610617
}
611618

612619

packages/core/test/animation/animation_integration_spec.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,116 @@ export function main() {
3737
});
3838
});
3939

40+
describe('component fixture integration', () => {
41+
describe('whenRenderingDone', () => {
42+
it('should wait until the animations are finished until continuing', fakeAsync(() => {
43+
@Component({
44+
selector: 'cmp',
45+
template: `
46+
<div [@myAnimation]="exp"></div>
47+
`,
48+
animations: [trigger(
49+
'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])]
50+
})
51+
class Cmp {
52+
exp: any = false;
53+
}
54+
55+
TestBed.configureTestingModule({declarations: [Cmp]});
56+
const engine = TestBed.get(ɵAnimationEngine);
57+
const fixture = TestBed.createComponent(Cmp);
58+
const cmp = fixture.componentInstance;
59+
60+
let isDone = false;
61+
fixture.whenRenderingDone().then(() => isDone = true);
62+
expect(isDone).toBe(false);
63+
64+
cmp.exp = 'on';
65+
fixture.detectChanges();
66+
engine.flush();
67+
expect(isDone).toBe(false);
68+
69+
const players = engine.players;
70+
expect(players.length).toEqual(1);
71+
players[0].finish();
72+
expect(isDone).toBe(false);
73+
74+
flushMicrotasks();
75+
expect(isDone).toBe(true);
76+
}));
77+
78+
it('should wait for a noop animation to finish before continuing', fakeAsync(() => {
79+
@Component({
80+
selector: 'cmp',
81+
template: `
82+
<div [@myAnimation]="exp"></div>
83+
`,
84+
animations: [trigger(
85+
'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])]
86+
})
87+
class Cmp {
88+
exp: any = false;
89+
}
90+
91+
TestBed.configureTestingModule({
92+
providers: [{provide: AnimationDriver, useClass: ɵNoopAnimationDriver}],
93+
declarations: [Cmp]
94+
});
95+
96+
const engine = TestBed.get(ɵAnimationEngine);
97+
const fixture = TestBed.createComponent(Cmp);
98+
const cmp = fixture.componentInstance;
99+
100+
let isDone = false;
101+
fixture.whenRenderingDone().then(() => isDone = true);
102+
expect(isDone).toBe(false);
103+
104+
cmp.exp = 'off';
105+
fixture.detectChanges();
106+
engine.flush();
107+
expect(isDone).toBe(false);
108+
109+
flushMicrotasks();
110+
expect(isDone).toBe(true);
111+
}));
112+
113+
it('should wait for active animations to finish even if they have already started',
114+
fakeAsync(() => {
115+
@Component({
116+
selector: 'cmp',
117+
template: `
118+
<div [@myAnimation]="exp"></div>
119+
`,
120+
animations: [trigger(
121+
'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])]
122+
})
123+
class Cmp {
124+
exp: any = false;
125+
}
126+
127+
TestBed.configureTestingModule({declarations: [Cmp]});
128+
const engine = TestBed.get(ɵAnimationEngine);
129+
const fixture = TestBed.createComponent(Cmp);
130+
const cmp = fixture.componentInstance;
131+
cmp.exp = 'on';
132+
fixture.detectChanges();
133+
engine.flush();
134+
135+
const players = engine.players;
136+
expect(players.length).toEqual(1);
137+
138+
let isDone = false;
139+
fixture.whenRenderingDone().then(() => isDone = true);
140+
flushMicrotasks();
141+
expect(isDone).toBe(false);
142+
143+
players[0].finish();
144+
flushMicrotasks();
145+
expect(isDone).toBe(true);
146+
}));
147+
});
148+
});
149+
40150
describe('animation triggers', () => {
41151
it('should trigger a state change animation from void => state', () => {
42152
@Component({

packages/core/test/component_fixture_spec.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -158,25 +158,6 @@ export function main() {
158158
});
159159
}));
160160

161-
it('should signal through whenRenderingDone when the fixture is stable', async(() => {
162-
const componentFixture = TestBed.createComponent(AsyncComp);
163-
164-
componentFixture.detectChanges();
165-
expect(componentFixture.nativeElement).toHaveText('1');
166-
167-
const element = componentFixture.debugElement.children[0];
168-
dispatchEvent(element.nativeElement, 'click');
169-
expect(componentFixture.nativeElement).toHaveText('1');
170-
171-
// Component is updated asynchronously. Wait for the fixture to become stable
172-
// before checking.
173-
componentFixture.whenRenderingDone().then((waited) => {
174-
expect(waited).toBe(true);
175-
componentFixture.detectChanges();
176-
expect(componentFixture.nativeElement).toHaveText('11');
177-
});
178-
}));
179-
180161
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
181162
'(autoDetectChanges)',
182163
async(() => {

packages/core/testing/src/component_fixture.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ChangeDetectorRef, ComponentRef, DebugElement, ElementRef, NgZone, getDebugNode} from '@angular/core';
9+
import {ChangeDetectorRef, ComponentRef, DebugElement, ElementRef, NgZone, RendererFactory2, getDebugNode} from '@angular/core';
1010

1111

1212
/**
@@ -40,6 +40,7 @@ export class ComponentFixture<T> {
4040
*/
4141
changeDetectorRef: ChangeDetectorRef;
4242

43+
private _renderer: RendererFactory2|null|undefined;
4344
private _isStable: boolean = true;
4445
private _isDestroyed: boolean = false;
4546
private _resolve: ((result: any) => void)|null = null;
@@ -160,11 +161,22 @@ export class ComponentFixture<T> {
160161
}
161162
}
162163

164+
165+
private _getRenderer() {
166+
if (this._renderer === undefined) {
167+
this._renderer = this.componentRef.injector.get(RendererFactory2, null);
168+
}
169+
return this._renderer as RendererFactory2 | null;
170+
}
171+
163172
/**
164-
* Get a promise that resolves when the ui state is stable following animations.
165-
*/
173+
* Get a promise that resolves when the ui state is stable following animations.
174+
*/
166175
whenRenderingDone(): Promise<any> {
167-
// this is temporary until this is functional
176+
const renderer = this._getRenderer();
177+
if (renderer && renderer.whenRenderingDone) {
178+
return renderer.whenRenderingDone();
179+
}
168180
return this.whenStable();
169181
}
170182

packages/platform-browser/animations/src/animation_renderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export class AnimationRendererFactory implements RendererFactory2 {
5353
this.delegate.end();
5454
}
5555
}
56+
57+
whenRenderingDone(): Promise<any> { return this._engine.whenRenderingDone(); }
5658
}
5759

5860
export class AnimationRenderer implements Renderer2 {

tools/public_api_guard/core/core.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,7 @@ export declare abstract class RendererFactory2 {
846846
abstract begin?(): void;
847847
abstract createRenderer(hostElement: any, type: RendererType2 | null): Renderer2;
848848
abstract end?(): void;
849+
abstract whenRenderingDone?(): Promise<any>;
849850
}
850851

851852
/** @experimental */

0 commit comments

Comments
 (0)