fix(animations): ensure web-animations are caught within the Angular zone
Closes #11881 Closes #11712 Closes #12355 Closes #11881 Closes #12546 Closes #12707 Closes #12774
This commit is contained in:
parent
6e35d13fbc
commit
f80a157b65
|
@ -8,11 +8,18 @@
|
|||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {DomElementSchemaRegistry, ElementSchemaRegistry} from '@angular/compiler';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {AnimationDriver} from '@angular/platform-browser/src/dom/animation_driver';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {WebAnimationsDriver} from '@angular/platform-browser/src/dom/web_animations_driver';
|
||||
import {WebAnimationsPlayer} from '@angular/platform-browser/src/dom/web_animations_player';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver';
|
||||
|
||||
import {Component} from '../../index';
|
||||
import {DomAnimatePlayer} from '../../../platform-browser/src/dom/dom_animate_player';
|
||||
import {ApplicationRef, Component, HostBinding, HostListener, NgModule, NgZone, destroyPlatform} from '../../index';
|
||||
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
||||
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||
|
@ -2068,6 +2075,57 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('full animation integration tests', () => {
|
||||
if (!getDOM().supportsWebAnimation()) return;
|
||||
|
||||
var el: any, testProviders: any[];
|
||||
|
||||
beforeEach(() => {
|
||||
destroyPlatform();
|
||||
|
||||
let fakeDoc = getDOM().createHtmlDocument();
|
||||
el = getDOM().createElement('animation-app', fakeDoc);
|
||||
getDOM().appendChild(fakeDoc.body, el);
|
||||
testProviders = [
|
||||
{provide: DOCUMENT, useValue: fakeDoc},
|
||||
{provide: AnimationDriver, useClass: ExtendedWebAnimationsDriver}
|
||||
];
|
||||
});
|
||||
|
||||
afterEach(() => { destroyPlatform(); });
|
||||
|
||||
it('should automatically run change detection when the animation done callback code updates any bindings',
|
||||
(asyncDone: Function) => {
|
||||
bootstrap(AnimationAppCmp, testProviders).then(ref => {
|
||||
let appRef = <ApplicationRef>ref.injector.get(ApplicationRef);
|
||||
let appCmp: AnimationAppCmp =
|
||||
appRef.components.find(cmp => cmp.componentType === AnimationAppCmp).instance;
|
||||
let driver: ExtendedWebAnimationsDriver = ref.injector.get(AnimationDriver);
|
||||
let zone: NgZone = ref.injector.get(NgZone);
|
||||
let text = '';
|
||||
zone.run(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: pending/);
|
||||
expect(text).toMatch(/Animation Time: 0/);
|
||||
appCmp.animationStatus = 'on';
|
||||
setTimeout(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: started/);
|
||||
expect(text).toMatch(/Animation Time: 555/);
|
||||
var player = driver.players.pop().domPlayer;
|
||||
getDOM().dispatchEvent(player, getDOM().createEvent('finish'));
|
||||
setTimeout(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: done/);
|
||||
expect(text).toMatch(/Animation Time: 555/);
|
||||
asyncDone();
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
|
||||
|
@ -2150,3 +2208,60 @@ class _NaiveElementSchema extends DomElementSchemaRegistry {
|
|||
return {error: null, value: <string>val};
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'animation-app',
|
||||
animations: [trigger('animationStatus', [transition('off => on', animate(555))])],
|
||||
template: `
|
||||
Animation Time: {{ time }}
|
||||
Animation Status: {{ status }}
|
||||
`
|
||||
})
|
||||
class AnimationAppCmp {
|
||||
time: number = 0;
|
||||
status: string = 'pending';
|
||||
|
||||
@HostBinding('@animationStatus')
|
||||
animationStatus = 'off';
|
||||
|
||||
@HostListener('@animationStatus.start', ['$event'])
|
||||
onStart(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'started';
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('@animationStatus.done', ['$event'])
|
||||
onDone(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'done';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimationTestModule {}
|
||||
function bootstrap(cmpType: any, providers: any[]): Promise<any> {
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
providers: providers,
|
||||
declarations: [cmpType],
|
||||
bootstrap: [cmpType]
|
||||
})
|
||||
class AnimationTestModule {
|
||||
}
|
||||
return platformBrowserDynamic().bootstrapModule(AnimationTestModule);
|
||||
}
|
||||
|
||||
class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
|
||||
players: WebAnimationsPlayer[] = [];
|
||||
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): WebAnimationsPlayer {
|
||||
var player = super.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
this.players.push(player);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,6 @@ export interface DomAnimatePlayer {
|
|||
onfinish: Function;
|
||||
position: number;
|
||||
currentTime: number;
|
||||
addEventListener(eventName: string, handler: (event: any) => any): any;
|
||||
dispatchEvent(eventName: string): any;
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
|||
|
||||
// this is required so that the player doesn't start to animate right away
|
||||
this._resetDomPlayerState();
|
||||
this._player.onfinish = () => this._onFinish();
|
||||
this._player.addEventListener('finish', () => this._onFinish());
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -63,6 +63,8 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
|||
return <DomAnimatePlayer>element.animate(keyframes, options);
|
||||
}
|
||||
|
||||
get domPlayer() { return this._player; }
|
||||
|
||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||
|
||||
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
||||
|
|
|
@ -14,7 +14,7 @@ import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
|
|||
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
|
||||
|
||||
class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
||||
public domPlayer = new MockDomAnimatePlayer();
|
||||
private _overriddenDomPlayer = new MockDomAnimatePlayer();
|
||||
|
||||
constructor(
|
||||
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
|
||||
|
@ -22,9 +22,11 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
|||
super(element, keyframes, options);
|
||||
}
|
||||
|
||||
get domPlayer() { return this._overriddenDomPlayer; }
|
||||
|
||||
/** @internal */
|
||||
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
|
||||
return this.domPlayer;
|
||||
return this._overriddenDomPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,4 +40,10 @@ export class MockDomAnimatePlayer implements DomAnimatePlayer {
|
|||
this._position = val;
|
||||
}
|
||||
get position(): number { return this._position; }
|
||||
addEventListener(eventName: string, handler: (event: any) => any): any {
|
||||
if (eventName == 'finish') {
|
||||
this.onfinish = handler;
|
||||
}
|
||||
}
|
||||
dispatchEvent(eventName: string): any {}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue