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 {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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
`
|
`
|
||||||
|
|
Loading…
Reference in New Issue