feat(animations): noop animation module and zone fixes (#14661)
This commit is contained in:

committed by
Igor Minar

parent
ab3527c99b
commit
e8d2743cfb
@ -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();
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user