fix(animations): ensure all child elements are rendered before running animations

Closes #9402
Closes #9775
This commit is contained in:
Matias Niemelä 2016-07-01 16:01:57 -07:00
parent 7073cf74fe
commit cbe85a0893
20 changed files with 283 additions and 81 deletions

View File

@ -271,7 +271,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
statements.push(_ANIMATION_FACTORY_VIEW_VAR
.callMethod(
'registerAndStartAnimation',
'queueAnimation',
[
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_PLAYER_VAR

View File

@ -8,6 +8,7 @@
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
import * as cdAst from '../expression_parser/ast';
import {Map} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
@ -36,6 +37,8 @@ function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: `
}
var _animationViewCheckedFlagMap = new Map<CompileView, boolean>();
function bind(
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
@ -171,6 +174,12 @@ function bindAndWriteToRenderer(
animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
.toStmt());
if (!_animationViewCheckedFlagMap.get(view)) {
_animationViewCheckedFlagMap.set(view, true);
view.afterViewLifecycleCallbacksMethod.addStmt(
o.THIS_EXPR.callMethod('triggerQueuedAnimations', []).toStmt());
}
break;
}

View File

@ -14,6 +14,8 @@ import {AnimationPlayer} from './animation_player';
export class AnimationGroupPlayer implements AnimationPlayer {
private _subscriptions: Function[] = [];
private _finished = false;
private _started = false;
public parentPlayer: AnimationPlayer = null;
constructor(private _players: AnimationPlayer[]) {
@ -44,9 +46,19 @@ export class AnimationGroupPlayer implements AnimationPlayer {
}
}
init(): void { this._players.forEach(player => player.init()); }
onDone(fn: Function): void { this._subscriptions.push(fn); }
play() { this._players.forEach(player => player.play()); }
hasStarted() { return this._started; }
play() {
if (!isPresent(this.parentPlayer)) {
this.init();
}
this._started = true;
this._players.forEach(player => player.play());
}
pause(): void { this._players.forEach(player => player.pause()); }

View File

@ -15,6 +15,8 @@ import {scheduleMicroTask} from '../facade/lang';
*/
export abstract class AnimationPlayer {
abstract onDone(fn: Function): void;
abstract init(): void;
abstract hasStarted(): boolean;
abstract play(): void;
abstract pause(): void;
abstract restart(): void;
@ -31,6 +33,7 @@ export abstract class AnimationPlayer {
export class NoOpAnimationPlayer implements AnimationPlayer {
private _subscriptions: any[] /** TODO #9100 */ = [];
private _started = false;
public parentPlayer: AnimationPlayer = null;
constructor() { scheduleMicroTask(() => this._onFinish()); }
/** @internal */
@ -39,7 +42,9 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
this._subscriptions = [];
}
onDone(fn: Function): void { this._subscriptions.push(fn); }
play(): void {}
hasStarted(): boolean { return this._started; }
init(): void {}
play(): void { this._started = true; }
pause(): void {}
restart(): void {}
finish(): void { this._onFinish(); }

View File

@ -15,6 +15,7 @@ export class AnimationSequencePlayer implements AnimationPlayer {
private _activePlayer: AnimationPlayer;
private _subscriptions: Function[] = [];
private _finished = false;
private _started: boolean = false;
public parentPlayer: AnimationPlayer = null;
@ -54,9 +55,19 @@ export class AnimationSequencePlayer implements AnimationPlayer {
}
}
init(): void { this._players.forEach(player => player.init()); }
onDone(fn: Function): void { this._subscriptions.push(fn); }
play(): void { this._activePlayer.play(); }
hasStarted() { return this._started; }
play(): void {
if (!isPresent(this.parentPlayer)) {
this.init();
}
this._started = true;
this._activePlayer.play();
}
pause(): void { this._activePlayer.pause(); }

View File

@ -11,7 +11,7 @@ import {isPresent} from '../facade/lang';
import {AnimationPlayer} from './animation_player';
export class ActiveAnimationPlayersMap {
export class ViewAnimationMap {
private _map = new Map<any, {[key: string]: AnimationPlayer}>();
private _allPlayers: AnimationPlayer[] = [];
@ -25,9 +25,9 @@ export class ActiveAnimationPlayersMap {
}
findAllPlayersByElement(element: any): AnimationPlayer[] {
var players: any[] /** TODO #9100 */ = [];
var players: AnimationPlayer[] = [];
StringMapWrapper.forEach(
this._map.get(element), (player: any /** TODO #9100 */) => players.push(player));
this._map.get(element), (player: AnimationPlayer) => players.push(player));
return players;
}

View File

@ -28,7 +28,7 @@ import {AnimationPlayer} from '../animation/animation_player';
import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationKeyframe} from '../animation/animation_keyframe';
import {AnimationStyles} from '../animation/animation_styles';
import {ActiveAnimationPlayersMap} from '../animation/active_animation_players_map';
import {ViewAnimationMap} from '../animation/view_animation_map';
var _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
@ -54,7 +54,7 @@ export abstract class AppView<T> {
private _hasExternalHostElement: boolean;
public activeAnimationPlayers = new ActiveAnimationPlayersMap();
public animationPlayers = new ViewAnimationMap();
public context: T;
@ -74,21 +74,27 @@ export abstract class AppView<T> {
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false) {
if (removeAllAnimations) {
this.activeAnimationPlayers.findAllPlayersByElement(element).forEach(
player => player.destroy());
this.animationPlayers.findAllPlayersByElement(element).forEach(player => player.destroy());
} else {
var player = this.activeAnimationPlayers.find(element, animationName);
var player = this.animationPlayers.find(element, animationName);
if (isPresent(player)) {
player.destroy();
}
}
}
registerAndStartAnimation(element: any, animationName: string, player: AnimationPlayer): void {
this.activeAnimationPlayers.set(element, animationName, player);
player.onDone(() => { this.activeAnimationPlayers.remove(element, animationName); });
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
this.animationPlayers.set(element, animationName, player);
player.onDone(() => { this.animationPlayers.remove(element, animationName); });
}
triggerQueuedAnimations() {
this.animationPlayers.getAllPlayers().forEach(player => {
if (!player.hasStarted()) {
player.play();
}
});
}
create(context: T, givenProjectableNodes: Array<any|any[]>, rootSelectorOrNode: string|any):
AppElement {
@ -201,10 +207,10 @@ export abstract class AppView<T> {
this.destroyInternal();
this.dirtyParentQueriesInternal();
if (this.activeAnimationPlayers.length == 0) {
if (this.animationPlayers.length == 0) {
this.renderer.destroyView(hostElement, this.allNodes);
} else {
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
var player = new AnimationGroupPlayer(this.animationPlayers.getAllPlayers());
player.onDone(() => { this.renderer.destroyView(hostElement, this.allNodes); });
}
}
@ -221,10 +227,10 @@ export abstract class AppView<T> {
detach(): void {
this.detachInternal();
if (this.activeAnimationPlayers.length == 0) {
if (this.animationPlayers.length == 0) {
this.renderer.detachView(this.flatRootNodes);
} else {
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
var player = new AnimationGroupPlayer(this.animationPlayers.getAllPlayers());
player.onDone(() => { this.renderer.detachView(this.flatRootNodes); });
}
}

View File

@ -9,10 +9,10 @@
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {el} from '@angular/platform-browser/testing/browser_util';
import {ActiveAnimationPlayersMap} from '../../src/animation/active_animation_players_map';
import {MockAnimationPlayer} from '../../../platform-browser/testing/mock_animation_player';
import {ViewAnimationMap} from '../../src/animation/view_animation_map';
import {isPresent} from '../../src/facade/lang';
import {fakeAsync, flushMicrotasks} from '../../testing';
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '../../testing/testing_internal';
export function main() {
@ -22,7 +22,7 @@ export function main() {
var animationName = 'animationName';
beforeEach(() => {
playersMap = new ActiveAnimationPlayersMap();
playersMap = new ViewAnimationMap();
elementNode = el('<div></div>');
});

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {MockAnimationPlayer} from '../../../platform-browser/testing/mock_animation_player';
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
import {isPresent} from '../../src/facade/lang';
import {fakeAsync, flushMicrotasks} from '../../testing';
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '../../testing/testing_internal';
export function main() {

View File

@ -12,9 +12,13 @@ import {TestComponentBuilder} from '@angular/compiler/testing';
import {AnimationDriver} from '@angular/platform-browser/src/dom/animation_driver';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver';
import {MockAnimationPlayer} from '@angular/platform-browser/testing/mock_animation_player';
import {Component} from '../../index';
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
import {AnimationPlayer} from '../../src/animation/animation_player';
import {AnimationStyles} from '../../src/animation/animation_styles';
import {AnimationEntryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
import {AUTO_STYLE} from '../../src/animation/metadata';
import {IS_DART, isArray, isPresent} from '../../src/facade/lang';
@ -26,8 +30,7 @@ export function main() {
declareTests({useJit: false});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
// describe('no jit', () => { declareTests({useJit: false}); });
}
}
@ -748,6 +751,76 @@ function declareTests({useJit}: {useJit: boolean}) {
})));
});
describe('DOM order tracking', () => {
if (!getDOM().supportsDOMEvents()) return;
beforeEachProviders(
() => [{provide: AnimationDriver, useClass: InnerContentTrackingAnimationDriver}]);
it('should evaluate all inner children and their bindings before running the animation on a parent',
inject(
[TestComponentBuilder, AnimationDriver],
fakeAsync((tcb: TestComponentBuilder, driver: InnerContentTrackingAnimationDriver) => {
makeAnimationCmp(
tcb, `<div class="target" @status="exp">
<div *ngIf="exp2" class="inner">inner child guy</div>
</div>`,
[trigger(
'status',
[
state('final', style({'height': '*'})),
transition('* => *', [animate(1000)])
])],
(fixture: any /** TODO #9100 */) => {
tick();
var cmp = fixture.debugElement.componentInstance;
var node =
getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
cmp.exp = true;
cmp.exp2 = true;
fixture.detectChanges();
flushMicrotasks();
var animation = driver.log.pop();
var player = <InnerContentTrackingAnimationPlayer>animation['player'];
expect(player.capturedInnerText).toEqual('inner child guy');
});
})));
it('should run the initialization stage after all children have been evaluated',
inject(
[TestComponentBuilder, AnimationDriver],
fakeAsync((tcb: TestComponentBuilder, driver: InnerContentTrackingAnimationDriver) => {
makeAnimationCmp(
tcb, `<div class="target" @status="exp">
<div style="height:20px"></div>
<div *ngIf="exp2" style="height:40px;" class="inner">inner child guy</div>
</div>`,
[trigger('status', [transition('* => *', sequence([
animate(1000, style({height: 0})),
animate(1000, style({height: '*'}))
]))])],
(fixture: any /** TODO #9100 */) => {
tick();
var cmp = fixture.debugElement.componentInstance;
cmp.exp = true;
cmp.exp2 = true;
fixture.detectChanges();
flushMicrotasks();
fixture.detectChanges();
var animation = driver.log.pop();
var player = <InnerContentTrackingAnimationPlayer>animation['player'];
// this is just to confirm that the player is using the parent element
expect(player.element.className).toEqual('target');
expect(player.computedHeight).toEqual('60px');
});
})));
});
describe('animation states', () => {
it('should retain the destination animation state styles once the animation is complete',
inject(
@ -1049,3 +1122,22 @@ class DummyIfCmp {
exp = false;
exp2 = false;
}
class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
super.animate(element, startingStyles, keyframes, duration, delay, easing);
var player = new InnerContentTrackingAnimationPlayer(element);
this.log[this.log.length - 1]['player'] = player;
return player;
}
}
class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
constructor(public element: any) { super(); }
public computedHeight: number;
public capturedInnerText: string;
init() { this.computedHeight = getDOM().getComputedStyle(this.element)['height']; }
play() { this.capturedInnerText = this.element.querySelector('.inner').innerText; }
}

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {MockAnimationPlayer} from '../../../platform-browser/testing/mock_animation_player';
import {AnimationSequencePlayer} from '../../src/animation/animation_sequence_player';
import {isPresent} from '../../src/facade/lang';
import {fakeAsync, flushMicrotasks} from '../../testing';
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '../../testing/testing_internal';
export function main() {

View File

@ -13,7 +13,7 @@ import {Math, global, isFunction, isPromise} from '../src/facade/lang';
import {AsyncTestCompleter} from './async_test_completer';
import {getTestInjector, inject} from './test_injector';
export {MockAnimationPlayer} from './animation/mock_animation_player';
export {MockAnimationPlayer} from '../../platform-browser/testing/mock_animation_player';
export {AsyncTestCompleter} from './async_test_completer';
export {inject} from './test_injector';
export {expect} from './testing';

View File

@ -11,9 +11,7 @@ import {AUTO_STYLE, BaseException} from '@angular/core';
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, NoOpAnimationPlayer} from '../../core_private';
import {StringMapWrapper} from '../facade/collection';
import {StringWrapper, isNumber, isPresent} from '../facade/lang';
import {AnimationDriver} from './animation_driver';
import {getDOM} from './dom_adapter';
import {DomAnimatePlayer} from './dom_animate_player';
import {dashCaseToCamelCase} from './util';
import {WebAnimationsPlayer} from './web_animations_player';
@ -21,19 +19,17 @@ import {WebAnimationsPlayer} from './web_animations_player';
export class WebAnimationsDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
var anyElm = <any>element;
duration: number, delay: number, easing: string): WebAnimationsPlayer {
var formattedSteps: {[key: string]: string | number}[] = [];
var startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
startingStyleLookup = _populateStyles(anyElm, startingStyles, {});
startingStyleLookup = _populateStyles(element, startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
}
keyframes.forEach((keyframe: AnimationKeyframe) => {
let data = _populateStyles(anyElm, keyframe.styles, startingStyleLookup);
let data = _populateStyles(element, keyframe.styles, startingStyleLookup);
data['offset'] = keyframe.offset;
formattedSteps.push(data);
});
@ -60,14 +56,7 @@ export class WebAnimationsDriver implements AnimationDriver {
playerOptions['easing'] = easing;
}
var player = this._triggerWebAnimation(anyElm, formattedSteps, playerOptions);
return new WebAnimationsPlayer(player, duration);
}
/** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
return elm.animate(keyframes, options);
return new WebAnimationsPlayer(element, formattedSteps, playerOptions);
}
}
@ -78,9 +67,8 @@ function _populateStyles(
styles.styles.forEach((entry) => {
StringMapWrapper.forEach(entry, (val: any, prop: string) => {
var formattedProp = dashCaseToCamelCase(prop);
data[formattedProp] = val == AUTO_STYLE ?
_computeStyle(element, formattedProp) :
val.toString() + _resolveStyleUnit(val, prop, formattedProp);
data[formattedProp] =
val == AUTO_STYLE ? val : val.toString() + _resolveStyleUnit(val, prop, formattedProp);
});
});
StringMapWrapper.forEach(defaultStyles, (value: string, prop: string) => {
@ -154,7 +142,3 @@ function _isPixelDimensionStyle(prop: string): boolean {
return false;
}
}
function _computeStyle(element: any, prop: string): string {
return getDOM().getComputedStyle(element)[prop];
}

View File

@ -6,20 +6,29 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AUTO_STYLE} from '@angular/core';
import {AnimationPlayer} from '../../core_private';
import {StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang';
import {getDOM} from './dom_adapter';
import {DomAnimatePlayer} from './dom_animate_player';
export class WebAnimationsPlayer implements AnimationPlayer {
private _subscriptions: Function[] = [];
private _finished = false;
private _initialized = false;
private _player: DomAnimatePlayer;
private _started: boolean = false;
private _duration: number;
public parentPlayer: AnimationPlayer = null;
constructor(private _player: DomAnimatePlayer, public totalTime: number) {
// this is required to make the player startable at a later time
this.reset();
this._player.onfinish = () => this._onFinish();
constructor(
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) {
this._duration = <number>options['duration'];
}
private _onFinish() {
@ -33,13 +42,46 @@ export class WebAnimationsPlayer implements AnimationPlayer {
}
}
init(): void {
if (this._initialized) return;
this._initialized = true;
var anyElm = <any>this.element;
var keyframes = this.keyframes.map(styles => {
var formattedKeyframe: {[key: string]: string | number} = {};
StringMapWrapper.forEach(styles, (value: string | number, prop: string) => {
formattedKeyframe[prop] = value == AUTO_STYLE ? _computeStyle(anyElm, prop) : value;
});
return formattedKeyframe;
});
this._player = this._triggerWebAnimation(anyElm, keyframes, this.options);
// this is required so that the player doesn't start to animate right away
this.reset();
this._player.onfinish = () => this._onFinish();
}
/** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
return <DomAnimatePlayer>elm.animate(keyframes, options);
}
onDone(fn: Function): void { this._subscriptions.push(fn); }
play(): void { this._player.play(); }
play(): void {
this.init();
this._player.play();
}
pause(): void { this._player.pause(); }
pause(): void {
this.init();
this._player.pause();
}
finish(): void {
this.init();
this._onFinish();
this._player.finish();
}
@ -51,12 +93,20 @@ export class WebAnimationsPlayer implements AnimationPlayer {
this.play();
}
hasStarted(): boolean { return this._started; }
destroy(): void {
this.reset();
this._onFinish();
}
setPosition(p: any /** TODO #9100 */): void { this._player.currentTime = p * this.totalTime; }
get totalTime(): number { return this._duration; }
setPosition(p: number): void { this._player.currentTime = p * this.totalTime; }
getPosition(): number { return this._player.currentTime / this.totalTime; }
}
function _computeStyle(element: any, prop: string): string {
return getDOM().getComputedStyle(element)[prop];
}

View File

@ -12,6 +12,7 @@ import {el} from '@angular/platform-browser/testing/browser_util';
import {AnimationKeyframe, AnimationStyles} from '../../core_private';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
import {StringMapWrapper} from '../../src/facade/collection';
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
@ -51,8 +52,8 @@ export function main() {
_makeKeyframe(1, {'font-size': '555px'})
];
driver.animate(elm, startingStyles, styles, 0, 0, 'linear');
var details = driver.log.pop();
var player = driver.animate(elm, startingStyles, styles, 0, 0, 'linear');
var details = _formatOptions(player);
var startKeyframe = details['keyframes'][0];
var firstKeyframe = details['keyframes'][1];
var lastKeyframe = details['keyframes'][2];
@ -71,8 +72,8 @@ export function main() {
var startingStyles = _makeStyles({'borderTopWidth': 40});
var styles = [_makeKeyframe(0, {'font-size': 100}), _makeKeyframe(1, {'height': '555em'})];
driver.animate(elm, startingStyles, styles, 0, 0, 'linear');
var details = driver.log.pop();
var player = driver.animate(elm, startingStyles, styles, 0, 0, 'linear');
var details = _formatOptions(player);
var startKeyframe = details['keyframes'][0];
var firstKeyframe = details['keyframes'][1];
var lastKeyframe = details['keyframes'][2];
@ -88,8 +89,8 @@ export function main() {
var startingStyles = _makeStyles({});
var styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
var details = driver.log.pop();
var player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
var details = _formatOptions(player);
var options = details['options'];
expect(options['fill']).toEqual('both');
});
@ -98,8 +99,8 @@ export function main() {
var startingStyles = _makeStyles({});
var styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
var details = driver.log.pop();
var player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
var details = _formatOptions(player);
var options = details['options'];
expect(options['easing']).toEqual('ease-out');
});
@ -108,11 +109,15 @@ export function main() {
var startingStyles = _makeStyles({});
var styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
driver.animate(elm, startingStyles, styles, 1000, 1000, null);
var details = driver.log.pop();
var player = driver.animate(elm, startingStyles, styles, 1000, 1000, null);
var details = _formatOptions(player);
var options = details['options'];
var keys = StringMapWrapper.keys(options);
expect(keys.indexOf('easing')).toEqual(-1);
});
});
}
function _formatOptions(player: WebAnimationsPlayer): {[key: string]: any} {
return {'element': player.element, 'keyframes': player.keyframes, 'options': player.options};
}

View File

@ -7,16 +7,32 @@
*/
import {AsyncTestCompleter, MockAnimationPlayer, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
public domPlayer = new MockDomAnimatePlayer();
constructor(
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) {
super(element, keyframes, options);
}
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
return this.domPlayer;
}
}
export function main() {
function makePlayer(): {[key: string]: any} {
var mockPlayer = new MockDomAnimatePlayer();
var c = mockPlayer.captures;
var p = new WebAnimationsPlayer(mockPlayer, 0);
return {'captures': c, 'player': p};
var someElm = el('<div></div>');
var player = new ExtendedWebAnimationsPlayer(someElm, [], {});
player.init();
return {'captures': player.domPlayer.captures, 'player': player};
}
describe('WebAnimationsPlayer', () => {

View File

@ -14,7 +14,7 @@ import {AnimationDriver} from '../src/dom/animation_driver';
import {StringMapWrapper} from '../src/facade/collection';
export class MockAnimationDriver extends AnimationDriver {
log: any[] /** TODO #9100 */ = [];
public log: {[key: string]: any}[] = [];
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
@ -38,11 +38,9 @@ function _serializeKeyframes(keyframes: AnimationKeyframe[]): any[] {
}
function _serializeStyles(styles: AnimationStyles): {[key: string]: any} {
var flatStyles = {};
styles.styles.forEach(
entry => StringMapWrapper.forEach(
entry, (val: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
(flatStyles as any /** TODO #9100 */)[prop] = val;
var flatStyles: {[key: string]: any} = {};
styles.styles.forEach(entry => StringMapWrapper.forEach(entry, (val: any, prop: string) => {
flatStyles[prop] = val;
}));
return flatStyles;
}

View File

@ -6,13 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AnimationPlayer} from '../../src/animation/animation_player';
import {isPresent} from '../../src/facade/lang';
import {AnimationPlayer} from '../../core/src/animation/animation_player';
import {isPresent} from '../../core/src/facade/lang';
export class MockAnimationPlayer implements AnimationPlayer {
private _subscriptions: any[] /** TODO #9100 */ = [];
private _finished = false;
private _destroyed = false;
private _started: boolean = false;
public parentPlayer: AnimationPlayer = null;
public log: any[] /** TODO #9100 */ = [];
@ -30,9 +32,16 @@ export class MockAnimationPlayer implements AnimationPlayer {
}
}
init(): void { this.log.push('init'); }
onDone(fn: Function): void { this._subscriptions.push(fn); }
play(): void { this.log.push('play'); }
hasStarted() { return this._started; }
play(): void {
this._started = true;
this.log.push('play');
}
pause(): void { this.log.push('pause'); }

View File

@ -32,6 +32,9 @@ import {
<hr />
<div *ngFor="let item of items" class="box" @boxAnimation="state">
{{ item }}
<div *ngIf="true">
something inside
</div>
</div>
</div>
`,

View File

@ -67,6 +67,8 @@ export declare abstract class AnimationPlayer {
abstract destroy(): void;
abstract finish(): void;
abstract getPosition(): number;
abstract hasStarted(): boolean;
abstract init(): void;
abstract onDone(fn: Function): void;
abstract pause(): void;
abstract play(): void;