From 94da80148ef324ee00c7b734448f6d6c4d634e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 20 Mar 2017 10:35:02 -0700 Subject: [PATCH] fix(animations): stringify boolean values as `1` and `0` (#15311) Closes #15247 Closes #15311 PR Close #15311 --- .../src/render/dom_animation_engine.ts | 19 +++++++-- .../animation/animation_integration_spec.ts | 40 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/packages/animations/browser/src/render/dom_animation_engine.ts b/packages/animations/browser/src/render/dom_animation_engine.ts index 5756c08384..870a60855f 100644 --- a/packages/animations/browser/src/render/dom_animation_engine.ts +++ b/packages/animations/browser/src/render/dom_animation_engine.ts @@ -31,6 +31,7 @@ export interface TriggerListenerTuple { const MARKED_FOR_ANIMATION_CLASSNAME = 'ng-animating'; const MARKED_FOR_ANIMATION_SELECTOR = '.ng-animating'; const MARKED_FOR_REMOVAL = '$$ngRemove'; +const VOID_STATE = 'void'; export class DomAnimationEngine { private _flaggedInserts = new Set(); @@ -84,7 +85,7 @@ export class DomAnimationEngine { const possibleTriggers = Object.keys(lookupRef); const hasRemoval = possibleTriggers.some(triggerName => { const oldValue = lookupRef[triggerName]; - const instruction = this._triggers[triggerName].matchTransition(oldValue, 'void'); + const instruction = this._triggers[triggerName].matchTransition(oldValue, VOID_STATE); return !!instruction; }); if (hasRemoval) { @@ -115,8 +116,9 @@ export class DomAnimationEngine { this._elementTriggerStates.set(element, lookupRef = {}); } - let oldValue = lookupRef.hasOwnProperty(property) ? lookupRef[property] : 'void'; + let oldValue = lookupRef.hasOwnProperty(property) ? lookupRef[property] : VOID_STATE; if (oldValue !== value) { + value = normalizeTriggerValue(value); let instruction = trigger.matchTransition(oldValue, value); if (!instruction) { // we do this to make sure we always have an animation player so @@ -407,11 +409,11 @@ export class DomAnimationEngine { Object.keys(stateDetails).forEach(triggerName => { flushAgain = true; const oldValue = stateDetails[triggerName]; - const instruction = this._triggers[triggerName].matchTransition(oldValue, 'void'); + const instruction = this._triggers[triggerName].matchTransition(oldValue, VOID_STATE); if (instruction) { players.push(this.animateTransition(element, instruction)); } else { - const event = makeAnimationEvent(element, triggerName, oldValue, 'void', '', 0); + const event = makeAnimationEvent(element, triggerName, oldValue, VOID_STATE, '', 0); const player = new NoopAnimationPlayer(); this._queuePlayer(element, triggerName, player, event); } @@ -515,3 +517,12 @@ function makeAnimationEvent( totalTime: number): AnimationEvent { return {element, triggerName, fromState, toState, phaseName, totalTime}; } + +function normalizeTriggerValue(value: any): string { + switch (typeof value) { + case 'boolean': + return value ? '1' : '0'; + default: + return value ? value.toString() : null; + } +} diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 05cf9f74c7..5aad80a08a 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -147,6 +147,46 @@ export function main() { ]); }); + it('should stringify boolean triggers to `1` and `0`', () => { + @Component({ + selector: 'if-cmp', + template: ` +
+ `, + animations: [trigger( + 'myAnimation', + [ + transition('void => 1', [style({opacity: 0}), animate(1000, style({opacity: 1}))]), + transition('1 => 0', [style({opacity: 1}), animate(1000, style({opacity: 0}))]) + ])] + }) + class Cmp { + exp: any = false; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + + cmp.exp = true; + fixture.detectChanges(); + engine.flush(); + + expect(getLog().pop().keyframes).toEqual([ + {offset: 0, opacity: '0'}, {offset: 1, opacity: '1'} + ]); + + cmp.exp = false; + fixture.detectChanges(); + engine.flush(); + + expect(getLog().pop().keyframes).toEqual([ + {offset: 0, opacity: '1'}, {offset: 1, opacity: '0'} + ]); + }); + it('should not throw an error if a trigger with the same name exists in separate components', () => { @Component({selector: 'cmp1', template: '...', animations: [trigger('trig', [])]})