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 {CommonModule} from '@angular/common';
|
||||||
import {DomElementSchemaRegistry, ElementSchemaRegistry} from '@angular/compiler';
|
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 {AnimationDriver} from '@angular/platform-browser/src/dom/animation_driver';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
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 {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 {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
||||||
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
||||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
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 {
|
class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
|
||||||
|
@ -2150,3 +2208,60 @@ class _NaiveElementSchema extends DomElementSchemaRegistry {
|
||||||
return {error: null, value: <string>val};
|
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;
|
onfinish: Function;
|
||||||
position: number;
|
position: number;
|
||||||
currentTime: 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 is required so that the player doesn't start to animate right away
|
||||||
this._resetDomPlayerState();
|
this._resetDomPlayerState();
|
||||||
this._player.onfinish = () => this._onFinish();
|
this._player.addEventListener('finish', () => this._onFinish());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -63,6 +63,8 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||||
return <DomAnimatePlayer>element.animate(keyframes, options);
|
return <DomAnimatePlayer>element.animate(keyframes, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get domPlayer() { return this._player; }
|
||||||
|
|
||||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||||
|
|
||||||
onDone(fn: () => void): void { this._onDoneFns.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';
|
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
|
||||||
|
|
||||||
class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
||||||
public domPlayer = new MockDomAnimatePlayer();
|
private _overriddenDomPlayer = new MockDomAnimatePlayer();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
|
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
|
||||||
|
@ -22,9 +22,11 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
||||||
super(element, keyframes, options);
|
super(element, keyframes, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get domPlayer() { return this._overriddenDomPlayer; }
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
|
_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;
|
this._position = val;
|
||||||
}
|
}
|
||||||
get position(): number { return this._position; }
|
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