refactor(animations): make animation testing work with fixture.whenRenderingDone

This commit is contained in:
Matias Niemelä 2017-05-12 17:32:51 -04:00 committed by Jason Aden
parent 8a6eb1ac78
commit 54a6e4ff9e
9 changed files with 149 additions and 23 deletions

View File

@ -92,4 +92,6 @@ export class AnimationEngine {
return (this._transitionEngine.players as AnimationPlayer[])
.concat(this._timelineEngine.players as AnimationPlayer[]);
}
whenRenderingDone(): Promise<any> { return this._transitionEngine.whenRenderingDone(); }
}

View File

@ -617,6 +617,16 @@ export class TransitionAnimationEngine {
});
}
whenRenderingDone(): Promise<any> {
return new Promise(resolve => {
if (this.players.length) {
return optimizeGroupPlayer(this.players).onDone(() => resolve());
} else {
resolve();
}
});
}
flush() {
let players: AnimationPlayer[] = [];
if (this.newHostElements.size) {

View File

@ -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<any>;
}
/**

View File

@ -607,6 +607,13 @@ class DebugRendererFactory2 implements RendererFactory2 {
this.delegate.end();
}
}
whenRenderingDone(): Promise<any> {
if (this.delegate.whenRenderingDone) {
return this.delegate.whenRenderingDone();
}
return Promise.resolve(null);
}
}

View File

@ -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: `
<div [@myAnimation]="exp"></div>
`,
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: `
<div [@myAnimation]="exp"></div>
`,
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: `
<div [@myAnimation]="exp"></div>
`,
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({

View File

@ -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(() => {

View File

@ -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<T> {
*/
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<T> {
}
}
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.
*/
whenRenderingDone(): Promise<any> {
// this is temporary until this is functional
const renderer = this._getRenderer();
if (renderer && renderer.whenRenderingDone) {
return renderer.whenRenderingDone();
}
return this.whenStable();
}

View File

@ -53,6 +53,8 @@ export class AnimationRendererFactory implements RendererFactory2 {
this.delegate.end();
}
}
whenRenderingDone(): Promise<any> { return this._engine.whenRenderingDone(); }
}
export class AnimationRenderer implements Renderer2 {

View File

@ -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<any>;
}
/** @experimental */