fix(animations): report errors for missing host-level referenced animations (#10650)
Closes #10650
This commit is contained in:
parent
6fd5bc075d
commit
f12d51992d
|
@ -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 {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser';
|
||||
|
||||
const animationCompilationCache = new Map<CompileDirectiveMetadata, CompiledAnimation[]>();
|
||||
|
||||
export class CompiledAnimation {
|
||||
constructor(
|
||||
public name: string, public statesMapStatement: o.Statement,
|
||||
|
@ -68,6 +70,7 @@ export class AnimationCompiler {
|
|||
throw new BaseException(errorMessageStr);
|
||||
}
|
||||
|
||||
animationCompilationCache.set(component, compiledAnimations);
|
||||
return compiledAnimations;
|
||||
}
|
||||
}
|
||||
|
@ -389,24 +392,43 @@ function _validateAnimationProperties(
|
|||
}
|
||||
|
||||
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
|
||||
private _animationRegistry: {[key: string]: boolean} = {};
|
||||
|
||||
private _animationRegistry: {[key: string]: boolean};
|
||||
public errors: AnimationParseError[] = [];
|
||||
|
||||
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 {
|
||||
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) {
|
||||
var animationName = input.name;
|
||||
if (!isPresent(this._animationRegistry[animationName])) {
|
||||
if (!isPresent(componentAnimationRegistry[animationName])) {
|
||||
this.errors.push(
|
||||
new AnimationParseError(`couldn't find an animation entry for ${animationName}`));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
t.templateVisitAll(this, ast.children);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import {AnimationPlayer} from '../../src/animation/animation_player';
|
|||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
import {AUTO_STYLE, AnimationEntryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
|
||||
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 {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() {
|
||||
beforeEachProviders(() => {
|
||||
TestBed.configureCompiler({useJit: useJit});
|
||||
TestBed.configureTestingModule(
|
||||
{providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DummyLoadingCmp, DummyIfCmp],
|
||||
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]
|
||||
});
|
||||
});
|
||||
|
||||
var makeAnimationCmp =
|
||||
|
@ -1043,6 +1045,21 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
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',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
|
@ -1361,6 +1378,7 @@ class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
|
|||
@Component({
|
||||
selector: 'if-cmp',
|
||||
directives: [NgIf],
|
||||
animations: [trigger('myAnimation', [])],
|
||||
template: `
|
||||
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
||||
`
|
||||
|
@ -1375,6 +1393,7 @@ class DummyIfCmp {
|
|||
selector: 'if-cmp',
|
||||
host: {'[@loading]': 'exp'},
|
||||
directives: [NgIf],
|
||||
animations: [trigger('loading', [])],
|
||||
template: `
|
||||
<div>loading...</div>
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue