feat(animations): noop animation module and zone fixes (#14661)

This commit is contained in:
Matias Niemelä
2017-02-23 08:51:00 -08:00
committed by Igor Minar
parent ab3527c99b
commit e8d2743cfb
16 changed files with 608 additions and 104 deletions

View File

@ -12,7 +12,7 @@ import {el} from '@angular/platform-browser/testing/browser_util';
import {buildAnimationKeyframes} from '../../src/dsl/animation_timeline_visitor';
import {buildTrigger} from '../../src/dsl/animation_trigger';
import {AnimationStyleNormalizer, NoOpAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
import {AnimationEngine} from '../../src/render/animation_engine';
import {DomAnimationEngine} from '../../src/render/dom_animation_engine';
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/mock_animation_driver';
function makeTrigger(name: string, steps: any) {
@ -27,7 +27,7 @@ export function main() {
// these tests are only mean't to be run within the DOM
if (typeof Element == 'undefined') return;
describe('AnimationEngine', () => {
describe('DomAnimationEngine', () => {
let element: any;
beforeEach(() => {
@ -36,7 +36,7 @@ export function main() {
});
function makeEngine(normalizer: AnimationStyleNormalizer = null) {
return new AnimationEngine(driver, normalizer || new NoOpAnimationStyleNormalizer());
return new DomAnimationEngine(driver, normalizer || new NoOpAnimationStyleNormalizer());
}
describe('trigger registration', () => {
@ -292,51 +292,6 @@ export function main() {
});
});
describe('flushing animations', () => {
let ticks: (() => any)[];
let _raf: () => any;
beforeEach(() => {
ticks = [];
_raf = <() => any>AnimationEngine.raf;
AnimationEngine.raf = (cb: () => any) => { ticks.push(cb); };
});
afterEach(() => AnimationEngine.raf = _raf);
function flushTicks() {
ticks.forEach(tick => tick());
ticks = [];
}
it('should invoke queued transition animations after a requestAnimationFrame flushes', () => {
const engine = makeEngine();
engine.registerTrigger(trigger('myTrigger', [transition('* => *', animate(1234))]));
engine.setProperty(element, 'myTrigger', 'on');
expect(engine.queuedPlayers.length).toEqual(1);
expect(engine.activePlayers.length).toEqual(0);
flushTicks();
expect(engine.queuedPlayers.length).toEqual(0);
expect(engine.activePlayers.length).toEqual(1);
});
it('should not flush the animations twice when flushed right away before a frame changes',
() => {
const engine = makeEngine();
engine.registerTrigger(trigger('myTrigger', [transition('* => *', animate(1234))]));
engine.setProperty(element, 'myTrigger', 'on');
expect(engine.activePlayers.length).toEqual(0);
engine.flush();
expect(engine.activePlayers.length).toEqual(1);
flushTicks();
expect(engine.activePlayers.length).toEqual(1);
});
});
describe('instructions', () => {
it('should animate a transition instruction', () => {
const engine = makeEngine();

View File

@ -0,0 +1,209 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {state, style, trigger} from '@angular/animations';
import {el} from '@angular/platform-browser/testing/browser_util';
import {NoopAnimationEngine} from '../src/render/noop_animation_engine';
export function main() {
describe('NoopAnimationEngine', () => {
let captures: string[] = [];
function capture(value: string = null) { return (v: any = null) => captures.push(value || v); }
beforeEach(() => { captures = []; });
it('should immediately issue DOM removals during remove animations and then fire the animation callbacks after flush',
() => {
const engine = new NoopAnimationEngine();
const elm1 = {};
const elm2 = {};
engine.onRemove(elm1, capture('1'));
engine.onRemove(elm2, capture('2'));
engine.listen(elm1, 'trig', 'start', capture('1-start'));
engine.listen(elm2, 'trig', 'start', capture('2-start'));
engine.listen(elm1, 'trig', 'done', capture('1-done'));
engine.listen(elm2, 'trig', 'done', capture('2-done'));
expect(captures).toEqual(['1', '2']);
engine.flush();
expect(captures).toEqual(['1', '2', '1-start', '2-start', '1-done', '2-done']);
});
it('should only fire the `start` listener for a trigger that has had a property change', () => {
const engine = new NoopAnimationEngine();
const elm1 = {};
const elm2 = {};
const elm3 = {};
engine.listen(elm1, 'trig1', 'start', capture());
engine.setProperty(elm1, 'trig1', 'cool');
engine.setProperty(elm2, 'trig2', 'sweet');
engine.listen(elm2, 'trig2', 'start', capture());
engine.listen(elm3, 'trig3', 'start', capture());
expect(captures).toEqual([]);
engine.flush();
expect(captures.length).toEqual(2);
const trig1Data = captures.shift();
const trig2Data = captures.shift();
expect(trig1Data).toEqual({
element: elm1,
triggerName: 'trig1',
fromState: 'void',
toState: 'cool',
phaseName: 'start',
totalTime: 0
});
expect(trig2Data).toEqual({
element: elm2,
triggerName: 'trig2',
fromState: 'void',
toState: 'sweet',
phaseName: 'start',
totalTime: 0
});
captures = [];
engine.flush();
expect(captures).toEqual([]);
});
it('should only fire the `done` listener for a trigger that has had a property change', () => {
const engine = new NoopAnimationEngine();
const elm1 = {};
const elm2 = {};
const elm3 = {};
engine.listen(elm1, 'trig1', 'done', capture());
engine.setProperty(elm1, 'trig1', 'awesome');
engine.setProperty(elm2, 'trig2', 'amazing');
engine.listen(elm2, 'trig2', 'done', capture());
engine.listen(elm3, 'trig3', 'done', capture());
expect(captures).toEqual([]);
engine.flush();
expect(captures.length).toEqual(2);
const trig1Data = captures.shift();
const trig2Data = captures.shift();
expect(trig1Data).toEqual({
element: elm1,
triggerName: 'trig1',
fromState: 'void',
toState: 'awesome',
phaseName: 'done',
totalTime: 0
});
expect(trig2Data).toEqual({
element: elm2,
triggerName: 'trig2',
fromState: 'void',
toState: 'amazing',
phaseName: 'done',
totalTime: 0
});
captures = [];
engine.flush();
expect(captures).toEqual([]);
});
it('should deregister a listener when the return function is called', () => {
const engine = new NoopAnimationEngine();
const elm = {};
const fn1 = engine.listen(elm, 'trig1', 'start', capture('trig1-start'));
const fn2 = engine.listen(elm, 'trig2', 'done', capture('trig2-done'));
engine.setProperty(elm, 'trig1', 'value1');
engine.setProperty(elm, 'trig2', 'value2');
engine.flush();
expect(captures).toEqual(['trig1-start', 'trig2-done']);
captures = [];
engine.setProperty(elm, 'trig1', 'value3');
engine.setProperty(elm, 'trig2', 'value4');
fn1();
engine.flush();
expect(captures).toEqual(['trig2-done']);
captures = [];
engine.setProperty(elm, 'trig1', 'value5');
engine.setProperty(elm, 'trig2', 'value6');
fn2();
engine.flush();
expect(captures).toEqual([]);
});
describe('styling', () => {
// these tests are only mean't to be run within the DOM
if (typeof Element == 'undefined') return;
it('should persist the styles on the element when the animation is complete', () => {
const engine = new NoopAnimationEngine();
engine.registerTrigger(trigger('matias', [
state('a', style({width: '100px'})),
]));
const element = el('<div></div>');
expect(element.style.width).not.toEqual('100px');
engine.setProperty(element, 'matias', 'a');
expect(element.style.width).not.toEqual('100px');
engine.flush();
expect(element.style.width).toEqual('100px');
});
it('should remove previously persist styles off of the element when a follow-up animation starts',
() => {
const engine = new NoopAnimationEngine();
engine.registerTrigger(trigger('matias', [
state('a', style({width: '100px'})),
state('b', style({height: '100px'})),
]));
const element = el('<div></div>');
engine.setProperty(element, 'matias', 'a');
engine.flush();
expect(element.style.width).toEqual('100px');
engine.setProperty(element, 'matias', 'b');
expect(element.style.width).not.toEqual('100px');
expect(element.style.height).not.toEqual('100px');
engine.flush();
expect(element.style.height).toEqual('100px');
});
it('should fall back to `*` styles incase the target state styles are not found', () => {
const engine = new NoopAnimationEngine();
engine.registerTrigger(trigger('matias', [
state('*', style({opacity: '0.5'})),
]));
const element = el('<div></div>');
engine.setProperty(element, 'matias', 'xyz');
engine.flush();
expect(element.style.opacity).toEqual('0.5');
});
});
});
}

View File

@ -0,0 +1,67 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {animate, state, style, transition, trigger} from '@angular/animations';
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {ɵAnimationEngine} from '@angular/platform-browser/animations';
import {NoopBrowserAnimationModule} from '../src/noop_browser_animation_module';
import {NoopAnimationEngine} from '../src/render/noop_animation_engine';
export function main() {
describe('NoopBrowserAnimationModule', () => {
beforeEach(() => {
TestBed.configureTestingModule({imports: [NoopBrowserAnimationModule]});
TestBed.configureCompiler({
useJit: true,
providers: [{
provide: USE_VIEW_ENGINE,
useValue: true,
}]
});
});
it('the engine should be a Noop engine', () => {
const engine = TestBed.get(ɵAnimationEngine);
expect(engine instanceof NoopAnimationEngine).toBeTruthy();
});
it('should flush and fire callbacks when the zone becomes stable', (async) => {
@Component({
selector: 'my-cmp',
template:
'<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)" (@myAnimation.done)="onDone($event)"></div>',
animations: [trigger(
'myAnimation',
[transition(
'* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
})
class Cmp {
exp: any;
startEvent: any;
doneEvent: any;
onStart(event: any) { this.startEvent = event; }
onDone(event: any) { this.doneEvent = event; }
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = 'state';
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(cmp.startEvent.triggerName).toEqual('myAnimation');
expect(cmp.startEvent.phaseName).toEqual('start');
expect(cmp.doneEvent.triggerName).toEqual('myAnimation');
expect(cmp.doneEvent.phaseName).toEqual('done');
async();
});
});
});
}