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[]) return (this._transitionEngine.players as AnimationPlayer[])
.concat(this._timelineEngine.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() { flush() {
let players: AnimationPlayer[] = []; let players: AnimationPlayer[] = [];
if (this.newHostElements.size) { if (this.newHostElements.size) {

View File

@ -130,6 +130,7 @@ export abstract class RendererFactory2 {
abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2; abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2;
abstract begin?(): void; abstract begin?(): void;
abstract end?(): void; abstract end?(): void;
abstract whenRenderingDone?(): Promise<any>;
} }
/** /**

View File

@ -607,6 +607,13 @@ class DebugRendererFactory2 implements RendererFactory2 {
this.delegate.end(); 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', () => { describe('animation triggers', () => {
it('should trigger a state change animation from void => state', () => { it('should trigger a state change animation from void => state', () => {
@Component({ @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 ' + it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)', '(autoDetectChanges)',
async(() => { async(() => {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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; changeDetectorRef: ChangeDetectorRef;
private _renderer: RendererFactory2|null|undefined;
private _isStable: boolean = true; private _isStable: boolean = true;
private _isDestroyed: boolean = false; private _isDestroyed: boolean = false;
private _resolve: ((result: any) => void)|null = null; 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. * Get a promise that resolves when the ui state is stable following animations.
*/ */
whenRenderingDone(): Promise<any> { whenRenderingDone(): Promise<any> {
// this is temporary until this is functional const renderer = this._getRenderer();
if (renderer && renderer.whenRenderingDone) {
return renderer.whenRenderingDone();
}
return this.whenStable(); return this.whenStable();
} }

View File

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

View File

@ -846,6 +846,7 @@ export declare abstract class RendererFactory2 {
abstract begin?(): void; abstract begin?(): void;
abstract createRenderer(hostElement: any, type: RendererType2 | null): Renderer2; abstract createRenderer(hostElement: any, type: RendererType2 | null): Renderer2;
abstract end?(): void; abstract end?(): void;
abstract whenRenderingDone?(): Promise<any>;
} }
/** @experimental */ /** @experimental */