@ -18,12 +18,9 @@ export class BrowserAnimationBuilder extends AnimationBuilder {
|
||||
|
||||
constructor(rootRenderer: RendererFactory2, @Inject(DOCUMENT) doc: any) {
|
||||
super();
|
||||
const typeData = {
|
||||
id: '0',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: [],
|
||||
data: {animation: []}
|
||||
} as RendererType2;
|
||||
const typeData =
|
||||
{id: '0', encapsulation: ViewEncapsulation.None, styles: [], data: {animation: []}} as
|
||||
RendererType2;
|
||||
this._renderer = rootRenderer.createRenderer(doc.body, typeData) as AnimationRenderer;
|
||||
}
|
||||
|
||||
@ -37,7 +34,9 @@ export class BrowserAnimationBuilder extends AnimationBuilder {
|
||||
}
|
||||
|
||||
export class BrowserAnimationFactory extends AnimationFactory {
|
||||
constructor(private _id: string, private _renderer: AnimationRenderer) { super(); }
|
||||
constructor(private _id: string, private _renderer: AnimationRenderer) {
|
||||
super();
|
||||
}
|
||||
|
||||
create(element: any, options?: AnimationOptions): AnimationPlayer {
|
||||
return new RendererAnimationPlayer(this._id, element, options || {}, this._renderer);
|
||||
@ -62,34 +61,58 @@ export class RendererAnimationPlayer implements AnimationPlayer {
|
||||
return issueAnimationCommand(this._renderer, this.element, this.id, command, args);
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void { this._listen('done', fn); }
|
||||
onDone(fn: () => void): void {
|
||||
this._listen('done', fn);
|
||||
}
|
||||
|
||||
onStart(fn: () => void): void { this._listen('start', fn); }
|
||||
onStart(fn: () => void): void {
|
||||
this._listen('start', fn);
|
||||
}
|
||||
|
||||
onDestroy(fn: () => void): void { this._listen('destroy', fn); }
|
||||
onDestroy(fn: () => void): void {
|
||||
this._listen('destroy', fn);
|
||||
}
|
||||
|
||||
init(): void { this._command('init'); }
|
||||
init(): void {
|
||||
this._command('init');
|
||||
}
|
||||
|
||||
hasStarted(): boolean { return this._started; }
|
||||
hasStarted(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
play(): void {
|
||||
this._command('play');
|
||||
this._started = true;
|
||||
}
|
||||
|
||||
pause(): void { this._command('pause'); }
|
||||
pause(): void {
|
||||
this._command('pause');
|
||||
}
|
||||
|
||||
restart(): void { this._command('restart'); }
|
||||
restart(): void {
|
||||
this._command('restart');
|
||||
}
|
||||
|
||||
finish(): void { this._command('finish'); }
|
||||
finish(): void {
|
||||
this._command('finish');
|
||||
}
|
||||
|
||||
destroy(): void { this._command('destroy'); }
|
||||
destroy(): void {
|
||||
this._command('destroy');
|
||||
}
|
||||
|
||||
reset(): void { this._command('reset'); }
|
||||
reset(): void {
|
||||
this._command('reset');
|
||||
}
|
||||
|
||||
setPosition(p: number): void { this._command('setPosition', p); }
|
||||
setPosition(p: number): void {
|
||||
this._command('setPosition', p);
|
||||
}
|
||||
|
||||
getPosition(): number { return 0; }
|
||||
getPosition(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public totalTime = 0;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ const DISABLE_ANIMATIONS_FLAG = '@.disabled';
|
||||
// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an
|
||||
// interface declaration is used as TypeScript prior to 3.7 does not support recursive type
|
||||
// references, see https://github.com/microsoft/TypeScript/pull/33050 for details.
|
||||
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata | RecursiveAnimationTriggerMetadata;
|
||||
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata;
|
||||
interface RecursiveAnimationTriggerMetadata extends Array<NestedAnimationTriggerMetadata> {}
|
||||
|
||||
@Injectable()
|
||||
@ -84,7 +84,9 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
|
||||
private _scheduleCountTask() {
|
||||
// always use promise to schedule microtask instead of use Zone
|
||||
this.promise.then(() => { this._microtaskId++; });
|
||||
this.promise.then(() => {
|
||||
this._microtaskId++;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -125,16 +127,20 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
}
|
||||
}
|
||||
|
||||
whenRenderingDone(): Promise<any> { return this.engine.whenRenderingDone(); }
|
||||
whenRenderingDone(): Promise<any> {
|
||||
return this.engine.whenRenderingDone();
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseAnimationRenderer implements Renderer2 {
|
||||
constructor(
|
||||
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine) {
|
||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode !(n) : null;
|
||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null;
|
||||
}
|
||||
|
||||
get data() { return this.delegate.data; }
|
||||
get data() {
|
||||
return this.delegate.data;
|
||||
}
|
||||
|
||||
destroyNode: ((n: any) => void)|null;
|
||||
|
||||
@ -147,9 +153,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
return this.delegate.createElement(name, namespace);
|
||||
}
|
||||
|
||||
createComment(value: string) { return this.delegate.createComment(value); }
|
||||
createComment(value: string) {
|
||||
return this.delegate.createComment(value);
|
||||
}
|
||||
|
||||
createText(value: string) { return this.delegate.createText(value); }
|
||||
createText(value: string) {
|
||||
return this.delegate.createText(value);
|
||||
}
|
||||
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
this.delegate.appendChild(parent, newChild);
|
||||
@ -169,9 +179,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
return this.delegate.selectRootElement(selectorOrNode, preserveContent);
|
||||
}
|
||||
|
||||
parentNode(node: any) { return this.delegate.parentNode(node); }
|
||||
parentNode(node: any) {
|
||||
return this.delegate.parentNode(node);
|
||||
}
|
||||
|
||||
nextSibling(node: any) { return this.delegate.nextSibling(node); }
|
||||
nextSibling(node: any) {
|
||||
return this.delegate.nextSibling(node);
|
||||
}
|
||||
|
||||
setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void {
|
||||
this.delegate.setAttribute(el, name, value, namespace);
|
||||
@ -181,9 +195,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
this.delegate.removeAttribute(el, name, namespace);
|
||||
}
|
||||
|
||||
addClass(el: any, name: string): void { this.delegate.addClass(el, name); }
|
||||
addClass(el: any, name: string): void {
|
||||
this.delegate.addClass(el, name);
|
||||
}
|
||||
|
||||
removeClass(el: any, name: string): void { this.delegate.removeClass(el, name); }
|
||||
removeClass(el: any, name: string): void {
|
||||
this.delegate.removeClass(el, name);
|
||||
}
|
||||
|
||||
setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void {
|
||||
this.delegate.setStyle(el, style, value, flags);
|
||||
@ -201,7 +219,9 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
}
|
||||
}
|
||||
|
||||
setValue(node: any, value: string): void { this.delegate.setValue(node, value); }
|
||||
setValue(node: any, value: string): void {
|
||||
this.delegate.setValue(node, value);
|
||||
}
|
||||
|
||||
listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void {
|
||||
return this.delegate.listen(target, eventName, callback);
|
||||
@ -253,7 +273,7 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer
|
||||
}
|
||||
}
|
||||
|
||||
function resolveElementFromTarget(target: 'window' | 'document' | 'body' | any): any {
|
||||
function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any {
|
||||
switch (target) {
|
||||
case 'body':
|
||||
return document.body;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {AnimationBuilder} from '@angular/animations';
|
||||
import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵCssKeyframesDriver as CssKeyframesDriver, ɵNoopAnimationDriver as NoopAnimationDriver, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, ɵsupportsWebAnimations as supportsWebAnimations} from '@angular/animations/browser';
|
||||
import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵCssKeyframesDriver as CssKeyframesDriver, ɵNoopAnimationDriver as NoopAnimationDriver, ɵsupportsWebAnimations as supportsWebAnimations, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer} from '@angular/animations/browser';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Inject, Injectable, InjectionToken, NgZone, Provider, RendererFactory2} from '@angular/core';
|
||||
import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
|
||||
|
@ -5,7 +5,7 @@
|
||||
* 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 {AnimationPlayer, AnimationTriggerMetadata, animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {animate, AnimationPlayer, AnimationTriggerMetadata, state, style, transition, trigger} from '@angular/animations';
|
||||
import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser';
|
||||
import {Component, Injectable, NgZone, RendererFactory2, RendererType2, ViewChild} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
@ -15,289 +15,139 @@ import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_rendere
|
||||
import {el} from '../../testing/src/browser_util';
|
||||
|
||||
(function() {
|
||||
if (isNode) return;
|
||||
describe('AnimationRenderer', () => {
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
element = el('<div></div>');
|
||||
if (isNode) return;
|
||||
describe('AnimationRenderer', () => {
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
element = el('<div></div>');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: MockAnimationEngine}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
function makeRenderer(animationTriggers: any[] = []) {
|
||||
const type = <RendererType2>{
|
||||
id: 'id',
|
||||
encapsulation: null !,
|
||||
styles: [],
|
||||
data: {'animation': animationTriggers}
|
||||
};
|
||||
return (TestBed.inject(RendererFactory2) as AnimationRendererFactory)
|
||||
.createRenderer(element, type);
|
||||
}
|
||||
|
||||
it('should hook into the engine\'s insert operations when appending children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.appendChild(container, element);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when inserting a child before another',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
const element2 = el('<div></div>');
|
||||
container.appendChild(element2);
|
||||
|
||||
renderer.insertBefore(container, element, element2);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when removing children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.removeChild(container, element);
|
||||
expect(engine.captures['onRemove'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s setProperty call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
renderer.setProperty(element, 'prop', 'value');
|
||||
expect(engine.captures['setProperty']).toBeFalsy();
|
||||
|
||||
renderer.setProperty(element, '@prop', 'value');
|
||||
expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/32794
|
||||
it('should support nested animation triggers', () => {
|
||||
makeRenderer([[trigger('myAnimation', [])]]);
|
||||
|
||||
const {triggers} = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
expect(triggers.length).toEqual(1);
|
||||
expect(triggers[0].name).toEqual('myAnimation');
|
||||
});
|
||||
|
||||
describe('listen', () => {
|
||||
it('should hook into the engine\'s listen call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen(element, 'event', cb);
|
||||
expect(engine.captures['listen']).toBeFalsy();
|
||||
|
||||
renderer.listen(element, '@event.phase', cb);
|
||||
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']);
|
||||
});
|
||||
|
||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen('body', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document.body);
|
||||
|
||||
renderer.listen('document', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document);
|
||||
|
||||
renderer.listen('window', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registering animations', () => {
|
||||
it('should only create a trigger definition once even if the registered multiple times');
|
||||
});
|
||||
|
||||
describe('flushing animations', () => {
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => state',
|
||||
[style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
event: any;
|
||||
onStart(event: any) { this.event = event; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'state';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||
expect(cmp.event.phaseName).toEqual('start');
|
||||
cmp.event = null;
|
||||
|
||||
engine.flush();
|
||||
expect(cmp.event).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly insert/remove nodes through the animation renderer that do not contain animations',
|
||||
(async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div #elm *ngIf="exp"></div>',
|
||||
animations: [trigger(
|
||||
'someAnimation',
|
||||
[transition(
|
||||
'* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
@ViewChild('elm') public element: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
cmp.exp = false;
|
||||
const element = cmp.element;
|
||||
expect(element.nativeElement.parentNode).toBeTruthy();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.parentNode).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only queue up dom removals if the element itself contains a valid leave animation',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div #elm1 *ngIf="exp1"></div>
|
||||
<div #elm2 @animation1 *ngIf="exp2"></div>
|
||||
<div #elm3 @animation2 *ngIf="exp3"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('animation1', [transition('a => b', [])]),
|
||||
trigger('animation2', [transition(':leave', [])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp1: any = true;
|
||||
exp2: any = true;
|
||||
exp3: any = true;
|
||||
|
||||
@ViewChild('elm1') public elm1: any;
|
||||
|
||||
@ViewChild('elm2') public elm2: any;
|
||||
|
||||
@ViewChild('elm3') public elm3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
const elm1 = cmp.elm1;
|
||||
const elm2 = cmp.elm2;
|
||||
const elm3 = cmp.elm3;
|
||||
assertHasParent(elm1);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
finishPlayers(engine.players);
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp2 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp3 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(1);
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: MockAnimationEngine}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnimationRendererFactory', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{
|
||||
provide: RendererFactory2,
|
||||
useClass: ExtendedAnimationRendererFactory,
|
||||
deps: [DomRendererFactory2, AnimationEngine, NgZone]
|
||||
}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
function makeRenderer(animationTriggers: any[] = []) {
|
||||
const type = <RendererType2>{
|
||||
id: 'id',
|
||||
encapsulation: null!,
|
||||
styles: [],
|
||||
data: {'animation': animationTriggers}
|
||||
};
|
||||
return (TestBed.inject(RendererFactory2) as AnimationRendererFactory)
|
||||
.createRenderer(element, type);
|
||||
}
|
||||
|
||||
it('should hook into the engine\'s insert operations when appending children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.appendChild(container, element);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when inserting a child before another',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
const element2 = el('<div></div>');
|
||||
container.appendChild(element2);
|
||||
|
||||
renderer.insertBefore(container, element, element2);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when removing children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.removeChild(container, element);
|
||||
expect(engine.captures['onRemove'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s setProperty call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
renderer.setProperty(element, 'prop', 'value');
|
||||
expect(engine.captures['setProperty']).toBeFalsy();
|
||||
|
||||
renderer.setProperty(element, '@prop', 'value');
|
||||
expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/32794
|
||||
it('should support nested animation triggers', () => {
|
||||
makeRenderer([[trigger('myAnimation', [])]]);
|
||||
|
||||
const {triggers} = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
expect(triggers.length).toEqual(1);
|
||||
expect(triggers[0].name).toEqual('myAnimation');
|
||||
});
|
||||
|
||||
describe('listen', () => {
|
||||
it('should hook into the engine\'s listen call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
renderer.listen(element, 'event', cb);
|
||||
expect(engine.captures['listen']).toBeFalsy();
|
||||
|
||||
renderer.listen(element, '@event.phase', cb);
|
||||
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']);
|
||||
});
|
||||
|
||||
it('should provide hooks at the start and end of change detection', () => {
|
||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
renderer.listen('body', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document.body);
|
||||
|
||||
renderer.listen('document', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document);
|
||||
|
||||
renderer.listen('window', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registering animations', () => {
|
||||
it('should only create a trigger definition once even if the registered multiple times');
|
||||
});
|
||||
|
||||
describe('flushing animations', () => {
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [])]
|
||||
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
exp: any;
|
||||
event: any;
|
||||
onStart(event: any) {
|
||||
this.event = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@ -305,19 +155,174 @@ import {el} from '../../testing/src/browser_util';
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const renderer = TestBed.inject(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
renderer.log = [];
|
||||
cmp.exp = 'state';
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
fixture.whenStable().then(() => {
|
||||
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||
expect(cmp.event.phaseName).toEqual('start');
|
||||
cmp.event = null;
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
engine.flush();
|
||||
expect(cmp.event).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly insert/remove nodes through the animation renderer that do not contain animations',
|
||||
(async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div #elm *ngIf="exp"></div>',
|
||||
animations: [trigger(
|
||||
'someAnimation',
|
||||
[transition(
|
||||
'* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
@ViewChild('elm') public element: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
cmp.exp = false;
|
||||
const element = cmp.element;
|
||||
expect(element.nativeElement.parentNode).toBeTruthy();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.parentNode).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only queue up dom removals if the element itself contains a valid leave animation',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div #elm1 *ngIf="exp1"></div>
|
||||
<div #elm2 @animation1 *ngIf="exp2"></div>
|
||||
<div #elm3 @animation2 *ngIf="exp3"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('animation1', [transition('a => b', [])]),
|
||||
trigger('animation2', [transition(':leave', [])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp1: any = true;
|
||||
exp2: any = true;
|
||||
exp3: any = true;
|
||||
|
||||
@ViewChild('elm1') public elm1: any;
|
||||
|
||||
@ViewChild('elm2') public elm2: any;
|
||||
|
||||
@ViewChild('elm3') public elm3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
const elm1 = cmp.elm1;
|
||||
const elm2 = cmp.elm2;
|
||||
const elm3 = cmp.elm3;
|
||||
assertHasParent(elm1);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
finishPlayers(engine.players);
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp2 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp3 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnimationRendererFactory', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{
|
||||
provide: RendererFactory2,
|
||||
useClass: ExtendedAnimationRendererFactory,
|
||||
deps: [DomRendererFactory2, AnimationEngine, NgZone]
|
||||
}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide hooks at the start and end of change detection', () => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [])]
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const renderer = TestBed.inject(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@Injectable()
|
||||
@ -336,7 +341,9 @@ class MockAnimationEngine extends InjectableAnimationEngine {
|
||||
this.triggers.push(metadata);
|
||||
}
|
||||
|
||||
onInsert(namespaceId: string, element: any): void { this._capture('onInsert', [element]); }
|
||||
onInsert(namespaceId: string, element: any): void {
|
||||
this._capture('onInsert', [element]);
|
||||
}
|
||||
|
||||
onRemove(namespaceId: string, element: any, domFn: () => any): void {
|
||||
this._capture('onRemove', [element]);
|
||||
|
@ -5,11 +5,11 @@
|
||||
* 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 {AnimationBuilder, animate, style} from '@angular/animations';
|
||||
import {animate, AnimationBuilder, style} from '@angular/animations';
|
||||
import {AnimationDriver} from '@angular/animations/browser';
|
||||
import {MockAnimationDriver} from '@angular/animations/browser/testing';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
|
||||
import {NoopAnimationsModule, ɵBrowserAnimationBuilder as BrowserAnimationBuilder} from '@angular/platform-browser/animations';
|
||||
|
||||
import {el} from '../../testing/src/browser_util';
|
||||
|
@ -13,7 +13,9 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
|
||||
{
|
||||
describe('NoopAnimationsModule', () => {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [NoopAnimationsModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [NoopAnimationsModule]});
|
||||
});
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
@ -29,8 +31,12 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
exp: any;
|
||||
startEvent: any;
|
||||
doneEvent: any;
|
||||
onStart(event: any) { this.startEvent = event; }
|
||||
onDone(event: any) { this.doneEvent = event; }
|
||||
onStart(event: any) {
|
||||
this.startEvent = event;
|
||||
}
|
||||
onDone(event: any) {
|
||||
this.doneEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
@ -63,8 +69,12 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
exp: any;
|
||||
startEvent: any;
|
||||
doneEvent: any;
|
||||
onStart(event: any) { this.startEvent = event; }
|
||||
onDone(event: any) { this.doneEvent = event; }
|
||||
onStart(event: any) {
|
||||
this.startEvent = event;
|
||||
}
|
||||
onDone(event: any) {
|
||||
this.doneEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
Reference in New Issue
Block a user