From a0e9fde653cd63a4abbf37e62de7cdec59d061d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 8 Nov 2016 15:45:30 -0800 Subject: [PATCH] fix(animations): always normalize style properties and values during compilation (#12755) Closes #11582 Closes #12481 Closes #12755 --- modules/@angular/compiler-cli/src/codegen.ts | 5 +- modules/@angular/compiler/index.ts | 1 + .../src/animation/animation_parser.ts | 74 +++-- modules/@angular/compiler/src/compiler.ts | 4 +- .../@angular/compiler/src/offline_compiler.ts | 4 +- .../@angular/compiler/src/runtime_compiler.ts | 3 +- .../src/schema/dom_element_schema_registry.ts | 64 +++- .../src/schema/element_schema_registry.ts | 4 + modules/@angular/compiler/src/util.ts | 5 + .../test/animation/animation_compiler_spec.ts | 11 +- .../test/animation/animation_parser_spec.ts | 109 +++---- .../dom_element_schema_registry_spec.ts | 39 +++ .../compiler/testing/schema_registry_mock.ts | 6 + .../animation/animation_integration_spec.ts | 274 +++++++++++++----- .../src/dom/web_animations_driver.ts | 76 +---- .../test/dom/web_animations_driver_spec.ts | 40 --- 16 files changed, 448 insertions(+), 271 deletions(-) diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 6b0a02798a..d04a3a3c98 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -138,7 +138,8 @@ export class CodeGenerator { new compiler.DirectiveWrapperCompiler( config, expressionParser, elementSchemaRegistry, console), new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), - cliOptions.locale, cliOptions.i18nFormat); + cliOptions.locale, cliOptions.i18nFormat, + new compiler.AnimationParser(elementSchemaRegistry)); return new CodeGenerator( options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost); @@ -181,4 +182,4 @@ export function extractProgramSymbols( }); return staticSymbols; -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/index.ts b/modules/@angular/compiler/index.ts index b26f6800a6..7c362dbebe 100644 --- a/modules/@angular/compiler/index.ts +++ b/modules/@angular/compiler/index.ts @@ -52,5 +52,6 @@ export * from './src/selector'; export * from './src/style_compiler'; export * from './src/template_parser/template_parser'; export {ViewCompiler} from './src/view_compiler/view_compiler'; +export {AnimationParser} from './src/animation/animation_parser'; // This file only reexports content of the `src` folder. Keep it that way. diff --git a/modules/@angular/compiler/src/animation/animation_parser.ts b/modules/@angular/compiler/src/animation/animation_parser.ts index a28edf4068..35bf2eee6e 100644 --- a/modules/@angular/compiler/src/animation/animation_parser.ts +++ b/modules/@angular/compiler/src/animation/animation_parser.ts @@ -6,11 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ +import {Injectable} from '@angular/core'; + import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata'; import {StringMapWrapper} from '../facade/collection'; import {isBlank, isPresent} from '../facade/lang'; import {ParseError} from '../parse_util'; import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core'; +import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast'; import {StylesCollection} from './styles_collection'; @@ -32,7 +35,10 @@ export class AnimationEntryParseResult { constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {} } +@Injectable() export class AnimationParser { + constructor(private _schema: ElementSchemaRegistry) {} + parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] { const errors: string[] = []; const componentName = component.type.name; @@ -73,7 +79,7 @@ export class AnimationParser { var stateDeclarationAsts: AnimationStateDeclarationAst[] = []; entry.definitions.forEach(def => { if (def instanceof CompileAnimationStateDeclarationMetadata) { - _parseAnimationDeclarationStates(def, errors).forEach(ast => { + _parseAnimationDeclarationStates(def, this._schema, errors).forEach(ast => { stateDeclarationAsts.push(ast); stateStyles[ast.stateName] = ast.styles; }); @@ -82,8 +88,8 @@ export class AnimationParser { } }); - var stateTransitionAsts = - transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors)); + var stateTransitionAsts = transitions.map( + transDef => _parseAnimationStateTransition(transDef, stateStyles, this._schema, errors)); var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts); return new AnimationEntryParseResult(ast, errors); @@ -91,27 +97,17 @@ export class AnimationParser { } function _parseAnimationDeclarationStates( - stateMetadata: CompileAnimationStateDeclarationMetadata, + stateMetadata: CompileAnimationStateDeclarationMetadata, schema: ElementSchemaRegistry, errors: AnimationParseError[]): AnimationStateDeclarationAst[] { - var styleValues: Styles[] = []; - stateMetadata.styles.styles.forEach(stylesEntry => { - // TODO (matsko): change this when we get CSS class integration support - if (typeof stylesEntry === 'object' && stylesEntry !== null) { - styleValues.push(stylesEntry as Styles); - } else { - errors.push(new AnimationParseError( - `State based animations cannot contain references to other states`)); - } - }); - var defStyles = new AnimationStylesAst(styleValues); - + var normalizedStyles = _normalizeStyleMetadata(stateMetadata.styles, {}, schema, errors, false); + var defStyles = new AnimationStylesAst(normalizedStyles); var states = stateMetadata.stateNameExpr.split(/\s*,\s*/); return states.map(state => new AnimationStateDeclarationAst(state, defStyles)); } function _parseAnimationStateTransition( transitionStateMetadata: CompileAnimationStateTransitionMetadata, - stateStyles: {[key: string]: AnimationStylesAst}, + stateStyles: {[key: string]: AnimationStylesAst}, schema: ElementSchemaRegistry, errors: AnimationParseError[]): AnimationStateTransitionAst { var styles = new StylesCollection(); var transitionExprs: AnimationStateTransitionExpression[] = []; @@ -119,7 +115,7 @@ function _parseAnimationStateTransition( transitionStates.forEach( expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); }); var entry = _normalizeAnimationEntry(transitionStateMetadata.steps); - var animation = _normalizeStyleSteps(entry, stateStyles, errors); + var animation = _normalizeStyleSteps(entry, stateStyles, schema, errors); var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors); if (errors.length == 0) { _fillAnimationAstStartingKeyframes(animationAst, styles, errors); @@ -176,13 +172,31 @@ function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnima function _normalizeStyleMetadata( entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst}, - errors: AnimationParseError[]): {[key: string]: string | number}[] { + schema: ElementSchemaRegistry, errors: AnimationParseError[], + permitStateReferences: boolean): {[key: string]: string | number}[] { var normalizedStyles: {[key: string]: string | number}[] = []; entry.styles.forEach(styleEntry => { if (typeof styleEntry === 'string') { - normalizedStyles.push(..._resolveStylesFromState(styleEntry, stateStyles, errors)); + if (permitStateReferences) { + normalizedStyles.push(..._resolveStylesFromState(styleEntry, stateStyles, errors)); + } else { + errors.push(new AnimationParseError( + `State based animations cannot contain references to other states`)); + } } else { - normalizedStyles.push(<{[key: string]: string | number}>styleEntry); + var stylesObj = styleEntry; + var normalizedStylesObj: Styles = {}; + Object.keys(stylesObj).forEach(propName => { + var normalizedProp = schema.normalizeAnimationStyleProperty(propName); + var normalizedOutput = + schema.normalizeAnimationStyleValue(normalizedProp, propName, stylesObj[propName]); + var normalizationError = normalizedOutput['error']; + if (normalizationError) { + errors.push(new AnimationParseError(normalizationError)); + } + normalizedStylesObj[normalizedProp] = normalizedOutput['value']; + }); + normalizedStyles.push(normalizedStylesObj); } }); return normalizedStyles; @@ -190,8 +204,8 @@ function _normalizeStyleMetadata( function _normalizeStyleSteps( entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst}, - errors: AnimationParseError[]): CompileAnimationMetadata { - var steps = _normalizeStyleStepEntry(entry, stateStyles, errors); + schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata { + var steps = _normalizeStyleStepEntry(entry, stateStyles, schema, errors); return (entry instanceof CompileAnimationGroupMetadata) ? new CompileAnimationGroupMetadata(steps) : new CompileAnimationSequenceMetadata(steps); @@ -213,7 +227,7 @@ function _mergeAnimationStyles( function _normalizeStyleStepEntry( entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst}, - errors: AnimationParseError[]): CompileAnimationMetadata[] { + schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata[] { var steps: CompileAnimationMetadata[]; if (entry instanceof CompileAnimationWithStepsMetadata) { steps = entry.steps; @@ -232,7 +246,8 @@ function _normalizeStyleStepEntry( if (!isPresent(combinedStyles)) { combinedStyles = []; } - _normalizeStyleMetadata(step, stateStyles, errors) + _normalizeStyleMetadata( + step, stateStyles, schema, errors, true) .forEach(entry => { _mergeAnimationStyles(combinedStyles, entry); }); } else { // it is important that we create a metadata entry of the combined styles @@ -250,13 +265,14 @@ function _normalizeStyleStepEntry( var animateStyleValue = (step).styles; if (animateStyleValue instanceof CompileAnimationStyleMetadata) { animateStyleValue.styles = - _normalizeStyleMetadata(animateStyleValue, stateStyles, errors); + _normalizeStyleMetadata(animateStyleValue, stateStyles, schema, errors, true); } else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) { - animateStyleValue.steps.forEach( - step => { step.styles = _normalizeStyleMetadata(step, stateStyles, errors); }); + animateStyleValue.steps.forEach(step => { + step.styles = _normalizeStyleMetadata(step, stateStyles, schema, errors, true); + }); } } else if (step instanceof CompileAnimationWithStepsMetadata) { - let innerSteps = _normalizeStyleStepEntry(step, stateStyles, errors); + let innerSteps = _normalizeStyleStepEntry(step, stateStyles, schema, errors); step = step instanceof CompileAnimationGroupMetadata ? new CompileAnimationGroupMetadata(innerSteps) : new CompileAnimationSequenceMetadata(innerSteps); diff --git a/modules/@angular/compiler/src/compiler.ts b/modules/@angular/compiler/src/compiler.ts index 268a62bbfb..3a82d5923b 100644 --- a/modules/@angular/compiler/src/compiler.ts +++ b/modules/@angular/compiler/src/compiler.ts @@ -8,6 +8,7 @@ import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; +import {AnimationParser} from './animation/animation_parser'; import {CompilerConfig} from './config'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveResolver} from './directive_resolver'; @@ -74,7 +75,8 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = UrlResolver, DirectiveResolver, PipeResolver, - NgModuleResolver + NgModuleResolver, + AnimationParser ]; diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 8403c8ee0f..07c9a846fe 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -109,7 +109,6 @@ export function analyzeNgModules( } export class OfflineCompiler { - private _animationParser = new AnimationParser(); private _animationCompiler = new AnimationCompiler(); constructor( @@ -118,7 +117,8 @@ export class OfflineCompiler { private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, - private _localeId: string, private _translationFormat: string) {} + private _localeId: string, private _translationFormat: string, + private _animationParser: AnimationParser) {} clearCache() { this._directiveNormalizer.clearCache(); diff --git a/modules/@angular/compiler/src/runtime_compiler.ts b/modules/@angular/compiler/src/runtime_compiler.ts index 951820a943..14fafc9ae7 100644 --- a/modules/@angular/compiler/src/runtime_compiler.ts +++ b/modules/@angular/compiler/src/runtime_compiler.ts @@ -43,7 +43,6 @@ export class RuntimeCompiler implements Compiler { private _compiledHostTemplateCache = new Map, CompiledTemplate>(); private _compiledDirectiveWrapperCache = new Map, Type>(); private _compiledNgModuleCache = new Map, NgModuleFactory>(); - private _animationParser = new AnimationParser(); private _animationCompiler = new AnimationCompiler(); constructor( @@ -52,7 +51,7 @@ export class RuntimeCompiler implements Compiler { private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler, private _directiveWrapperCompiler: DirectiveWrapperCompiler, - private _compilerConfig: CompilerConfig) {} + private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {} get injector(): Injector { return this._injector; } diff --git a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts index 64059d7d92..9c18b62667 100644 --- a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts +++ b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; +import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; + +import {dashCaseToCamelCase} from '../util'; import {SECURITY_SCHEMA} from './dom_security_schema'; import {ElementSchemaRegistry} from './element_schema_registry'; @@ -373,4 +375,64 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { } allKnownElementNames(): string[] { return Object.keys(this._schema); } + + normalizeAnimationStyleProperty(propName: string): string { + return dashCaseToCamelCase(propName); + } + + normalizeAnimationStyleValue(camelCaseProp: string, userProvidedProp: string, val: string|number): + {error: string, value: string} { + var unit: string = ''; + var strVal = val.toString().trim(); + var errorMsg: string = null; + + if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') { + if (typeof val === 'number') { + unit = 'px'; + } else { + let valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/); + if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) { + errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`; + } + } + } + return {error: errorMsg, value: strVal + unit}; + } +} + +function _isPixelDimensionStyle(prop: string): boolean { + switch (prop) { + case 'width': + case 'height': + case 'minWidth': + case 'minHeight': + case 'maxWidth': + case 'maxHeight': + case 'left': + case 'top': + case 'bottom': + case 'right': + case 'fontSize': + case 'outlineWidth': + case 'outlineOffset': + case 'paddingTop': + case 'paddingLeft': + case 'paddingBottom': + case 'paddingRight': + case 'marginTop': + case 'marginLeft': + case 'marginBottom': + case 'marginRight': + case 'borderRadius': + case 'borderWidth': + case 'borderTopWidth': + case 'borderLeftWidth': + case 'borderRightWidth': + case 'borderBottomWidth': + case 'textIndent': + return true; + + default: + return false; + } } diff --git a/modules/@angular/compiler/src/schema/element_schema_registry.ts b/modules/@angular/compiler/src/schema/element_schema_registry.ts index d76eaa6991..3b8727b0b7 100644 --- a/modules/@angular/compiler/src/schema/element_schema_registry.ts +++ b/modules/@angular/compiler/src/schema/element_schema_registry.ts @@ -18,4 +18,8 @@ export abstract class ElementSchemaRegistry { abstract getDefaultComponentElementName(): string; abstract validateProperty(name: string): {error: boolean, msg?: string}; abstract validateAttribute(name: string): {error: boolean, msg?: string}; + abstract normalizeAnimationStyleProperty(propName: string): string; + abstract normalizeAnimationStyleValue( + camelCaseProp: string, userProvidedProp: string, + val: string|number): {error: string, value: string}; } diff --git a/modules/@angular/compiler/src/util.ts b/modules/@angular/compiler/src/util.ts index 901bb383a3..cc3d63eb62 100644 --- a/modules/@angular/compiler/src/util.ts +++ b/modules/@angular/compiler/src/util.ts @@ -11,11 +11,16 @@ import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang'; export const MODULE_SUFFIX = ''; const CAMEL_CASE_REGEXP = /([A-Z])/g; +const DASH_CASE_REGEXP = /-+([a-z0-9])/g; export function camelCaseToDashCase(input: string): string { return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase()); } +export function dashCaseToCamelCase(input: string): string { + return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase()); +} + export function splitAtColon(input: string, defaultValues: string[]): string[] { return _splitAt(input, ':', defaultValues); } diff --git a/modules/@angular/compiler/test/animation/animation_compiler_spec.ts b/modules/@angular/compiler/test/animation/animation_compiler_spec.ts index 8fe0d326d6..4172d9917e 100644 --- a/modules/@angular/compiler/test/animation/animation_compiler_spec.ts +++ b/modules/@angular/compiler/test/animation/animation_compiler_spec.ts @@ -12,14 +12,19 @@ import {AnimationCompiler, AnimationEntryCompileResult} from '../../src/animatio import {AnimationParser} from '../../src/animation/animation_parser'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata'; import {CompileMetadataResolver} from '../../src/metadata_resolver'; +import {ElementSchemaRegistry} from '../../src/schema/element_schema_registry'; export function main() { describe('RuntimeAnimationCompiler', () => { var resolver: CompileMetadataResolver; - beforeEach( - inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; })); + var parser: AnimationParser; + beforeEach(inject( + [CompileMetadataResolver, ElementSchemaRegistry], + (res: CompileMetadataResolver, schema: ElementSchemaRegistry) => { + resolver = res; + parser = new AnimationParser(schema); + })); - const parser = new AnimationParser(); const compiler = new AnimationCompiler(); var compileAnimations = diff --git a/modules/@angular/compiler/test/animation/animation_parser_spec.ts b/modules/@angular/compiler/test/animation/animation_parser_spec.ts index 69dcd54777..9042c5ea88 100644 --- a/modules/@angular/compiler/test/animation/animation_parser_spec.ts +++ b/modules/@angular/compiler/test/animation/animation_parser_spec.ts @@ -13,6 +13,7 @@ import {expect} from '@angular/platform-browser/testing/matchers'; import {AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStepAst, AnimationStylesAst} from '../../src/animation/animation_ast'; import {AnimationParser} from '../../src/animation/animation_parser'; import {CompileMetadataResolver} from '../../src/metadata_resolver'; +import {ElementSchemaRegistry} from '../../src/schema/element_schema_registry'; import {FILL_STYLE_FLAG, flattenStyles} from '../private_import_core'; export function main() { @@ -39,13 +40,18 @@ export function main() { }; var resolver: CompileMetadataResolver; - beforeEach( - inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; })); + var schema: ElementSchemaRegistry; + beforeEach(inject( + [CompileMetadataResolver, ElementSchemaRegistry], + (res: CompileMetadataResolver, sch: ElementSchemaRegistry) => { + resolver = res; + schema = sch; + })); var parseAnimation = (data: AnimationMetadata[]) => { const entry = trigger('myAnimation', [transition('state1 => state2', sequence(data))]); const compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry); - const parser = new AnimationParser(); + const parser = new AnimationParser(schema); return parser.parseEntry(compiledAnimationEntry); }; @@ -59,21 +65,21 @@ export function main() { 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})) + 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}); + .toEqual({'color': 'black', 'background': 'red', 'opacity': '0'}); expect(step.keyframes[0].styles.styles[0]) - .toEqual({'color': 'black', 'background': 'red', 'opacity': 0}); + .toEqual({'color': 'black', 'background': 'red', 'opacity': '0'}); expect(step.keyframes[1].styles.styles[0]) - .toEqual({'color': 'white', 'background': 'black', 'opacity': 1}); + .toEqual({'color': 'white', 'background': 'black', 'opacity': '1'}); }); it('should animate only the styles requested within an animation step', () => { @@ -93,7 +99,7 @@ export function main() { it('should populate the starting and duration times propertly', () => { var ast = parseAnimationAst([ - style({'color': 'black', 'opacity': 1}), + style({'color': 'black', 'opacity': '1'}), animate(1000, style({'color': 'red'})), animate(4000, style({'color': 'yellow'})), sequence( @@ -144,13 +150,13 @@ export function main() { 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([ + style({'opacity': '0', 'color': 'red'}), group([ sequence([ animate(2000, style({'color': 'black'})), - animate(2000, style({'opacity': 0.5})), + animate(2000, style({'opacity': '0.5'})), ]), sequence([ - animate(2000, style({'opacity': 0.8})), + animate(2000, style({'opacity': '0.8'})), animate(2000, style({'color': 'blue'})) ]) ]) @@ -169,10 +175,10 @@ export function main() { expect(collectStepStyles(sq1a1)).toEqual([{'color': 'red'}, {'color': 'black'}]); var sq1a2 = sq1.steps[1]; - expect(collectStepStyles(sq1a2)).toEqual([{'opacity': 0.8}, {'opacity': 0.5}]); + expect(collectStepStyles(sq1a2)).toEqual([{'opacity': '0.8'}, {'opacity': '0.5'}]); var sq2a1 = sq2.steps[0]; - expect(collectStepStyles(sq2a1)).toEqual([{'opacity': 0}, {'opacity': 0.8}]); + expect(collectStepStyles(sq2a1)).toEqual([{'opacity': '0'}, {'opacity': '0.8'}]); var sq2a2 = sq2.steps[1]; expect(collectStepStyles(sq2a2)).toEqual([{'color': 'black'}, {'color': 'blue'}]); @@ -180,8 +186,8 @@ export function main() { 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}))]) + style({'opacity': '0'}), + group([animate(1000, style({'opacity': '1'})), animate(2000, style({'opacity': '0.5'}))]) ]); var errors1 = animation1.errors; @@ -205,23 +211,24 @@ export function main() { 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}))]); + [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'}) + 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 ast = + parseAnimationAst([animate(1000, keyframes([ + style({'width': '0'}), style({'width': '25px'}), + style({'width': '50px'}), style({'width': '75px'}) + ]))]); var step = ast.steps[0]; expect(step.keyframes.length).toEqual(4); @@ -233,11 +240,11 @@ export function main() { }); 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 ast = parseAnimationAst([animate( + 1000, keyframes([ + style({'height': '0', 'offset': 0}), style({'height': '25px', 'offset': 0.6}), + style({'height': '50px', 'offset': 0.7}), style({'height': '75px', 'offset': 1}) + ]))]); var step = ast.steps[0]; expect(step.keyframes.length).toEqual(4); @@ -251,24 +258,25 @@ export function main() { 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}) + style({'opacity': '0', 'offset': 0.9}), style({'opacity': '0.25', 'offset': 0}), + style({'opacity': '0.50', 'offset': 1}), + style({'opacity': '0.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[0].styles.styles[0]['opacity']).toEqual('0.25'); expect(step.keyframes[1].offset).toEqual(0.9); - expect(step.keyframes[1].styles.styles[0]['opacity']).toEqual(0); + 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[2].styles.styles[0]['opacity']).toEqual('0.75'); expect(step.keyframes[3].offset).toEqual(1); - expect(step.keyframes[3].styles.styles[0]['opacity']).toEqual(.50); + expect(step.keyframes[3].styles.styles[0]['opacity']).toEqual('0.50'); }); it('should throw an error if a partial amount of keyframes contain an offset', () => { @@ -302,7 +310,7 @@ export function main() { 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': 'white', 'borderColor': 'white'}), style({'color': 'red', 'background': 'blue'}), style({'background': 'blue'}) ]))]); @@ -312,20 +320,17 @@ export function main() { var kf3 = keyframesStep.keyframes[2]; expect(flattenStyles(kf3.styles.styles)) - .toEqual({'background': 'blue', 'color': 'red', 'border-color': 'white'}); + .toEqual({'background': 'blue', 'color': 'red', 'borderColor': '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 ast = parseAnimationAst([animate( + 1000, keyframes([ + style({'color': 'white', 'background': 'black', 'offset': 0.5}), + style( + {'color': 'orange', 'background': 'red', 'fontSize': '100px', 'offset': 1}) + ]))]); var keyframesStep = ast.steps[0]; expect(keyframesStep.keyframes.length).toEqual(3); @@ -335,7 +340,7 @@ export function main() { expect(kf1.offset).toEqual(0); expect(flattenStyles(kf1.styles.styles)).toEqual({ - 'font-size': FILL_STYLE_FLAG, + 'fontSize': FILL_STYLE_FLAG, 'background': FILL_STYLE_FLAG, 'color': FILL_STYLE_FLAG }); @@ -353,7 +358,7 @@ export function main() { style({ 'color': 'orange', 'background': 'red', - 'font-size': '100px', + 'fontSize': '100px', 'offset': 0.5 }) ]))]); @@ -369,13 +374,13 @@ export function main() { 'color': 'orange', 'background': 'red', 'transform': 'rotate(360deg)', - 'font-size': '100px' + 'fontSize': '100px' }); }); describe('easing / duration / delay', () => { it('should parse simple string-based values', () => { - var ast = parseAnimationAst([animate('1s .5s ease-out', style({'opacity': 1}))]); + var ast = parseAnimationAst([animate('1s .5s ease-out', style({'opacity': '1'}))]); var step = ast.steps[0]; expect(step.duration).toEqual(1000); @@ -384,7 +389,7 @@ export function main() { }); it('should parse a numeric duration value', () => { - var ast = parseAnimationAst([animate(666, style({'opacity': 1}))]); + var ast = parseAnimationAst([animate(666, style({'opacity': '1'}))]); var step = ast.steps[0]; expect(step.duration).toEqual(666); @@ -393,7 +398,7 @@ export function main() { }); it('should parse an easing value without a delay', () => { - var ast = parseAnimationAst([animate('5s linear', style({'opacity': 1}))]); + var ast = parseAnimationAst([animate('5s linear', style({'opacity': '1'}))]); var step = ast.steps[0]; expect(step.duration).toEqual(5000); @@ -403,7 +408,7 @@ export function main() { it('should parse a complex easing value', () => { var ast = - parseAnimationAst([animate('30ms cubic-bezier(0, 0,0, .69)', style({'opacity': 1}))]); + parseAnimationAst([animate('30ms cubic-bezier(0, 0,0, .69)', style({'opacity': '1'}))]); var step = ast.steps[0]; expect(step.duration).toEqual(30); diff --git a/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts b/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts index e373466f52..0e93a53c7f 100644 --- a/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts +++ b/modules/@angular/compiler/test/schema/dom_element_schema_registry_spec.ts @@ -192,5 +192,44 @@ If 'onAnything' is a directive input, make sure the directive is imported by the }); } + describe('normalizeAnimationStyleProperty', () => { + it('should normalize the given CSS property to camelCase', () => { + expect(registry.normalizeAnimationStyleProperty('border-radius')).toBe('borderRadius'); + expect(registry.normalizeAnimationStyleProperty('zIndex')).toBe('zIndex'); + expect(registry.normalizeAnimationStyleProperty('-webkit-animation')) + .toBe('WebkitAnimation'); + }); + }); + + describe('normalizeAnimationStyleValue', () => { + it('should normalize the given dimensional CSS style value to contain a PX value when numeric', + () => { + expect( + registry.normalizeAnimationStyleValue('borderRadius', 'border-radius', 10)['value']) + .toBe('10px'); + }); + + it('should not normalize any values that are of zero', () => { + expect(registry.normalizeAnimationStyleValue('opacity', 'opacity', 0)['value']).toBe('0'); + expect(registry.normalizeAnimationStyleValue('width', 'width', 0)['value']).toBe('0'); + }); + + it('should retain the given dimensional CSS style value\'s unit if it already exists', () => { + expect( + registry.normalizeAnimationStyleValue('borderRadius', 'border-radius', '10em')['value']) + .toBe('10em'); + }); + + it('should trim the provided CSS style value', () => { + expect(registry.normalizeAnimationStyleValue('color', 'color', ' red ')['value']) + .toBe('red'); + }); + + it('should stringify all non dimensional numeric style values', () => { + expect(registry.normalizeAnimationStyleValue('zIndex', 'zIndex', 10)['value']).toBe('10'); + expect(registry.normalizeAnimationStyleValue('opacity', 'opacity', 0.5)['value']) + .toBe('0.5'); + }); + }); }); } diff --git a/modules/@angular/compiler/testing/schema_registry_mock.ts b/modules/@angular/compiler/testing/schema_registry_mock.ts index 45b7d0885a..7a76129e48 100644 --- a/modules/@angular/compiler/testing/schema_registry_mock.ts +++ b/modules/@angular/compiler/testing/schema_registry_mock.ts @@ -54,4 +54,10 @@ export class MockSchemaRegistry implements ElementSchemaRegistry { return {error: false}; } } + + normalizeAnimationStyleProperty(propName: string): string { return propName; } + normalizeAnimationStyleValue(camelCaseProp: string, userProvidedProp: string, val: string|number): + {error: string, value: string} { + return {error: null, value: val.toString()}; + } } diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 21cf07093e..de20f9a027 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -7,6 +7,7 @@ */ import {CommonModule} from '@angular/common'; +import {DomElementSchemaRegistry, ElementSchemaRegistry} from '@angular/compiler'; import {AnimationDriver} from '@angular/platform-browser/src/dom/animation_driver'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver'; @@ -52,7 +53,7 @@ function declareTests({useJit}: {useJit: boolean}) { 'myAnimation', [transition( 'void => *', - [style({'opacity': 0}), animate(500, style({'opacity': 1}))])])] + [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])] } }); @@ -68,8 +69,8 @@ function declareTests({useJit}: {useJit: boolean}) { var keyframes2 = driver.log[0]['keyframeLookup']; expect(keyframes2.length).toEqual(2); - expect(keyframes2[0]).toEqual([0, {'opacity': 0}]); - expect(keyframes2[1]).toEqual([1, {'opacity': 1}]); + expect(keyframes2[0]).toEqual([0, {'opacity': '0'}]); + expect(keyframes2[1]).toEqual([1, {'opacity': '1'}]); })); it('should trigger a state change animation from state => void', fakeAsync(() => { @@ -82,7 +83,7 @@ function declareTests({useJit}: {useJit: boolean}) { 'myAnimation', [transition( '* => void', - [style({'opacity': 1}), animate(500, style({'opacity': 0}))])])] + [style({'opacity': '1'}), animate(500, style({'opacity': '0'}))])])] } }); @@ -102,12 +103,14 @@ function declareTests({useJit}: {useJit: boolean}) { var keyframes2 = driver.log[0]['keyframeLookup']; expect(keyframes2.length).toEqual(2); - expect(keyframes2[0]).toEqual([0, {'opacity': 1}]); - expect(keyframes2[1]).toEqual([1, {'opacity': 0}]); + expect(keyframes2[0]).toEqual([0, {'opacity': '1'}]); + expect(keyframes2[1]).toEqual([1, {'opacity': '0'}]); })); - it('should animate the element when the expression changes between states', fakeAsync(() => { - TestBed.overrideComponent(DummyIfCmp, { + it('should animate the element when the expression changes between states', + fakeAsync( + () => { + TestBed.overrideComponent(DummyIfCmp, { set: { template: `
@@ -115,36 +118,36 @@ function declareTests({useJit}: {useJit: boolean}) { animations: [ trigger('myAnimation', [ transition('* => state1', [ - style({'background': 'red'}), - animate('0.5s 1s ease-out', style({'background': 'blue'})) + style({'backgroundColor': 'red'}), + animate('0.5s 1s ease-out', style({'backgroundColor': 'blue'})) ]) ]) ] } }); - const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; - let fixture = TestBed.createComponent(DummyIfCmp); - var cmp = fixture.componentInstance; - cmp.exp = 'state1'; - fixture.detectChanges(); + const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp = 'state1'; + fixture.detectChanges(); - flushMicrotasks(); + flushMicrotasks(); - expect(driver.log.length).toEqual(1); + expect(driver.log.length).toEqual(1); - var animation1 = driver.log[0]; - expect(animation1['duration']).toEqual(500); - expect(animation1['delay']).toEqual(1000); - expect(animation1['easing']).toEqual('ease-out'); + var animation1 = driver.log[0]; + expect(animation1['duration']).toEqual(500); + expect(animation1['delay']).toEqual(1000); + expect(animation1['easing']).toEqual('ease-out'); - var startingStyles = animation1['startingStyles']; - expect(startingStyles).toEqual({'background': 'red'}); + var startingStyles = animation1['startingStyles']; + expect(startingStyles).toEqual({'backgroundColor': 'red'}); - var kf = animation1['keyframeLookup']; - expect(kf[0]).toEqual([0, {'background': 'red'}]); - expect(kf[1]).toEqual([1, {'background': 'blue'}]); - })); + var kf = animation1['keyframeLookup']; + expect(kf[0]).toEqual([0, {'backgroundColor': 'red'}]); + expect(kf[1]).toEqual([1, {'backgroundColor': 'blue'}]); + })); describe('animation aliases', () => { it('should animate the ":enter" animation alias as "void => *"', fakeAsync(() => { @@ -154,10 +157,12 @@ function declareTests({useJit}: {useJit: boolean}) {
`, animations: [trigger( - 'myAnimation', - [transition( - ':enter', - [style({'opacity': 0}), animate('500ms', style({opacity: 1}))])])] + 'myAnimation', [transition( + ':enter', + [ + style({'opacity': '0'}), + animate('500ms', style({'opacity': '1'})) + ])])] } }); @@ -181,7 +186,7 @@ function declareTests({useJit}: {useJit: boolean}) { `, animations: [trigger( 'myAnimation', - [transition(':leave', [animate('999ms', style({opacity: 0}))])])] + [transition(':leave', [animate('999ms', style({'opacity': '0'}))])])] } }); @@ -211,7 +216,8 @@ function declareTests({useJit}: {useJit: boolean}) { `, animations: [trigger( 'myAnimation', - [transition(':dont_leave_me', [animate('444ms', style({opacity: 0}))])])] + [transition( + ':dont_leave_me', [animate('444ms', style({'opacity': '0'}))])])] } }); @@ -265,6 +271,135 @@ function declareTests({useJit}: {useJit: boolean}) { expect(result['keyframeLookup']).toEqual([[0, {'opacity': '1'}], [1, {'opacity': '0'}]]); })); + describe('schema normalization', () => { + beforeEach(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: `
`, + animations: [trigger( + 'myAnimation', + [ + state('*', style({'border-width': '10px', 'height': 111})), + state('void', style({'z-index': '20'})), + transition('* => *', [ + style({ height: '200px ', '-webkit-border-radius': '10px' }), + animate('500ms') + ]) + ])] + } + }); + }); + + describe('via DomElementSchemaRegistry', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{provide: ElementSchemaRegistry, useClass: DomElementSchemaRegistry}] + }); + }); + + it('should normalize all CSS style properties to camelCase during compile time if a DOM schema is used', + fakeAsync(() => { + const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + var result = driver.log.pop(); + var styleProps1 = Object.keys(result['keyframeLookup'][0][1]).sort(); + var styleProps2 = Object.keys(result['keyframeLookup'][1][1]).sort(); + + var expectedProps = ['WebkitBorderRadius', 'borderWidth', 'height', 'zIndex']; + expect(styleProps1) + .toEqual(expectedProps); // the start/end styles are always balanced + expect(styleProps2).toEqual(expectedProps); + })); + + it('should normalize all dimensional CSS style values to `px` values if the value is a number', + fakeAsync(() => { + const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + var result = driver.log.pop(); + + var styleVals1 = result['keyframeLookup'][0][1]; + expect(styleVals1['zIndex']).toEqual('20'); + expect(styleVals1['height']).toEqual('200px'); + + var styleVals2 = result['keyframeLookup'][1][1]; + expect(styleVals2['borderWidth']).toEqual('10px'); + expect(styleVals2['height']).toEqual('111px'); + })); + + it('should throw an error when a string-based dimensional style value is used that does not contain a unit value is detected', + fakeAsync(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: `
`, + animations: [trigger( + 'myAnimation', + [state('*', style({width: '123'})), transition('* => *', animate(500))])] + } + }); + + expect(() => { + TestBed.createComponent(DummyIfCmp); + }).toThrowError(/Please provide a CSS unit value for width:123/); + })); + }); + + describe('not using DomElementSchemaRegistry', () => { + beforeEach(() => { + TestBed.configureTestingModule( + {providers: [{provide: ElementSchemaRegistry, useClass: _NaiveElementSchema}]}); + + it('should not normalize any CSS style properties to camelCase during compile time if a DOM schema is used', + fakeAsync(() => { + const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + var result = driver.log.pop(); + var styleProps1 = Object.keys(result['keyframeLookup'][0][1]).sort(); + var styleProps2 = Object.keys(result['keyframeLookup'][1][1]).sort(); + + var expectedProps = ['-webkit-border-radius', 'border-width', 'height', 'z-index']; + expect(styleProps1) + .toEqual(expectedProps); // the start/end styles are always balanced + expect(styleProps2).toEqual(expectedProps); + })); + + it('should not normalize nay dimensional CSS style values to `px` values if the value is a number', + fakeAsync(() => { + const driver = TestBed.get(AnimationDriver) as MockAnimationDriver; + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + var result = driver.log.pop(); + + var styleVals1 = result['keyframeLookup'][0][1]; + expect(styleVals1['z-index']).toEqual('20'); + expect(styleVals1['height']).toEqual('200px'); + + var styleVals2 = result['keyframeLookup'][1][1]; + expect(styleVals2['border-width']).toEqual('10px'); + expect(styleVals2['height']).toEqual(111); + })); + }); + }); + }); + it('should combine repeated style steps into a single step', fakeAsync(() => { TestBed.overrideComponent(DummyIfCmp, { set: { @@ -277,11 +412,11 @@ function declareTests({useJit}: {useJit: boolean}) { style({'background': 'red'}), style({'width': '100px'}), style({'background': 'gold'}), - style({'height': 111}), + style({'height': '111px'}), animate('999ms', style({'width': '200px', 'background': 'blue'})), style({'opacity': '1'}), - style({'border-width': '100px'}), - animate('999ms', style({'opacity': '0', 'height': '200px', 'border-width': '10px'})) + style({'borderWidth': '100px'}), + animate('999ms', style({'opacity': '0', 'height': '200px', 'borderWidth': '10px'})) ]) ]) ] @@ -303,7 +438,7 @@ function declareTests({useJit}: {useJit: boolean}) { expect(animation1['delay']).toEqual(0); expect(animation1['easing']).toEqual(null); expect(animation1['startingStyles']) - .toEqual({'background': 'gold', 'width': '100px', 'height': 111}); + .toEqual({'background': 'gold', 'width': '100px', 'height': '111px'}); var keyframes1 = animation1['keyframeLookup']; expect(keyframes1[0]).toEqual([0, {'background': 'gold', 'width': '100px'}]); @@ -313,14 +448,14 @@ function declareTests({useJit}: {useJit: boolean}) { expect(animation2['duration']).toEqual(999); expect(animation2['delay']).toEqual(0); expect(animation2['easing']).toEqual(null); - expect(animation2['startingStyles']).toEqual({'opacity': '1', 'border-width': '100px'}); + expect(animation2['startingStyles']).toEqual({'opacity': '1', 'borderWidth': '100px'}); var keyframes2 = animation2['keyframeLookup']; expect(keyframes2[0]).toEqual([ - 0, {'opacity': '1', 'height': 111, 'border-width': '100px'} + 0, {'opacity': '1', 'height': '111px', 'borderWidth': '100px'} ]); expect(keyframes2[1]).toEqual([ - 1, {'opacity': '0', 'height': '200px', 'border-width': '10px'} + 1, {'opacity': '0', 'height': '200px', 'borderWidth': '10px'} ]); })); @@ -515,10 +650,10 @@ function declareTests({useJit}: {useJit: boolean}) { var kf = driver.log[0]['keyframeLookup']; expect(kf.length).toEqual(4); - expect(kf[0]).toEqual([0, {'width': 0}]); - expect(kf[1]).toEqual([0.25, {'width': 100}]); - expect(kf[2]).toEqual([0.75, {'width': 200}]); - expect(kf[3]).toEqual([1, {'width': 300}]); + expect(kf[0]).toEqual([0, {'width': '0'}]); + expect(kf[1]).toEqual([0.25, {'width': '100px'}]); + expect(kf[2]).toEqual([0.75, {'width': '200px'}]); + expect(kf[3]).toEqual([1, {'width': '300px'}]); })); it('should fetch any keyframe styles that are not defined in the first keyframe from the previous entries or getCompuedStyle', @@ -535,7 +670,7 @@ function declareTests({useJit}: {useJit: boolean}) { animate(1000, style({'color': 'silver'})), animate(1000, keyframes([ style([{'color': 'gold', offset: 0.25}]), - style([{'color': 'bronze', 'background-color': 'teal', offset: 0.50}]), + style([{'color': 'bronze', 'backgroundColor': 'teal', offset: 0.50}]), style([{'color': 'platinum', offset: 0.75}]), style([{'color': 'diamond', offset: 1}]) ])) @@ -554,11 +689,11 @@ function declareTests({useJit}: {useJit: boolean}) { var kf = driver.log[1]['keyframeLookup']; expect(kf.length).toEqual(5); - expect(kf[0]).toEqual([0, {'color': 'silver', 'background-color': AUTO_STYLE}]); + expect(kf[0]).toEqual([0, {'color': 'silver', 'backgroundColor': AUTO_STYLE}]); expect(kf[1]).toEqual([0.25, {'color': 'gold'}]); - expect(kf[2]).toEqual([0.50, {'color': 'bronze', 'background-color': 'teal'}]); + expect(kf[2]).toEqual([0.50, {'color': 'bronze', 'backgroundColor': 'teal'}]); expect(kf[3]).toEqual([0.75, {'color': 'platinum'}]); - expect(kf[4]).toEqual([1, {'color': 'diamond', 'background-color': 'teal'}]); + expect(kf[4]).toEqual([1, {'color': 'diamond', 'backgroundColor': 'teal'}]); })); }); @@ -573,7 +708,7 @@ function declareTests({useJit}: {useJit: boolean}) { 'myAnimation', [transition( '* => *', - [style({'opacity': 0}), animate(500, style({'opacity': 1}))])])] + [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])] } }); @@ -607,7 +742,7 @@ function declareTests({useJit}: {useJit: boolean}) { animations: [ trigger('myAnimation', [ transition('void => *', [ - style({'background': 'red', 'opacity': 0.5}), + style({'background': 'red', 'opacity': '0.5'}), animate(500, style({'background': 'black'})), group([ animate(500, style({'background': 'black'})), @@ -714,7 +849,7 @@ function declareTests({useJit}: {useJit: boolean}) { `, animations: [trigger( 'myAnimation', - [transition('* => void', [animate(1000, style({'opacity': 0}))])])] + [transition('* => void', [animate(1000, style({'opacity': '0'}))])])] } }); @@ -750,8 +885,8 @@ function declareTests({useJit}: {useJit: boolean}) { [trigger('myAnimation', [transition( '* => *', [ - animate(1000, style({'opacity': 0})), - animate(1000, style({'opacity': 1})) + animate(1000, style({'opacity': '0'})), + animate(1000, style({'opacity': '1'})) ])])] } }); @@ -766,12 +901,12 @@ function declareTests({useJit}: {useJit: boolean}) { var animation1 = driver.log[0]; var keyframes1 = animation1['keyframeLookup']; expect(keyframes1[0]).toEqual([0, {'opacity': AUTO_STYLE}]); - expect(keyframes1[1]).toEqual([1, {'opacity': 0}]); + expect(keyframes1[1]).toEqual([1, {'opacity': '0'}]); var animation2 = driver.log[1]; var keyframes2 = animation2['keyframeLookup']; - expect(keyframes2[0]).toEqual([0, {'opacity': 0}]); - expect(keyframes2[1]).toEqual([1, {'opacity': 1}]); + expect(keyframes2[0]).toEqual([0, {'opacity': '0'}]); + expect(keyframes2[1]).toEqual([1, {'opacity': '1'}]); })); it('should perform two transitions in parallel if defined in different state triggers', @@ -783,9 +918,10 @@ function declareTests({useJit}: {useJit: boolean}) { `, animations: [ trigger( - 'one', [transition( - 'state1 => state2', - [style({'opacity': 0}), animate(1000, style({'opacity': 1}))])]), + 'one', + [transition( + 'state1 => state2', + [style({'opacity': '0'}), animate(1000, style({'opacity': '1'}))])]), trigger( 'two', [transition( @@ -1661,8 +1797,7 @@ function declareTests({useJit}: {useJit: boolean}) { animations: [trigger( 'status', [ - state('final', style({'top': '100px'})), - transition('* => final', [animate(1000)]) + state('final', style({'top': 100})), transition('* => final', [animate(1000)]) ])] } }); @@ -1778,8 +1913,8 @@ function declareTests({useJit}: {useJit: boolean}) { animations: [trigger( 'status', [ - state('void', style({'width': '0px'})), - state('final', style({'width': '100px'})), + state('void', style({'width': 0})), + state('final', style({'width': 100})), ])] } }); @@ -1909,7 +2044,7 @@ function declareTests({useJit}: {useJit: boolean}) { animations: [trigger( 'status', [ - state('void', style({'height': '100px', 'opacity': 0})), + state('void', style({'height': '100px', 'opacity': '0'})), state('final', style({'height': '333px', 'width': '200px'})), transition('void => final', [animate(1000)]) ])] @@ -1927,7 +2062,7 @@ function declareTests({useJit}: {useJit: boolean}) { var animation = driver.log.pop(); var kf = animation['keyframeLookup']; - expect(kf[0]).toEqual([0, {'height': '100px', 'opacity': 0, 'width': AUTO_STYLE}]); + expect(kf[0]).toEqual([0, {'height': '100px', 'opacity': '0', 'width': AUTO_STYLE}]); expect(kf[1]).toEqual([1, {'height': '333px', 'opacity': AUTO_STYLE, 'width': '200px'}]); }); @@ -2006,3 +2141,12 @@ class BrokenDummyLoadingCmp { exp = false; callback = () => {}; } + +class _NaiveElementSchema extends DomElementSchemaRegistry { + normalizeAnimationStyleProperty(propName: string): string { return propName; } + + normalizeAnimationStyleValue(camelCaseProp: string, userProvidedProp: string, val: string|number): + {error: string, value: string} { + return {error: null, value: val}; + } +} diff --git a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts index 8176b2fe3d..9a34818334 100644 --- a/modules/@angular/platform-browser/src/dom/web_animations_driver.ts +++ b/modules/@angular/platform-browser/src/dom/web_animations_driver.ts @@ -6,13 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {AUTO_STYLE} from '@angular/core'; - import {isPresent} from '../facade/lang'; import {AnimationKeyframe, AnimationStyles} from '../private_import_core'; import {AnimationDriver} from './animation_driver'; -import {dashCaseToCamelCase} from './util'; import {WebAnimationsPlayer} from './web_animations_player'; export class WebAnimationsDriver implements AnimationDriver { @@ -63,14 +60,8 @@ function _populateStyles( element: any, styles: AnimationStyles, defaultStyles: {[key: string]: string | number}): {[key: string]: string | number} { var data: {[key: string]: string | number} = {}; - styles.styles.forEach((entry) => { - Object.keys(entry).forEach(prop => { - const val = entry[prop]; - var formattedProp = dashCaseToCamelCase(prop); - data[formattedProp] = - val == AUTO_STYLE ? val : val.toString() + _resolveStyleUnit(val, prop, formattedProp); - }); - }); + styles.styles.forEach( + (entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); }); Object.keys(defaultStyles).forEach(prop => { if (!isPresent(data[prop])) { data[prop] = defaultStyles[prop]; @@ -78,66 +69,3 @@ function _populateStyles( }); return data; } - -function _resolveStyleUnit( - val: string | number, userProvidedProp: string, formattedProp: string): string { - var unit = ''; - if (_isPixelDimensionStyle(formattedProp) && val != 0 && val != '0') { - if (typeof val === 'number') { - unit = 'px'; - } else if (_findDimensionalSuffix(val.toString()).length == 0) { - throw new Error('Please provide a CSS unit value for ' + userProvidedProp + ':' + val); - } - } - return unit; -} - -const _$0 = 48; -const _$9 = 57; -const _$PERIOD = 46; - -function _findDimensionalSuffix(value: string): string { - for (var i = 0; i < value.length; i++) { - var c = value.charCodeAt(i); - if ((c >= _$0 && c <= _$9) || c == _$PERIOD) continue; - return value.substring(i, value.length); - } - return ''; -} - -function _isPixelDimensionStyle(prop: string): boolean { - switch (prop) { - case 'width': - case 'height': - case 'minWidth': - case 'minHeight': - case 'maxWidth': - case 'maxHeight': - case 'left': - case 'top': - case 'bottom': - case 'right': - case 'fontSize': - case 'outlineWidth': - case 'outlineOffset': - case 'paddingTop': - case 'paddingLeft': - case 'paddingBottom': - case 'paddingRight': - case 'marginTop': - case 'marginLeft': - case 'marginBottom': - case 'marginRight': - case 'borderRadius': - case 'borderWidth': - case 'borderTopWidth': - case 'borderLeftWidth': - case 'borderRightWidth': - case 'borderBottomWidth': - case 'textIndent': - return true; - - default: - return false; - } -} diff --git a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts index 660737baad..9a07463db6 100644 --- a/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts +++ b/modules/@angular/platform-browser/test/dom/web_animations_driver_spec.ts @@ -45,46 +45,6 @@ export function main() { elm = el('
'); }); - it('should convert all styles to camelcase', () => { - var startingStyles = _makeStyles({'border-top-right': '40px'}); - var styles = [ - _makeKeyframe(0, {'max-width': '100px', 'height': '200px'}), - _makeKeyframe(1, {'font-size': '555px'}) - ]; - - var player = driver.animate(elm, startingStyles, styles, 0, 0, 'linear'); - var details = _formatOptions(player); - var startKeyframe = details['keyframes'][0]; - var firstKeyframe = details['keyframes'][1]; - var lastKeyframe = details['keyframes'][2]; - - expect(startKeyframe['borderTopRight']).toEqual('40px'); - - expect(firstKeyframe['maxWidth']).toEqual('100px'); - expect(firstKeyframe['max-width']).toBeFalsy(); - expect(firstKeyframe['height']).toEqual('200px'); - - expect(lastKeyframe['fontSize']).toEqual('555px'); - expect(lastKeyframe['font-size']).toBeFalsy(); - }); - - it('should auto prefix numeric properties with a `px` value', () => { - var startingStyles = _makeStyles({'borderTopWidth': 40}); - var styles = [_makeKeyframe(0, {'font-size': 100}), _makeKeyframe(1, {'height': '555em'})]; - - var player = driver.animate(elm, startingStyles, styles, 0, 0, 'linear'); - var details = _formatOptions(player); - var startKeyframe = details['keyframes'][0]; - var firstKeyframe = details['keyframes'][1]; - var lastKeyframe = details['keyframes'][2]; - - expect(startKeyframe['borderTopWidth']).toEqual('40px'); - - expect(firstKeyframe['fontSize']).toEqual('100px'); - - expect(lastKeyframe['height']).toEqual('555em'); - }); - it('should use a fill mode of `both`', () => { var startingStyles = _makeStyles({}); var styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];