fix(animations): report errors for missing host-level referenced animations (#10650)

Closes #10650
This commit is contained in:
Matias Niemelä 2016-08-17 08:00:49 -07:00 committed by vikerman
parent 6fd5bc075d
commit f12d51992d
2 changed files with 49 additions and 8 deletions

View File

@ -19,6 +19,8 @@ import * as t from '../template_parser/template_ast';
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast'; import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser'; import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser';
const animationCompilationCache = new Map<CompileDirectiveMetadata, CompiledAnimation[]>();
export class CompiledAnimation { export class CompiledAnimation {
constructor( constructor(
public name: string, public statesMapStatement: o.Statement, public name: string, public statesMapStatement: o.Statement,
@ -68,6 +70,7 @@ export class AnimationCompiler {
throw new BaseException(errorMessageStr); throw new BaseException(errorMessageStr);
} }
animationCompilationCache.set(component, compiledAnimations);
return compiledAnimations; return compiledAnimations;
} }
} }
@ -389,24 +392,43 @@ function _validateAnimationProperties(
} }
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor { class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
private _animationRegistry: {[key: string]: boolean} = {}; private _animationRegistry: {[key: string]: boolean};
public errors: AnimationParseError[] = []; public errors: AnimationParseError[] = [];
constructor(animations: CompiledAnimation[]) { constructor(animations: CompiledAnimation[]) {
animations.forEach(entry => { this._animationRegistry[entry.name] = true; }); this._animationRegistry = this._buildCompileAnimationLookup(animations);
}
private _buildCompileAnimationLookup(animations: CompiledAnimation[]): {[key: string]: boolean} {
var map: {[key: string]: boolean} = {};
animations.forEach(entry => { map[entry.name] = true; });
return map;
} }
visitElement(ast: t.ElementAst, ctx: any): any { visitElement(ast: t.ElementAst, ctx: any): any {
ast.inputs.forEach(input => { var inputAsts: t.BoundElementPropertyAst[] = ast.inputs;
var componentAnimationRegistry = this._animationRegistry;
var componentOnElement: t.DirectiveAst =
ast.directives.find(directive => directive.directive.isComponent);
if (componentOnElement) {
inputAsts = componentOnElement.hostProperties;
let cachedComponentAnimations = animationCompilationCache.get(componentOnElement.directive);
if (cachedComponentAnimations) {
componentAnimationRegistry = this._buildCompileAnimationLookup(cachedComponentAnimations);
}
}
inputAsts.forEach(input => {
if (input.type == t.PropertyBindingType.Animation) { if (input.type == t.PropertyBindingType.Animation) {
var animationName = input.name; var animationName = input.name;
if (!isPresent(this._animationRegistry[animationName])) { if (!isPresent(componentAnimationRegistry[animationName])) {
this.errors.push( this.errors.push(
new AnimationParseError(`couldn't find an animation entry for ${animationName}`)); new AnimationParseError(`couldn't find an animation entry for ${animationName}`));
} }
} }
}); });
t.templateVisitAll(this, ast.children); t.templateVisitAll(this, ast.children);
} }

View File

@ -19,7 +19,7 @@ import {AnimationPlayer} from '../../src/animation/animation_player';
import {AnimationStyles} from '../../src/animation/animation_styles'; import {AnimationStyles} from '../../src/animation/animation_styles';
import {AUTO_STYLE, AnimationEntryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata'; import {AUTO_STYLE, AnimationEntryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
import {isArray, isPresent} from '../../src/facade/lang'; import {isArray, isPresent} from '../../src/facade/lang';
import {TestBed, fakeAsync, flushMicrotasks, tick} from '../../testing'; import {TestBed, async, fakeAsync, flushMicrotasks, tick} from '../../testing';
import {MockAnimationPlayer} from '../../testing/mock_animation_player'; import {MockAnimationPlayer} from '../../testing/mock_animation_player';
import {AsyncTestCompleter, TestComponentBuilder, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '../../testing/testing_internal'; import {AsyncTestCompleter, TestComponentBuilder, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '../../testing/testing_internal';
@ -32,8 +32,10 @@ function declareTests({useJit}: {useJit: boolean}) {
describe('animation tests', function() { describe('animation tests', function() {
beforeEachProviders(() => { beforeEachProviders(() => {
TestBed.configureCompiler({useJit: useJit}); TestBed.configureCompiler({useJit: useJit});
TestBed.configureTestingModule( TestBed.configureTestingModule({
{providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]}); declarations: [DummyLoadingCmp, DummyIfCmp],
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]
});
}); });
var makeAnimationCmp = var makeAnimationCmp =
@ -1043,6 +1045,21 @@ function declareTests({useJit}: {useJit: boolean}) {
tick(); tick();
}))); })));
it('should throw an error if a host-level referenced animation is not defined within the component',
() => {
TestBed.overrideComponent(DummyLoadingCmp, {set: {animations: []}});
var failureMessage = '';
try {
inject([AnimationDriver], (driver: AnimationDriver) => {})();
} catch (e) {
failureMessage = e.message;
}
expect(failureMessage).toMatch(/- couldn't find an animation entry for loading/);
});
it('should retain the destination animation state styles once the animation is complete', it('should retain the destination animation state styles once the animation is complete',
inject( inject(
[TestComponentBuilder, AnimationDriver], [TestComponentBuilder, AnimationDriver],
@ -1361,6 +1378,7 @@ class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
@Component({ @Component({
selector: 'if-cmp', selector: 'if-cmp',
directives: [NgIf], directives: [NgIf],
animations: [trigger('myAnimation', [])],
template: ` template: `
<div *ngIf="exp" [@myAnimation]="exp"></div> <div *ngIf="exp" [@myAnimation]="exp"></div>
` `
@ -1375,6 +1393,7 @@ class DummyIfCmp {
selector: 'if-cmp', selector: 'if-cmp',
host: {'[@loading]': 'exp'}, host: {'[@loading]': 'exp'},
directives: [NgIf], directives: [NgIf],
animations: [trigger('loading', [])],
template: ` template: `
<div>loading...</div> <div>loading...</div>
` `