import { AsyncTestCompleter, beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, it, xit, beforeEachProviders } from '@angular/core/testing/testing_internal'; import {parseAnimationEntry} from '../../src/animation/animation_parser'; import {CompileMetadataResolver} from '../../src/metadata_resolver'; import { style, animate, group, sequence, trigger, keyframes, transition, state, AnimationMetadata, AnimationWithStepsMetadata, AnimationStyleMetadata, AnimationAnimateMetadata, AnimationGroupMetadata, AnimationSequenceMetadata } from '@angular/core'; import { AnimationAst, AnimationStateTransitionAst, AnimationEntryAst, AnimationKeyframeAst, AnimationStylesAst, AnimationSequenceAst, AnimationGroupAst, AnimationStepAst } from '../../src/animation/animation_ast'; import {FILL_STYLE_FLAG, AnimationStyleUtil} from '../../core_private'; import {StringMapWrapper} from '../../src/facade/collection'; export function main() { describe('parseAnimationEntry', () => { var combineStyles = (styles: AnimationStylesAst): {[key: string]: string | number} => { var flatStyles: {[key: string]: string | number} = {}; styles.styles.forEach(entry => StringMapWrapper.forEach(entry, (val, prop) => { flatStyles[prop] = val; })); return flatStyles; }; var collectKeyframeStyles = (keyframe: AnimationKeyframeAst): {[key: string]: string | number} => { return combineStyles(keyframe.styles); }; var collectStepStyles = (step: AnimationStepAst): Array<{[key: string]: string | number}> => { var keyframes = step.keyframes; var styles = []; if (step.startingStyles.styles.length > 0) { styles.push(combineStyles(step.startingStyles)); } keyframes.forEach(keyframe => styles.push(collectKeyframeStyles(keyframe))); return styles; }; var resolver; beforeEach(inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; })); var parseAnimation = (data: AnimationMetadata[]) => { var entry = trigger('myAnimation', [ transition('state1 => state2', sequence(data)) ]); var compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry); return parseAnimationEntry(compiledAnimationEntry); }; var getAnimationAstFromEntryAst = (ast: AnimationEntryAst) => { return ast.stateTransitions[0].animation; } var parseAnimationAst = (data: AnimationMetadata[]) => { return getAnimationAstFromEntryAst(parseAnimation(data).ast); }; var parseAnimationAndGetErrors = (data: AnimationMetadata[]) => parseAnimation(data).errors; it('should merge repeated style steps into a single style ast step entry', () => { var ast = parseAnimationAst([ style({"color": 'black'}), style({"background": 'red'}), style({"opacity": 0}), animate(1000, style({"color": 'white', "background": 'black', "opacity": 1})) ]); expect(ast.steps.length).toEqual(1); var step = ast.steps[0]; expect(step.startingStyles.styles[0]) .toEqual({"color": 'black', "background": 'red', "opacity": 0}); expect(step.keyframes[0].styles.styles[0]) .toEqual({"color": 'black', "background": 'red', "opacity": 0}); expect(step.keyframes[1].styles.styles[0]) .toEqual({"color": 'white', "background": 'black', "opacity": 1}); }); it('should animate only the styles requested within an animation step', () => { var ast = parseAnimationAst([ style({"color": 'black', "background": 'blue'}), animate(1000, style({"background": 'orange'})) ]); expect(ast.steps.length).toEqual(1); var animateStep = ast.steps[0]; var fromKeyframe = animateStep.keyframes[0].styles.styles[0]; var toKeyframe = animateStep.keyframes[1].styles.styles[0]; expect(fromKeyframe).toEqual({"background": 'blue'}); expect(toKeyframe).toEqual({"background": 'orange'}); }); it('should populate the starting and duration times propertly', () => { var ast = parseAnimationAst([ style({"color": 'black', "opacity": 1}), animate(1000, style({"color": 'red'})), animate(4000, style({"color": 'yellow'})), sequence([animate(1000, style({"color": 'blue'})), animate(1000, style({"color": 'grey'}))]), group([animate(500, style({"color": 'pink'})), animate(1000, style({"opacity": '0.5'}))]), animate(300, style({"color": 'black'})), ]); expect(ast.steps.length).toEqual(5); var step1 = ast.steps[0]; expect(step1.playTime).toEqual(1000); expect(step1.startTime).toEqual(0); var step2 = ast.steps[1]; expect(step2.playTime).toEqual(4000); expect(step2.startTime).toEqual(1000); var seq = ast.steps[2]; expect(seq.playTime).toEqual(2000); expect(seq.startTime).toEqual(5000); var step4 = seq.steps[0]; expect(step4.playTime).toEqual(1000); expect(step4.startTime).toEqual(5000); var step5 = seq.steps[1]; expect(step5.playTime).toEqual(1000); expect(step5.startTime).toEqual(6000); var grp = ast.steps[3]; expect(grp.playTime).toEqual(1000); expect(grp.startTime).toEqual(7000); var step6 = grp.steps[0]; expect(step6.playTime).toEqual(500); expect(step6.startTime).toEqual(7000); var step7 = grp.steps[1]; expect(step7.playTime).toEqual(1000); expect(step7.startTime).toEqual(7000); var step8 = ast.steps[4]; expect(step8.playTime).toEqual(300); expect(step8.startTime).toEqual(8000); }); it('should apply the correct animate() styles when parallel animations are active and use the same properties', () => { var details = parseAnimation([ style({"opacity": 0, "color": 'red'}), group([ sequence([ animate(2000, style({"color": "black"})), animate(2000, style({"opacity": 0.5})), ]), sequence([animate(2000, style({"opacity": 0.8})), animate(2000, style({"color": "blue"}))]) ]) ]); var errors = details.errors; expect(errors.length).toEqual(0); var ast = getAnimationAstFromEntryAst(details.ast); var g1 = ast.steps[1]; var sq1 = g1.steps[0]; var sq2 = g1.steps[1]; var sq1a1 = sq1.steps[0]; expect(collectStepStyles(sq1a1)).toEqual([{"color": 'red'}, {"color": 'black'}]); var sq1a2 = sq1.steps[1]; expect(collectStepStyles(sq1a2)).toEqual([{"opacity": 0.8}, {"opacity": 0.5}]); var sq2a1 = sq2.steps[0]; expect(collectStepStyles(sq2a1)).toEqual([{"opacity": 0}, {"opacity": 0.8}]); var sq2a2 = sq2.steps[1]; expect(collectStepStyles(sq2a2)).toEqual([{"color": "black"}, {"color": "blue"}]); }); it('should throw errors when animations animate a CSS property at the same time', () => { var animation1 = parseAnimation([ style({"opacity": 0}), group([animate(1000, style({"opacity": 1})), animate(2000, style({"opacity": 0.5}))]) ]); var errors1 = animation1.errors; expect(errors1.length).toEqual(1); expect(errors1[0].msg) .toContainError( 'The animated CSS property "opacity" unexpectedly changes between steps "0ms" and "2000ms" at "1000ms"'); var animation2 = parseAnimation([ style({"color": "red"}), group([ animate(5000, style({"color": "blue"})), animate(2500, style({"color": "black"})) ]) ]); var errors2 = animation2.errors; expect(errors2.length).toEqual(1); expect(errors2[0].msg) .toContainError( 'The animated CSS property "color" unexpectedly changes between steps "0ms" and "5000ms" at "2500ms"'); }); it('should return an error when an animation style contains an invalid timing value', () => { var errors = parseAnimationAndGetErrors( [style({"opacity": 0}), animate('one second', style({"opacity": 1}))]); expect(errors[0].msg).toContainError(`The provided timing value "one second" is invalid.`); }); it('should collect and return any errors collected when parsing the metadata', () => { var errors = parseAnimationAndGetErrors([ style({"opacity": 0}), animate('one second', style({"opacity": 1})), style({"opacity": 0}), animate('one second', null), style({"background": 'red'}) ]); expect(errors.length).toBeGreaterThan(1); }); it('should normalize a series of keyframe styles into a list of offset steps', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"width": 0}), style({"width": 25}), style({"width": 50}), style({"width": 75}) ])) ]); var step = ast.steps[0]; expect(step.keyframes.length).toEqual(4); expect(step.keyframes[0].offset).toEqual(0); expect(step.keyframes[1].offset).toMatchPattern(/^0\.33/); expect(step.keyframes[2].offset).toMatchPattern(/^0\.66/); expect(step.keyframes[3].offset).toEqual(1); }); it('should use an existing collection of offset steps if provided', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"height": 0, "offset": 0}), style({"height": 25, "offset": 0.6}), style({"height": 50, "offset": 0.7}), style({"height": 75, "offset": 1}) ])) ]); var step = ast.steps[0]; expect(step.keyframes.length).toEqual(4); expect(step.keyframes[0].offset).toEqual(0); expect(step.keyframes[1].offset).toEqual(0.6); expect(step.keyframes[2].offset).toEqual(0.7); expect(step.keyframes[3].offset).toEqual(1); }); it('should sort the provided collection of steps that contain offsets', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"opacity": 0, "offset": 0.9}), style({"opacity": .25, "offset": 0}), style({"opacity": .50, "offset": 1}), style({"opacity": .75, "offset": 0.91}) ])) ]); var step = ast.steps[0]; expect(step.keyframes.length).toEqual(4); expect(step.keyframes[0].offset).toEqual(0); expect(step.keyframes[0].styles.styles[0]['opacity']).toEqual(.25); expect(step.keyframes[1].offset).toEqual(0.9); expect(step.keyframes[1].styles.styles[0]['opacity']).toEqual(0); expect(step.keyframes[2].offset).toEqual(0.91); expect(step.keyframes[2].styles.styles[0]['opacity']).toEqual(.75); expect(step.keyframes[3].offset).toEqual(1); expect(step.keyframes[3].styles.styles[0]['opacity']).toEqual(.50); }); it('should throw an error if a partial amount of keyframes contain an offset', () => { var errors = parseAnimationAndGetErrors([ animate(1000, keyframes([ style({"z-index": 0, "offset": 0}), style({"z-index": 1}), style({"z-index": 2, "offset": 1}) ])) ]); expect(errors.length).toEqual(1); var error = errors[0]; expect(error.msg).toMatchPattern(/Not all style\(\) entries contain an offset/); }); it('should use an existing style used earlier in the animation sequence if not defined in the first keyframe', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"color": "red"}), style({"background": "blue", "color": "white"}) ])) ]); var keyframesStep = ast.steps[0]; var kf1 = keyframesStep.keyframes[0]; var kf2 = keyframesStep.keyframes[1]; expect(AnimationStyleUtil.flattenStyles(kf1.styles.styles)).toEqual({ "color": "red", "background": FILL_STYLE_FLAG }); }); it('should copy over any missing styles to the final keyframe if not already defined', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"color": "white", "border-color":"white"}), style({"color": "red", "background": "blue"}), style({"background": "blue" }) ])) ]); var keyframesStep = ast.steps[0]; var kf1 = keyframesStep.keyframes[0]; var kf2 = keyframesStep.keyframes[1]; var kf3 = keyframesStep.keyframes[2]; expect(AnimationStyleUtil.flattenStyles(kf3.styles.styles)).toEqual({ "background": "blue", "color": "red", "border-color": "white" }); }); it('should create an initial keyframe if not detected and place all keyframes styles there', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"color": "white", "background": "black", "offset": 0.5}), style({"color": "orange", "background": "red", "font-size": "100px", "offset": 1}) ])) ]); var keyframesStep = ast.steps[0]; expect(keyframesStep.keyframes.length).toEqual(3); var kf1 = keyframesStep.keyframes[0]; var kf2 = keyframesStep.keyframes[1]; var kf3 = keyframesStep.keyframes[2]; expect(kf1.offset).toEqual(0); expect(AnimationStyleUtil.flattenStyles(kf1.styles.styles)).toEqual({ "font-size": FILL_STYLE_FLAG, "background": FILL_STYLE_FLAG, "color": FILL_STYLE_FLAG }); }); it('should create an destination keyframe if not detected and place all keyframes styles there', () => { var ast = parseAnimationAst([ animate(1000, keyframes([ style({"color": "white", "background": "black", "transform": "rotate(360deg)", "offset": 0}), style({"color": "orange", "background": "red", "font-size": "100px", "offset": 0.5}) ])) ]); var keyframesStep = ast.steps[0]; expect(keyframesStep.keyframes.length).toEqual(3); var kf1 = keyframesStep.keyframes[0]; var kf2 = keyframesStep.keyframes[1]; var kf3 = keyframesStep.keyframes[2]; expect(kf3.offset).toEqual(1); expect(AnimationStyleUtil.flattenStyles(kf3.styles.styles)).toEqual({ "color": "orange", "background": "red", "transform": "rotate(360deg)", "font-size": "100px" }); }); describe('easing / duration / delay', () => { it('should parse simple string-based values', () => { var ast = parseAnimationAst([ animate("1s .5s ease-out", style({ "opacity": 1 })) ]); var step = ast.steps[0]; expect(step.duration).toEqual(1000); expect(step.delay).toEqual(500); expect(step.easing).toEqual("ease-out"); }); it('should parse a numeric duration value', () => { var ast = parseAnimationAst([ animate(666, style({ "opacity": 1 })) ]); var step = ast.steps[0]; expect(step.duration).toEqual(666); expect(step.delay).toEqual(0); expect(step.easing).toBeFalsy(); }); it('should parse an easing value without a delay', () => { var ast = parseAnimationAst([ animate("5s linear", style({ "opacity": 1 })) ]); var step = ast.steps[0]; expect(step.duration).toEqual(5000); expect(step.delay).toEqual(0); expect(step.easing).toEqual("linear"); }); it('should parse a complex easing value', () => { var ast = parseAnimationAst([ animate("30ms cubic-bezier(0, 0,0, .69)", style({ "opacity": 1 })) ]); var step = ast.steps[0]; expect(step.duration).toEqual(30); expect(step.delay).toEqual(0); expect(step.easing).toEqual("cubic-bezier(0, 0,0, .69)"); }); }); }); }