From 54a6e4ff9ea9795a456bfd452e81384b90540037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Fri, 12 May 2017 17:32:51 -0400 Subject: [PATCH] refactor(animations): make animation testing work with fixture.whenRenderingDone --- .../src/render/animation_engine_next.ts | 2 + .../src/render/transition_animation_engine.ts | 10 ++ packages/core/src/render/api.ts | 1 + packages/core/src/view/services.ts | 7 ++ .../animation/animation_integration_spec.ts | 110 ++++++++++++++++++ packages/core/test/component_fixture_spec.ts | 19 --- .../core/testing/src/component_fixture.ts | 20 +++- .../animations/src/animation_renderer.ts | 2 + tools/public_api_guard/core/core.d.ts | 1 + 9 files changed, 149 insertions(+), 23 deletions(-) diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts index e84d9d3d57..84b5ab03f2 100644 --- a/packages/animations/browser/src/render/animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -92,4 +92,6 @@ export class AnimationEngine { return (this._transitionEngine.players as AnimationPlayer[]) .concat(this._timelineEngine.players as AnimationPlayer[]); } + + whenRenderingDone(): Promise { return this._transitionEngine.whenRenderingDone(); } } diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index 1824ba0abd..f3d53e9716 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -617,6 +617,16 @@ export class TransitionAnimationEngine { }); } + whenRenderingDone(): Promise { + return new Promise(resolve => { + if (this.players.length) { + return optimizeGroupPlayer(this.players).onDone(() => resolve()); + } else { + resolve(); + } + }); + } + flush() { let players: AnimationPlayer[] = []; if (this.newHostElements.size) { diff --git a/packages/core/src/render/api.ts b/packages/core/src/render/api.ts index 5a54ff0a9f..a741f5224e 100644 --- a/packages/core/src/render/api.ts +++ b/packages/core/src/render/api.ts @@ -130,6 +130,7 @@ export abstract class RendererFactory2 { abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2; abstract begin?(): void; abstract end?(): void; + abstract whenRenderingDone?(): Promise; } /** diff --git a/packages/core/src/view/services.ts b/packages/core/src/view/services.ts index b33fcc0f01..61df5225a7 100644 --- a/packages/core/src/view/services.ts +++ b/packages/core/src/view/services.ts @@ -607,6 +607,13 @@ class DebugRendererFactory2 implements RendererFactory2 { this.delegate.end(); } } + + whenRenderingDone(): Promise { + if (this.delegate.whenRenderingDone) { + return this.delegate.whenRenderingDone(); + } + return Promise.resolve(null); + } } diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 1fa6d59166..b96ac7cdc0 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -37,6 +37,116 @@ export function main() { }); }); + describe('component fixture integration', () => { + describe('whenRenderingDone', () => { + it('should wait until the animations are finished until continuing', fakeAsync(() => { + @Component({ + selector: 'cmp', + template: ` +
+ `, + animations: [trigger( + 'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])] + }) + class Cmp { + exp: any = false; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + + let isDone = false; + fixture.whenRenderingDone().then(() => isDone = true); + expect(isDone).toBe(false); + + cmp.exp = 'on'; + fixture.detectChanges(); + engine.flush(); + expect(isDone).toBe(false); + + const players = engine.players; + expect(players.length).toEqual(1); + players[0].finish(); + expect(isDone).toBe(false); + + flushMicrotasks(); + expect(isDone).toBe(true); + })); + + it('should wait for a noop animation to finish before continuing', fakeAsync(() => { + @Component({ + selector: 'cmp', + template: ` +
+ `, + animations: [trigger( + 'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])] + }) + class Cmp { + exp: any = false; + } + + TestBed.configureTestingModule({ + providers: [{provide: AnimationDriver, useClass: ɵNoopAnimationDriver}], + declarations: [Cmp] + }); + + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + + let isDone = false; + fixture.whenRenderingDone().then(() => isDone = true); + expect(isDone).toBe(false); + + cmp.exp = 'off'; + fixture.detectChanges(); + engine.flush(); + expect(isDone).toBe(false); + + flushMicrotasks(); + expect(isDone).toBe(true); + })); + + it('should wait for active animations to finish even if they have already started', + fakeAsync(() => { + @Component({ + selector: 'cmp', + template: ` +
+ `, + animations: [trigger( + 'myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])] + }) + class Cmp { + exp: any = false; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.exp = 'on'; + fixture.detectChanges(); + engine.flush(); + + const players = engine.players; + expect(players.length).toEqual(1); + + let isDone = false; + fixture.whenRenderingDone().then(() => isDone = true); + flushMicrotasks(); + expect(isDone).toBe(false); + + players[0].finish(); + flushMicrotasks(); + expect(isDone).toBe(true); + })); + }); + }); + describe('animation triggers', () => { it('should trigger a state change animation from void => state', () => { @Component({ diff --git a/packages/core/test/component_fixture_spec.ts b/packages/core/test/component_fixture_spec.ts index 8b78fe270c..97abe805e6 100644 --- a/packages/core/test/component_fixture_spec.ts +++ b/packages/core/test/component_fixture_spec.ts @@ -158,25 +158,6 @@ export function main() { }); })); - it('should signal through whenRenderingDone when the fixture is stable', async(() => { - const componentFixture = TestBed.createComponent(AsyncComp); - - componentFixture.detectChanges(); - expect(componentFixture.nativeElement).toHaveText('1'); - - const element = componentFixture.debugElement.children[0]; - dispatchEvent(element.nativeElement, 'click'); - expect(componentFixture.nativeElement).toHaveText('1'); - - // Component is updated asynchronously. Wait for the fixture to become stable - // before checking. - componentFixture.whenRenderingDone().then((waited) => { - expect(waited).toBe(true); - componentFixture.detectChanges(); - expect(componentFixture.nativeElement).toHaveText('11'); - }); - })); - it('should wait for macroTask(setTimeout) while checking for whenStable ' + '(autoDetectChanges)', async(() => { diff --git a/packages/core/testing/src/component_fixture.ts b/packages/core/testing/src/component_fixture.ts index ba7bc98314..fe83d7ff23 100644 --- a/packages/core/testing/src/component_fixture.ts +++ b/packages/core/testing/src/component_fixture.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, ComponentRef, DebugElement, ElementRef, NgZone, getDebugNode} from '@angular/core'; +import {ChangeDetectorRef, ComponentRef, DebugElement, ElementRef, NgZone, RendererFactory2, getDebugNode} from '@angular/core'; /** @@ -40,6 +40,7 @@ export class ComponentFixture { */ changeDetectorRef: ChangeDetectorRef; + private _renderer: RendererFactory2|null|undefined; private _isStable: boolean = true; private _isDestroyed: boolean = false; private _resolve: ((result: any) => void)|null = null; @@ -160,11 +161,22 @@ export class ComponentFixture { } } + + private _getRenderer() { + if (this._renderer === undefined) { + this._renderer = this.componentRef.injector.get(RendererFactory2, null); + } + return this._renderer as RendererFactory2 | null; + } + /** - * Get a promise that resolves when the ui state is stable following animations. - */ + * Get a promise that resolves when the ui state is stable following animations. + */ whenRenderingDone(): Promise { - // this is temporary until this is functional + const renderer = this._getRenderer(); + if (renderer && renderer.whenRenderingDone) { + return renderer.whenRenderingDone(); + } return this.whenStable(); } diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index 011acaaa81..81250a3c14 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -53,6 +53,8 @@ export class AnimationRendererFactory implements RendererFactory2 { this.delegate.end(); } } + + whenRenderingDone(): Promise { return this._engine.whenRenderingDone(); } } export class AnimationRenderer implements Renderer2 { diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 62fd4d37cb..37bca83628 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -846,6 +846,7 @@ export declare abstract class RendererFactory2 { abstract begin?(): void; abstract createRenderer(hostElement: any, type: RendererType2 | null): Renderer2; abstract end?(): void; + abstract whenRenderingDone?(): Promise; } /** @experimental */