fix(animations): only require one flushMicrotasks call when testing animations
This commit is contained in:
parent
eed67ddafb
commit
6cb93c1fac
|
@ -86,7 +86,7 @@ export class AnimationEngine {
|
|||
return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback);
|
||||
}
|
||||
|
||||
flush(): void { this._transitionEngine.flush(); }
|
||||
flush(countId: number = -1): void { this._transitionEngine.flush(countId); }
|
||||
|
||||
get players(): AnimationPlayer[] {
|
||||
return (this._transitionEngine.players as AnimationPlayer[])
|
||||
|
|
|
@ -80,9 +80,14 @@ export function listenOnPlayer(
|
|||
|
||||
export function copyAnimationEvent(
|
||||
e: AnimationEvent, phaseName?: string, totalTime?: number): AnimationEvent {
|
||||
return makeAnimationEvent(
|
||||
const event = makeAnimationEvent(
|
||||
e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName,
|
||||
totalTime == undefined ? e.totalTime : totalTime);
|
||||
const data = (e as any)['_data'];
|
||||
if (data != null) {
|
||||
(event as any)['_data'] = data;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
export function makeAnimationEvent(
|
||||
|
|
|
@ -382,7 +382,7 @@ export class AnimationTransitionNamespace {
|
|||
|
||||
insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); }
|
||||
|
||||
drainQueuedTransitions(): QueueInstruction[] {
|
||||
drainQueuedTransitions(countId: number): QueueInstruction[] {
|
||||
const instructions: QueueInstruction[] = [];
|
||||
this._queue.forEach(entry => {
|
||||
const player = entry.player;
|
||||
|
@ -395,6 +395,7 @@ export class AnimationTransitionNamespace {
|
|||
if (listener.name == entry.triggerName) {
|
||||
const baseEvent = makeAnimationEvent(
|
||||
element, entry.triggerName, entry.fromState.value, entry.toState.value);
|
||||
(baseEvent as any)['_data'] = countId;
|
||||
listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback);
|
||||
}
|
||||
});
|
||||
|
@ -627,7 +628,7 @@ export class TransitionAnimationEngine {
|
|||
});
|
||||
}
|
||||
|
||||
flush() {
|
||||
flush(countId: number = -1) {
|
||||
let players: AnimationPlayer[] = [];
|
||||
if (this.newHostElements.size) {
|
||||
this.newHostElements.forEach((ns, element) => { this._balanceNamespaceList(ns, element); });
|
||||
|
@ -635,7 +636,7 @@ export class TransitionAnimationEngine {
|
|||
}
|
||||
|
||||
if (this._namespaceList.length && (this.totalQueuedPlayers || this.queuedRemovals.size)) {
|
||||
players = this._flushAnimations();
|
||||
players = this._flushAnimations(countId);
|
||||
}
|
||||
|
||||
this.totalQueuedPlayers = 0;
|
||||
|
@ -659,7 +660,7 @@ export class TransitionAnimationEngine {
|
|||
}
|
||||
}
|
||||
|
||||
private _flushAnimations(): TransitionAnimationPlayer[] {
|
||||
private _flushAnimations(countId: number): TransitionAnimationPlayer[] {
|
||||
const subTimelines = new ElementInstructionMap();
|
||||
const skippedPlayers: TransitionAnimationPlayer[] = [];
|
||||
const skippedPlayersMap = new Map<any, AnimationPlayer[]>();
|
||||
|
@ -677,8 +678,9 @@ export class TransitionAnimationEngine {
|
|||
|
||||
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
|
||||
const ns = this._namespaceList[i];
|
||||
ns.drainQueuedTransitions().forEach(entry => {
|
||||
ns.drainQueuedTransitions(countId).forEach(entry => {
|
||||
const player = entry.player;
|
||||
|
||||
const element = entry.element;
|
||||
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
||||
player.destroy();
|
||||
|
@ -746,7 +748,7 @@ export class TransitionAnimationEngine {
|
|||
}
|
||||
});
|
||||
|
||||
allPreviousPlayersMap.forEach(players => { players.forEach(player => player.destroy()); });
|
||||
allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy()));
|
||||
|
||||
const leaveNodes: any[] = bodyNode && allPostStyleElements.size ?
|
||||
listToArray(this.driver.query(bodyNode, LEAVE_SELECTOR, true)) :
|
||||
|
|
|
@ -292,6 +292,7 @@ export function main() {
|
|||
setProperty(element, engine, 'myTrigger', '456');
|
||||
engine.flush();
|
||||
|
||||
delete (capture as any)['_data'];
|
||||
expect(capture).toEqual({
|
||||
element,
|
||||
triggerName: 'myTrigger',
|
||||
|
@ -305,6 +306,7 @@ export function main() {
|
|||
const player = engine.players.pop() !;
|
||||
player.finish();
|
||||
|
||||
delete (capture as any)['_data'];
|
||||
expect(capture).toEqual({
|
||||
element,
|
||||
triggerName: 'myTrigger',
|
||||
|
|
|
@ -37,6 +37,51 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fakeAsync testing', () => {
|
||||
it('should only require one flushMicrotasks call to kick off animation callbacks',
|
||||
fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp" (@myAnimation.start)="cb('start')" (@myAnimation.done)="cb('done')"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition('* => on, * => off', [animate(1000, style({opacity: 1}))])])]
|
||||
})
|
||||
class Cmp {
|
||||
exp: any = false;
|
||||
status: string = '';
|
||||
cb(status: string) { this.status = status; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'on';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.status).toEqual('');
|
||||
|
||||
flushMicrotasks();
|
||||
expect(cmp.status).toEqual('start');
|
||||
|
||||
let player = MockAnimationDriver.log.pop() !;
|
||||
player.finish();
|
||||
expect(cmp.status).toEqual('done');
|
||||
|
||||
cmp.status = '';
|
||||
cmp.exp = 'off';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.status).toEqual('');
|
||||
|
||||
player = MockAnimationDriver.log.pop() !;
|
||||
player.finish();
|
||||
expect(cmp.status).toEqual('');
|
||||
flushMicrotasks();
|
||||
expect(cmp.status).toEqual('done');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('component fixture integration', () => {
|
||||
describe('whenRenderingDone', () => {
|
||||
it('should wait until the animations are finished until continuing', fakeAsync(() => {
|
||||
|
|
|
@ -12,6 +12,8 @@ import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, Re
|
|||
@Injectable()
|
||||
export class AnimationRendererFactory implements RendererFactory2 {
|
||||
private _currentId: number = 0;
|
||||
private _currentFlushId: number = 1;
|
||||
private _animationCallbacksBuffer: [(e: any) => any, any][] = [];
|
||||
|
||||
constructor(
|
||||
private delegate: RendererFactory2, private _engine: AnimationEngine, private _zone: NgZone) {
|
||||
|
@ -38,7 +40,7 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
|||
animationTriggers.forEach(
|
||||
trigger => this._engine.registerTrigger(
|
||||
componentId, namespaceId, hostElement, trigger.name, trigger));
|
||||
return new AnimationRenderer(delegate, this._engine, this._zone, namespaceId);
|
||||
return new AnimationRenderer(this, delegate, this._engine, this._zone, namespaceId);
|
||||
}
|
||||
|
||||
begin() {
|
||||
|
@ -47,8 +49,38 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
|||
}
|
||||
}
|
||||
|
||||
private _scheduleCountTask() {
|
||||
Zone.current.scheduleMicroTask(
|
||||
'incremenet the animation microtask', () => { this._currentFlushId++; });
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) {
|
||||
if (count >= 0 && count < this._currentFlushId) {
|
||||
this._zone.run(() => fn(data));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._animationCallbacksBuffer.length == 0) {
|
||||
Promise.resolve(null).then(() => {
|
||||
this._zone.run(() => {
|
||||
this._animationCallbacksBuffer.forEach(tuple => {
|
||||
const [fn, data] = tuple;
|
||||
fn(data);
|
||||
});
|
||||
this._animationCallbacksBuffer = [];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this._animationCallbacksBuffer.push([fn, data]);
|
||||
}
|
||||
|
||||
end() {
|
||||
this._zone.runOutsideAngular(() => this._engine.flush());
|
||||
this._zone.runOutsideAngular(() => {
|
||||
this._scheduleCountTask();
|
||||
this._engine.flush(this._currentFlushId);
|
||||
});
|
||||
if (this.delegate.end) {
|
||||
this.delegate.end();
|
||||
}
|
||||
|
@ -59,11 +91,11 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
|||
|
||||
export class AnimationRenderer implements Renderer2 {
|
||||
public destroyNode: ((node: any) => any)|null = null;
|
||||
private _animationCallbacksBuffer: [(e: any) => any, any][] = [];
|
||||
public microtaskCount: number = 0;
|
||||
|
||||
constructor(
|
||||
public delegate: Renderer2, private _engine: AnimationEngine, private _zone: NgZone,
|
||||
private _namespaceId: string) {
|
||||
private _factory: AnimationRendererFactory, public delegate: Renderer2,
|
||||
private _engine: AnimationEngine, private _zone: NgZone, private _namespaceId: string) {
|
||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode !(n) : null;
|
||||
}
|
||||
|
||||
|
@ -145,27 +177,12 @@ export class AnimationRenderer implements Renderer2 {
|
|||
[name, phase] = parseTriggerCallbackName(name);
|
||||
}
|
||||
return this._engine.listen(this._namespaceId, element, name, phase, event => {
|
||||
this._bufferMicrotaskIntoZone(callback, event);
|
||||
const countId = (event as any)['_data'] || -1;
|
||||
this._factory.scheduleListenerCallback(countId, callback, event);
|
||||
});
|
||||
}
|
||||
return this.delegate.listen(target, eventName, callback);
|
||||
}
|
||||
|
||||
private _bufferMicrotaskIntoZone(fn: (e: any) => any, data: any) {
|
||||
if (this._animationCallbacksBuffer.length == 0) {
|
||||
Promise.resolve(null).then(() => {
|
||||
this._zone.run(() => {
|
||||
this._animationCallbacksBuffer.forEach(tuple => {
|
||||
const [fn, data] = tuple;
|
||||
fn(data);
|
||||
});
|
||||
this._animationCallbacksBuffer = [];
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
this._animationCallbacksBuffer.push([fn, data]);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveElementFromTarget(target: 'window' | 'document' | 'body' | any): any {
|
||||
|
|
Loading…
Reference in New Issue