][bar.foo]>"): TestComp@0:3`);
+ });
+
+ describe('errors', () => {
+ it('should throw error when binding to an unknown property', () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
1. If 'my-component' is an Angular component and it has 'invalidProp' input, then verify that it is part of this module.
2. If 'my-component' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
("][invalidProp]="bar">"): TestComp@0:14`);
- });
+ });
- it('should throw error when binding to an unknown element w/o bindings', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
+ it('should throw error when binding to an unknown element w/o bindings', () => {
+ expect(() => parse('', [])).toThrowError(`Template parse errors:
'unknown' is not a known element:
1. If 'unknown' is an Angular component, then verify that it is part of this module.
2. If 'unknown' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]"): TestComp@0:0`);
- });
+ });
- it('should throw error when binding to an unknown custom element w/o bindings', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
+ it('should throw error when binding to an unknown custom element w/o bindings',
+ () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
'un-known' is not a known element:
1. If 'un-known' is an Angular component, then verify that it is part of this module.
2. If 'un-known' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]"): TestComp@0:0`);
- });
- });
+ });
- it('should parse bound properties via [...] and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', []))).toEqual([
- [ElementAst, 'div'],
- [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null]
- ]);
- });
+ it('should throw error when binding to an invalid property', () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
+Binding to property 'onEvent' is disallowed for security reasons ("][onEvent]="bar">"): TestComp@0:14`);
+ });
- it('should parse bound properties via bind- and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', []))).toEqual([
- [ElementAst, 'div'],
- [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null]
- ]);
- });
+ it('should throw error when binding to an invalid attribute', () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
+Binding to attribute 'onEvent' is disallowed for security reasons ("][attr.onEvent]="bar">"): TestComp@0:14`);
+ });
+ });
- it('should parse bound properties via {{...}} and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', []))).toEqual([
- [ElementAst, 'div'],
- [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null]
- ]);
- });
+ it('should parse bound properties via [...] and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', []))).toEqual([
+ [ElementAst, 'div'],
+ [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null]
+ ]);
+ });
- it('should parse bound properties via bind-animate- and not report them as attributes',
- () => {
- expect(humanizeTplAst(parse('
', [], [], [])))
- .toEqual([
+ it('should parse bound properties via bind- and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', []))).toEqual([
+ [ElementAst, 'div'],
+ [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null]
+ ]);
+ });
+
+ it('should parse bound properties via {{...}} and not report them as attributes',
+ () => {
+ expect(humanizeTplAst(parse('
', []))).toEqual([
[ElementAst, 'div'],
[
- BoundElementPropertyAst, PropertyBindingType.Animation, 'someAnimation',
- 'value2', null
+ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null
]
]);
- });
+ });
- it('should throw an error when parsing detects non-bound properties via @ that contain a value',
- () => {
- expect(() => { parse('
', [], [], []); })
- .toThrowError(
- /Assigning animation triggers via @prop="exp" attributes with an expression is invalid. Use property bindings \(e.g. \[@prop\]="exp"\) or use an attribute without a value \(e.g. @prop\) instead. \("
\]@someAnimation="value2">"\): TestComp@0:5/);
- });
+ it('should parse bound properties via bind-animate- and not report them as attributes',
+ () => {
+ expect(
+ humanizeTplAst(parse('
', [], [], [])))
+ .toEqual([
+ [ElementAst, 'div'],
+ [
+ BoundElementPropertyAst, PropertyBindingType.Animation, 'someAnimation',
+ 'value2', null
+ ]
+ ]);
+ });
- it('should not issue a warning when host attributes contain a valid property-bound animation trigger',
- () => {
- const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
- var dirA = CompileDirectiveMetadata.create({
- selector: 'div',
- template: new CompileTemplateMetadata({animations: animationEntries}),
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- host: {'[@prop]': 'expr'}
- });
+ it('should throw an error when parsing detects non-bound properties via @ that contain a value',
+ () => {
+ expect(() => { parse('
', [], [], []); })
+ .toThrowError(
+ /Assigning animation triggers via @prop="exp" attributes with an expression is invalid. Use property bindings \(e.g. \[@prop\]="exp"\) or use an attribute without a value \(e.g. @prop\) instead. \("
\]@someAnimation="value2">"\): TestComp@0:5/);
+ });
- humanizeTplAst(parse('', [dirA]));
- expect(console.warnings.length).toEqual(0);
- });
+ it('should not issue a warning when host attributes contain a valid property-bound animation trigger',
+ () => {
+ const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ template: new CompileTemplateMetadata({animations: animationEntries}),
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ host: {'[@prop]': 'expr'}
+ });
- it('should throw descriptive error when a host binding is not a string expression', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: 'broken',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- host: {'[class.foo]': null}
- });
+ humanizeTplAst(parse('', [dirA]));
+ expect(console.warnings.length).toEqual(0);
+ });
- expect(() => { parse('', [dirA]); })
- .toThrowError(
- `Template parse errors:\nValue of the host property binding "class.foo" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]"): TestComp@0:0, Directive DirA`);
- });
+ it('should throw descriptive error when a host binding is not a string expression', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'broken',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ host: {'[class.foo]': null}
+ });
- it('should throw descriptive error when a host event is not a string expression', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: 'broken',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- host: {'(click)': null}
- });
-
- expect(() => { parse('', [dirA]); })
- .toThrowError(
- `Template parse errors:\nValue of the host listener "click" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]"): TestComp@0:0, Directive DirA`);
- });
-
- it('should not issue a warning when an animation property is bound without an expression',
- () => {
- humanizeTplAst(parse('
', [], [], []));
- expect(console.warnings.length).toEqual(0);
- });
-
- it('should parse bound properties via [@] and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', []))
- .toThrowError(/Empty expressions are not allowed/);
-
- expect(() => parse('
', []))
- .toThrowError(/Empty expressions are not allowed/);
- });
-
- it('should parse bound events via (...) and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', []))
+ .toThrowError(/Empty expressions are not allowed/);
+
+ expect(() => parse('
', []))
+ .toThrowError(/Empty expressions are not allowed/);
+ });
+
+ it('should parse bound events via (...) and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[ElementAst, 'div'], [BoundEventAst, 'someEvent', null, 'v']]);
+ });
+
+ it('should parse bound events via on- and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[ElementAst, 'div'], [BoundEventAst, 'event', null, 'v']]);
+ });
+
+ it('should allow events on explicit embedded templates that are emitted by a directive',
+ () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'template',
+ outputs: ['e'],
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [EmbeddedTemplateAst],
+ [BoundEventAst, 'e', null, 'f'],
+ [DirectiveAst, dirA],
+ ]);
+ });
});
- }
- beforeEach(() => { nextProviderId = 0; });
+ describe('bindon', () => {
+ it('should parse bound events and properties via [(...)] and not report them as attributes',
+ () => {
+ expect(humanizeTplAst(parse('
', []))).toEqual([
+ [ElementAst, 'div'],
+ [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null],
+ [BoundEventAst, 'propChange', null, 'v = $event']
+ ]);
+ });
- it('should provide a component', () => {
- var comp = createDir('my-comp');
- var elAst: ElementAst = parse('', [comp])[0];
- expect(elAst.providers.length).toBe(1);
- expect(elAst.providers[0].providerType).toBe(ProviderAstType.Component);
- expect(elAst.providers[0].providers[0].useClass).toBe(comp.type);
- });
+ it('should parse bound events and properties via bindon- and not report them as attributes',
+ () => {
+ expect(humanizeTplAst(parse('
', []))).toEqual([
+ [ElementAst, 'div'],
+ [BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null],
+ [BoundEventAst, 'propChange', null, 'v = $event']
+ ]);
+ });
- it('should provide a directive', () => {
- var dirA = createDir('[dirA]');
- var elAst: ElementAst = parse('
', [dirA])[0];
- expect(elAst.providers.length).toBe(1);
- expect(elAst.providers[0].providerType).toBe(ProviderAstType.Directive);
- expect(elAst.providers[0].providers[0].useClass).toBe(dirA.type);
- });
-
- it('should use the public providers of a directive', () => {
- var provider = createProvider('service');
- var dirA = createDir('[dirA]', {providers: [provider]});
- var elAst: ElementAst = parse('
', [dirA])[0];
- expect(elAst.providers.length).toBe(2);
- expect(elAst.providers[1].providerType).toBe(ProviderAstType.PublicService);
- expect(elAst.providers[1].providers).toEqual([provider]);
- });
-
- it('should use the private providers of a component', () => {
- var provider = createProvider('service');
- var comp = createDir('my-comp', {viewProviders: [provider]});
- var elAst: ElementAst = parse('', [comp])[0];
- expect(elAst.providers.length).toBe(2);
- expect(elAst.providers[1].providerType).toBe(ProviderAstType.PrivateService);
- expect(elAst.providers[1].providers).toEqual([provider]);
- });
-
- it('should support multi providers', () => {
- var provider0 = createProvider('service0', {multi: true});
- var provider1 = createProvider('service1', {multi: true});
- var provider2 = createProvider('service0', {multi: true});
- var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
- var dirB = createDir('[dirB]', {providers: [provider2]});
- var elAst: ElementAst = parse('
', [dirA, dirB])[0];
- expect(elAst.providers.length).toBe(4);
- expect(elAst.providers[2].providers).toEqual([provider0, provider2]);
- expect(elAst.providers[3].providers).toEqual([provider1]);
- });
-
- it('should overwrite non multi providers', () => {
- var provider1 = createProvider('service0');
- var provider2 = createProvider('service1');
- var provider3 = createProvider('service0');
- var dirA = createDir('[dirA]', {providers: [provider1, provider2]});
- var dirB = createDir('[dirB]', {providers: [provider3]});
- var elAst: ElementAst = parse('
', [dirA, dirB])[0];
- expect(elAst.providers.length).toBe(4);
- expect(elAst.providers[2].providers).toEqual([provider3]);
- expect(elAst.providers[3].providers).toEqual([provider2]);
- });
-
- it('should overwrite component providers by directive providers', () => {
- var compProvider = createProvider('service0');
- var dirProvider = createProvider('service0');
- var comp = createDir('my-comp', {providers: [compProvider]});
- var dirA = createDir('[dirA]', {providers: [dirProvider]});
- var elAst: ElementAst = parse('', [dirA, comp])[0];
- expect(elAst.providers.length).toBe(3);
- expect(elAst.providers[2].providers).toEqual([dirProvider]);
- });
-
- it('should overwrite view providers by directive providers', () => {
- var viewProvider = createProvider('service0');
- var dirProvider = createProvider('service0');
- var comp = createDir('my-comp', {viewProviders: [viewProvider]});
- var dirA = createDir('[dirA]', {providers: [dirProvider]});
- var elAst: ElementAst = parse('', [dirA, comp])[0];
- expect(elAst.providers.length).toBe(3);
- expect(elAst.providers[2].providers).toEqual([dirProvider]);
- });
-
- it('should overwrite directives by providers', () => {
- var dirProvider = createProvider('type:my-comp');
- var comp = createDir('my-comp', {providers: [dirProvider]});
- var elAst: ElementAst = parse('', [comp])[0];
- expect(elAst.providers.length).toBe(1);
- expect(elAst.providers[0].providers).toEqual([dirProvider]);
- });
-
- it('if mixing multi and non multi providers', () => {
- var provider0 = createProvider('service0');
- var provider1 = createProvider('service0', {multi: true});
- var dirA = createDir('[dirA]', {providers: [provider0]});
- var dirB = createDir('[dirB]', {providers: [provider1]});
- expect(() => parse('
', [dirA, dirB]))
- .toThrowError(
- `Template parse errors:\n` +
- `Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]
"): TestComp@0:0`);
- });
-
- it('should sort providers by their DI order', () => {
- var provider0 = createProvider('service0', {deps: ['type:[dir2]']});
- var provider1 = createProvider('service1');
- var dir2 = createDir('[dir2]', {deps: ['service1']});
- var comp = createDir('my-comp', {providers: [provider0, provider1]});
- var elAst: ElementAst = parse('', [comp, dir2])[0];
- expect(elAst.providers.length).toBe(4);
- expect(elAst.providers[0].providers[0].useClass).toEqual(comp.type);
- expect(elAst.providers[1].providers).toEqual([provider1]);
- expect(elAst.providers[2].providers[0].useClass).toEqual(dir2.type);
- expect(elAst.providers[3].providers).toEqual([provider0]);
- });
-
- it('should sort directives by their DI order', () => {
- var dir0 = createDir('[dir0]', {deps: ['type:my-comp']});
- var dir1 = createDir('[dir1]', {deps: ['type:[dir0]']});
- var dir2 = createDir('[dir2]', {deps: ['type:[dir1]']});
- var comp = createDir('my-comp');
- var elAst: ElementAst =
- parse('', [comp, dir2, dir0, dir1])[0];
- expect(elAst.providers.length).toBe(4);
- expect(elAst.directives[0].directive).toBe(comp);
- expect(elAst.directives[1].directive).toBe(dir0);
- expect(elAst.directives[2].directive).toBe(dir1);
- expect(elAst.directives[3].directive).toBe(dir2);
- });
-
- it('should mark directives and dependencies of directives as eager', () => {
- var provider0 = createProvider('service0');
- var provider1 = createProvider('service1');
- var dirA = createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
- var elAst: ElementAst = parse('
', [dirA])[0];
- expect(elAst.providers.length).toBe(3);
- expect(elAst.providers[0].providers).toEqual([provider0]);
- expect(elAst.providers[0].eager).toBe(true);
- expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
- expect(elAst.providers[1].eager).toBe(true);
- expect(elAst.providers[2].providers).toEqual([provider1]);
- expect(elAst.providers[2].eager).toBe(false);
- });
-
- it('should mark dependencies on parent elements as eager', () => {
- var provider0 = createProvider('service0');
- var provider1 = createProvider('service1');
- var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
- var dirB = createDir('[dirB]', {deps: ['service0']});
- var elAst: ElementAst =
- parse('
', [dirA, dirB])[0];
- expect(elAst.providers.length).toBe(3);
- expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
- expect(elAst.providers[0].eager).toBe(true);
- expect(elAst.providers[1].providers).toEqual([provider0]);
- expect(elAst.providers[1].eager).toBe(true);
- expect(elAst.providers[2].providers).toEqual([provider1]);
- expect(elAst.providers[2].eager).toBe(false);
- });
-
- it('should mark queried providers as eager', () => {
- var provider0 = createProvider('service0');
- var provider1 = createProvider('service1');
- var dirA =
- createDir('[dirA]', {providers: [provider0, provider1], queries: ['service0']});
- var elAst: ElementAst = parse('', [dirA])[0];
- expect(elAst.providers.length).toBe(3);
- expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
- expect(elAst.providers[0].eager).toBe(true);
- expect(elAst.providers[1].providers).toEqual([provider0]);
- expect(elAst.providers[1].eager).toBe(true);
- expect(elAst.providers[2].providers).toEqual([provider1]);
- expect(elAst.providers[2].eager).toBe(false);
- });
-
- it('should not mark dependencies accross embedded views as eager', () => {
- var provider0 = createProvider('service0');
- var dirA = createDir('[dirA]', {providers: [provider0]});
- var dirB = createDir('[dirB]', {deps: ['service0']});
- var elAst: ElementAst =
- parse('
', [dirA, dirB])[0];
- expect(elAst.providers.length).toBe(2);
- expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
- expect(elAst.providers[0].eager).toBe(true);
- expect(elAst.providers[1].providers).toEqual([provider0]);
- expect(elAst.providers[1].eager).toBe(false);
- });
-
- it('should report missing @Self() deps as errors', () => {
- var dirA = createDir('[dirA]', {deps: ['self:provider0']});
- expect(() => parse('', [dirA]))
- .toThrowError(
- 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
- });
-
- it('should change missing @Self() that are optional to nulls', () => {
- var dirA = createDir('[dirA]', {deps: ['optional:self:provider0']});
- var elAst: ElementAst = parse('', [dirA])[0];
- expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
- expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
- });
-
- it('should report missing @Host() deps as errors', () => {
- var dirA = createDir('[dirA]', {deps: ['host:provider0']});
- expect(() => parse('', [dirA]))
- .toThrowError(
- 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
- });
-
- it('should change missing @Host() that are optional to nulls', () => {
- var dirA = createDir('[dirA]', {deps: ['optional:host:provider0']});
- var elAst: ElementAst = parse('', [dirA])[0];
- expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
- expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
- });
- });
-
- describe('references', () => {
-
- it('should parse references via #... and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', [
- ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
- });
-
- it('should parse references via ref-... and not report them as attributes', () => {
- expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
+ [ElementAst, 'div'], [BoundEventAst, 'a', null, 'b'], [DirectiveAst, dirA]
+ ]);
+ });
+
+ it('should parse directive host properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ host: {'[a]': 'expr'}
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [DirectiveAst, dirA],
+ [BoundElementPropertyAst, PropertyBindingType.Property, 'a', 'expr', null]
+ ]);
+ });
+
+ it('should parse directive host listeners', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ host: {'(a)': 'expr'}
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [DirectiveAst, dirA], [BoundEventAst, 'a', null, 'expr']
+ ]);
+ });
+
+ it('should parse directive properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['aProp']
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [DirectiveAst, dirA],
+ [BoundDirectivePropertyAst, 'aProp', 'expr']
+ ]);
+ });
+
+ it('should parse renamed directive properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['b:a']
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [DirectiveAst, dirA],
+ [BoundDirectivePropertyAst, 'b', 'expr']
+ ]);
+ });
+
+ it('should parse literal directive properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['a']
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA],
+ [BoundDirectivePropertyAst, 'a', '"literal"']
+ ]);
+ });
+
+ it('should favor explicit bound properties over literal properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['a']
+ });
+ expect(humanizeTplAst(parse('', [dirA])))
+ .toEqual([
+ [ElementAst, 'div'], [AttrAst, 'a', 'literal'], [DirectiveAst, dirA],
+ [BoundDirectivePropertyAst, 'a', '"literal2"']
+ ]);
+ });
+
+ it('should support optional directive properties', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['a']
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'], [DirectiveAst, dirA]
+ ]);
+ });
+
+ });
+
+ describe('providers', () => {
+ var nextProviderId: number;
+
+ function createToken(value: string): CompileTokenMetadata {
+ let token: CompileTokenMetadata;
+ if (value.startsWith('type:')) {
+ const name = value.substring(5);
+ token = new CompileTokenMetadata({
+ identifier: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name, reference: name as any as Type})
+ });
+ } else {
+ token = new CompileTokenMetadata({value: value});
+ }
+ return token;
+ }
+
+ function createDep(value: string): CompileDiDependencyMetadata {
+ var isOptional = false;
+ if (value.startsWith('optional:')) {
+ isOptional = true;
+ value = value.substring(9);
+ }
+ var isSelf = false;
+ if (value.startsWith('self:')) {
+ isSelf = true;
+ value = value.substring(5);
+ }
+ var isHost = false;
+ if (value.startsWith('host:')) {
+ isHost = true;
+ value = value.substring(5);
+ }
+ return new CompileDiDependencyMetadata({
+ token: createToken(value),
+ isOptional: isOptional,
+ isSelf: isSelf,
+ isHost: isHost
+ });
+ }
+
+ function createProvider(
+ token: string, {multi = false, deps = []}: {multi?: boolean, deps?: string[]} = {}):
+ CompileProviderMetadata {
+ const name = `provider${nextProviderId++}`;
+ return new CompileProviderMetadata({
+ token: createToken(token),
+ multi: multi,
+ useClass: new CompileTypeMetadata({name, reference: name as any as Type}),
+ deps: deps.map(createDep)
+ });
+ }
+
+ function createDir(
+ selector: string,
+ {providers = null, viewProviders = null, deps = [], queries = []}: {
+ providers?: CompileProviderMetadata[],
+ viewProviders?: CompileProviderMetadata[],
+ deps?: string[],
+ queries?: string[]
+ } = {}): CompileDirectiveMetadata {
+ var isComponent = !selector.startsWith('[');
+ return CompileDirectiveMetadata.create({
+ selector: selector,
+ type: new CompileTypeMetadata({
+ moduleUrl: someModuleUrl,
+ name: selector,
+ diDeps: deps.map(createDep),
+ reference: selector as any as Type
+ }),
+ isComponent: isComponent,
+ template: new CompileTemplateMetadata({ngContentSelectors: []}),
+ providers: providers,
+ viewProviders: viewProviders,
+ queries: queries.map(
+ (value) => new CompileQueryMetadata({selectors: [createToken(value)]}))
+ });
+ }
+
+ beforeEach(() => { nextProviderId = 0; });
+
+ it('should provide a component', () => {
+ var comp = createDir('my-comp');
+ var elAst: ElementAst = parse('', [comp])[0];
+ expect(elAst.providers.length).toBe(1);
+ expect(elAst.providers[0].providerType).toBe(ProviderAstType.Component);
+ expect(elAst.providers[0].providers[0].useClass).toBe(comp.type);
+ });
+
+ it('should provide a directive', () => {
+ var dirA = createDir('[dirA]');
+ var elAst: ElementAst = parse('
', [dirA])[0];
+ expect(elAst.providers.length).toBe(1);
+ expect(elAst.providers[0].providerType).toBe(ProviderAstType.Directive);
+ expect(elAst.providers[0].providers[0].useClass).toBe(dirA.type);
+ });
+
+ it('should use the public providers of a directive', () => {
+ var provider = createProvider('service');
+ var dirA = createDir('[dirA]', {providers: [provider]});
+ var elAst: ElementAst = parse('
', [dirA])[0];
+ expect(elAst.providers.length).toBe(2);
+ expect(elAst.providers[1].providerType).toBe(ProviderAstType.PublicService);
+ expect(elAst.providers[1].providers).toEqual([provider]);
+ });
+
+ it('should use the private providers of a component', () => {
+ var provider = createProvider('service');
+ var comp = createDir('my-comp', {viewProviders: [provider]});
+ var elAst: ElementAst = parse('', [comp])[0];
+ expect(elAst.providers.length).toBe(2);
+ expect(elAst.providers[1].providerType).toBe(ProviderAstType.PrivateService);
+ expect(elAst.providers[1].providers).toEqual([provider]);
+ });
+
+ it('should support multi providers', () => {
+ var provider0 = createProvider('service0', {multi: true});
+ var provider1 = createProvider('service1', {multi: true});
+ var provider2 = createProvider('service0', {multi: true});
+ var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
+ var dirB = createDir('[dirB]', {providers: [provider2]});
+ var elAst: ElementAst = parse('
', [dirA, dirB])[0];
+ expect(elAst.providers.length).toBe(4);
+ expect(elAst.providers[2].providers).toEqual([provider0, provider2]);
+ expect(elAst.providers[3].providers).toEqual([provider1]);
+ });
+
+ it('should overwrite non multi providers', () => {
+ var provider1 = createProvider('service0');
+ var provider2 = createProvider('service1');
+ var provider3 = createProvider('service0');
+ var dirA = createDir('[dirA]', {providers: [provider1, provider2]});
+ var dirB = createDir('[dirB]', {providers: [provider3]});
+ var elAst: ElementAst = parse('
', [dirA, dirB])[0];
+ expect(elAst.providers.length).toBe(4);
+ expect(elAst.providers[2].providers).toEqual([provider3]);
+ expect(elAst.providers[3].providers).toEqual([provider2]);
+ });
+
+ it('should overwrite component providers by directive providers', () => {
+ var compProvider = createProvider('service0');
+ var dirProvider = createProvider('service0');
+ var comp = createDir('my-comp', {providers: [compProvider]});
+ var dirA = createDir('[dirA]', {providers: [dirProvider]});
+ var elAst: ElementAst = parse('', [dirA, comp])[0];
+ expect(elAst.providers.length).toBe(3);
+ expect(elAst.providers[2].providers).toEqual([dirProvider]);
+ });
+
+ it('should overwrite view providers by directive providers', () => {
+ var viewProvider = createProvider('service0');
+ var dirProvider = createProvider('service0');
+ var comp = createDir('my-comp', {viewProviders: [viewProvider]});
+ var dirA = createDir('[dirA]', {providers: [dirProvider]});
+ var elAst: ElementAst = parse('', [dirA, comp])[0];
+ expect(elAst.providers.length).toBe(3);
+ expect(elAst.providers[2].providers).toEqual([dirProvider]);
+ });
+
+ it('should overwrite directives by providers', () => {
+ var dirProvider = createProvider('type:my-comp');
+ var comp = createDir('my-comp', {providers: [dirProvider]});
+ var elAst: ElementAst = parse('', [comp])[0];
+ expect(elAst.providers.length).toBe(1);
+ expect(elAst.providers[0].providers).toEqual([dirProvider]);
+ });
+
+ it('if mixing multi and non multi providers', () => {
+ var provider0 = createProvider('service0');
+ var provider1 = createProvider('service0', {multi: true});
+ var dirA = createDir('[dirA]', {providers: [provider0]});
+ var dirB = createDir('[dirB]', {providers: [provider1]});
+ expect(() => parse('
', [dirA, dirB]))
+ .toThrowError(
+ `Template parse errors:\n` +
+ `Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]
"): TestComp@0:0`);
+ });
+
+ it('should sort providers by their DI order', () => {
+ var provider0 = createProvider('service0', {deps: ['type:[dir2]']});
+ var provider1 = createProvider('service1');
+ var dir2 = createDir('[dir2]', {deps: ['service1']});
+ var comp = createDir('my-comp', {providers: [provider0, provider1]});
+ var elAst: ElementAst = parse('', [comp, dir2])[0];
+ expect(elAst.providers.length).toBe(4);
+ expect(elAst.providers[0].providers[0].useClass).toEqual(comp.type);
+ expect(elAst.providers[1].providers).toEqual([provider1]);
+ expect(elAst.providers[2].providers[0].useClass).toEqual(dir2.type);
+ expect(elAst.providers[3].providers).toEqual([provider0]);
+ });
+
+ it('should sort directives by their DI order', () => {
+ var dir0 = createDir('[dir0]', {deps: ['type:my-comp']});
+ var dir1 = createDir('[dir1]', {deps: ['type:[dir0]']});
+ var dir2 = createDir('[dir2]', {deps: ['type:[dir1]']});
+ var comp = createDir('my-comp');
+ var elAst: ElementAst =
+ parse('', [comp, dir2, dir0, dir1])[0];
+ expect(elAst.providers.length).toBe(4);
+ expect(elAst.directives[0].directive).toBe(comp);
+ expect(elAst.directives[1].directive).toBe(dir0);
+ expect(elAst.directives[2].directive).toBe(dir1);
+ expect(elAst.directives[3].directive).toBe(dir2);
+ });
+
+ it('should mark directives and dependencies of directives as eager', () => {
+ var provider0 = createProvider('service0');
+ var provider1 = createProvider('service1');
+ var dirA =
+ createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
+ var elAst: ElementAst = parse('
', [dirA])[0];
+ expect(elAst.providers.length).toBe(3);
+ expect(elAst.providers[0].providers).toEqual([provider0]);
+ expect(elAst.providers[0].eager).toBe(true);
+ expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
+ expect(elAst.providers[1].eager).toBe(true);
+ expect(elAst.providers[2].providers).toEqual([provider1]);
+ expect(elAst.providers[2].eager).toBe(false);
+ });
+
+ it('should mark dependencies on parent elements as eager', () => {
+ var provider0 = createProvider('service0');
+ var provider1 = createProvider('service1');
+ var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
+ var dirB = createDir('[dirB]', {deps: ['service0']});
+ var elAst: ElementAst =
+ parse('
', [dirA, dirB])[0];
+ expect(elAst.providers.length).toBe(3);
+ expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
+ expect(elAst.providers[0].eager).toBe(true);
+ expect(elAst.providers[1].providers).toEqual([provider0]);
+ expect(elAst.providers[1].eager).toBe(true);
+ expect(elAst.providers[2].providers).toEqual([provider1]);
+ expect(elAst.providers[2].eager).toBe(false);
+ });
+
+ it('should mark queried providers as eager', () => {
+ var provider0 = createProvider('service0');
+ var provider1 = createProvider('service1');
+ var dirA =
+ createDir('[dirA]', {providers: [provider0, provider1], queries: ['service0']});
+ var elAst: ElementAst = parse('', [dirA])[0];
+ expect(elAst.providers.length).toBe(3);
+ expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
+ expect(elAst.providers[0].eager).toBe(true);
+ expect(elAst.providers[1].providers).toEqual([provider0]);
+ expect(elAst.providers[1].eager).toBe(true);
+ expect(elAst.providers[2].providers).toEqual([provider1]);
+ expect(elAst.providers[2].eager).toBe(false);
+ });
+
+ it('should not mark dependencies accross embedded views as eager', () => {
+ var provider0 = createProvider('service0');
+ var dirA = createDir('[dirA]', {providers: [provider0]});
+ var dirB = createDir('[dirB]', {deps: ['service0']});
+ var elAst: ElementAst =
+ parse('
', [dirA, dirB])[0];
+ expect(elAst.providers.length).toBe(2);
+ expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
+ expect(elAst.providers[0].eager).toBe(true);
+ expect(elAst.providers[1].providers).toEqual([provider0]);
+ expect(elAst.providers[1].eager).toBe(false);
+ });
+
+ it('should report missing @Self() deps as errors', () => {
+ var dirA = createDir('[dirA]', {deps: ['self:provider0']});
+ expect(() => parse('', [dirA]))
+ .toThrowError(
+ 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
+ });
+
+ it('should change missing @Self() that are optional to nulls', () => {
+ var dirA = createDir('[dirA]', {deps: ['optional:self:provider0']});
+ var elAst: ElementAst = parse('', [dirA])[0];
+ expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
+ expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
+ });
+
+ it('should report missing @Host() deps as errors', () => {
+ var dirA = createDir('[dirA]', {deps: ['host:provider0']});
+ expect(() => parse('', [dirA]))
+ .toThrowError(
+ 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
+ });
+
+ it('should change missing @Host() that are optional to nulls', () => {
+ var dirA = createDir('[dirA]', {deps: ['optional:host:provider0']});
+ var elAst: ElementAst = parse('', [dirA])[0];
+ expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
+ expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
+ });
+ });
+
+ describe('references', () => {
+
+ it('should parse references via #... and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
+ });
+
+ it('should parse references via ref-... and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
"): TestComp@0:19`);
- });
+ });
- it('should not throw error when there is same reference name in different templates',
- () => {
- expect(() => parse('
OK
', []))
- .not.toThrowError();
+ it('should not throw error when there is same reference name in different templates',
+ () => {
+ expect(() => parse('
OK
', []))
+ .not.toThrowError();
- });
+ });
- it('should assign references with empty value to components', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- isComponent: true,
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- exportAs: 'dirA',
- template: new CompileTemplateMetadata({ngContentSelectors: []})
+ it('should assign references with empty value to components', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ isComponent: true,
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ exportAs: 'dirA',
+ template: new CompileTemplateMetadata({ngContentSelectors: []})
+ });
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'],
+ [AttrAst, 'a', ''],
+ [ReferenceAst, 'a', identifierToken(dirA.type)],
+ [DirectiveAst, dirA],
+ ]);
+ });
+
+ it('should not locate directives in references', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
+ });
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
+ });
- it('should not locate directives in variables', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
+ describe('directives', () => {
+ it('should locate directives in property bindings', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a=b]',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['a']
+ });
+ var dirB = CompileDirectiveMetadata.create({
+ selector: '[b]',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type})
+ });
+ expect(humanizeTplAst(parse('
', [dirA, dirB]))).toEqual([
+ [EmbeddedTemplateAst], [DirectiveAst, dirA],
+ [BoundDirectivePropertyAst, 'a', 'b'], [ElementAst, 'div'], [AttrAst, 'b', ''],
+ [DirectiveAst, dirB]
+ ]);
+ });
+
+ it('should not locate directives in variables', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
+ });
+ expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
+ [EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']
+ ]);
+ });
+
+ it('should not locate directives in references', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
+ });
+ expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
+ [ElementAst, 'div'], [ReferenceAst, 'a', null]
+ ]);
+ });
+
+ });
+
+ it('should work with *... and use the attribute name as property binding name', () => {
+ expect(humanizeTplAst(parse('
', [ngIf]))).toEqual([
+ [EmbeddedTemplateAst], [DirectiveAst, ngIf],
+ [BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div']
+ ]);
+ });
+
+ it('should work with *... and empty value', () => {
+ expect(humanizeTplAst(parse('
', [createComp('div', ['*'])])))
+ .toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]);
+ });
+
+ it('should match the element when there is an inline template', () => {
+ expect(humanizeContentProjection(parse('
', [ngIf]))).toEqual([
- [EmbeddedTemplateAst], [DirectiveAst, ngIf],
- [BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div']
- ]);
+ it('should split 2 classes', () => { expect(splitClasses('a b')).toEqual(['a', 'b']); });
+
+ it('should trim classes', () => { expect(splitClasses(' a b ')).toEqual(['a', 'b']); });
});
- it('should report an error on variables declared with #', () => {
- expect(() => humanizeTplAst(parse('
', [])))
- .toThrowError(/Parser Error: Unexpected token # at column 6/);
- });
+ describe('error cases', () => {
+ it('should report when ng-content has content', () => {
+ expect(() => parse('content', []))
+ .toThrowError(`Template parse errors:
+ element cannot have content. must be immediately followed by ("[ERROR ->]content"): TestComp@0:0`);
+ });
- it('should parse variables via let ...', () => {
- expect(humanizeTplAst(parse('
', [
- ]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
- });
+ it('should treat *attr on a template element as valid',
+ () => { expect(() => parse('', [])).not.toThrowError(); });
- describe('directives', () => {
- it('should locate directives in property bindings', () => {
+ it('should treat template attribute on a template element as valid',
+ () => { expect(() => parse('', [])).not.toThrowError(); });
+
+ it('should report when mutliple *attrs are used on the same element', () => {
+ expect(() => parse('
', [])).toThrowError(`Template parse errors:
+Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("
]*ngFor>"): TestComp@0:11`);
+ });
+
+ it('should report when mix of template and *attrs are used on the same element', () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
+Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("]*ngFor>"): TestComp@0:22`);
+ });
+
+ it('should report invalid property names', () => {
+ expect(() => parse('', []))
+ .toThrowError(`Template parse errors:
+Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("
][invalidProp]>
"): TestComp@0:5`);
+ });
+
+ it('should report invalid host property names', () => {
var dirA = CompileDirectiveMetadata.create({
- selector: '[a=b]',
+ selector: 'div',
type: new CompileTypeMetadata(
{moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- inputs: ['a']
+ host: {'[invalidProp]': 'someProp'}
+ });
+ expect(() => parse('', [dirA])).toThrowError(`Template parse errors:
+Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]"): TestComp@0:0, Directive DirA`);
+ });
+
+ it('should report errors in expressions', () => {
+ expect(() => parse('', [])).toThrowError(`Template parse errors:
+Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("
][prop]="a b">
"): TestComp@0:5`);
+ });
+
+ it('should not throw on invalid property names if the property is used by a directive',
+ () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ inputs: ['invalidProp']
+ });
+ expect(() => parse('', [dirA])).not.toThrow();
+ });
+
+ it('should not allow more than 1 component per element', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: 'div',
+ isComponent: true,
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ template: new CompileTemplateMetadata({ngContentSelectors: []})
});
var dirB = CompileDirectiveMetadata.create({
- selector: '[b]',
+ selector: 'div',
+ isComponent: true,
type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type})
+ {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type}),
+ template: new CompileTemplateMetadata({ngContentSelectors: []})
});
- expect(humanizeTplAst(parse('
', [dirA, dirB]))).toEqual([
- [EmbeddedTemplateAst], [DirectiveAst, dirA], [BoundDirectivePropertyAst, 'a', 'b'],
- [ElementAst, 'div'], [AttrAst, 'b', ''], [DirectiveAst, dirB]
- ]);
- });
-
- it('should not locate directives in variables', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
- });
- expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
- [EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']
- ]);
- });
-
- it('should not locate directives in references', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type})
- });
- expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
- [ElementAst, 'div'], [ReferenceAst, 'a', null]
- ]);
- });
-
- });
-
- it('should work with *... and use the attribute name as property binding name', () => {
- expect(humanizeTplAst(parse('
', [ngIf]))).toEqual([
- [EmbeddedTemplateAst], [DirectiveAst, ngIf],
- [BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div']
- ]);
- });
-
- it('should work with *... and empty value', () => {
- expect(humanizeTplAst(parse('
', [ngIf]))).toEqual([
- [EmbeddedTemplateAst], [DirectiveAst, ngIf],
- [BoundDirectivePropertyAst, 'ngIf', 'null'], [ElementAst, 'div']
- ]);
- });
- });
- });
-
- describe('content projection', () => {
- var compCounter: any /** TODO #9100 */;
- beforeEach(() => { compCounter = 0; });
-
- function createComp(
- selector: string, ngContentSelectors: string[]): CompileDirectiveMetadata {
- return CompileDirectiveMetadata.create({
- selector: selector,
- isComponent: true,
- type: new CompileTypeMetadata({
- moduleUrl: someModuleUrl,
- name: `SomeComp${compCounter++}`,
- reference: {} as Type
- }),
- template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors})
- });
- }
-
- function createDir(selector: string): CompileDirectiveMetadata {
- return CompileDirectiveMetadata.create({
- selector: selector,
- type: new CompileTypeMetadata({
- moduleUrl: someModuleUrl,
- name: `SomeDir${compCounter++}`,
- reference: {} as Type
- })
- });
- }
-
- describe('project text nodes', () => {
- it('should project text nodes with wildcard selector', () => {
- expect(humanizeContentProjection(parse('
', [
- createComp('div', ['*'])
- ]))).toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]);
- });
-
- it('should match the element when there is an inline template', () => {
- expect(humanizeContentProjection(parse('
',
- [createComp('div', ['a', 'b']), ngIf])))
- .toEqual([['div', null], ['template', 1], ['a', null]]);
- });
- });
-
- it('should support other directives before the component', () => {
- expect(humanizeContentProjection(parse('
hello
', [
- createDir('div'), createComp('div', ['*'])
- ]))).toEqual([['div', null], ['#text(hello)', 0]]);
- });
- });
-
- describe('splitClasses', () => {
- it('should keep an empty class', () => { expect(splitClasses('a')).toEqual(['a']); });
-
- it('should split 2 classes', () => { expect(splitClasses('a b')).toEqual(['a', 'b']); });
-
- it('should trim classes', () => { expect(splitClasses(' a b ')).toEqual(['a', 'b']); });
- });
-
- describe('error cases', () => {
- it('should report when ng-content has content', () => {
- expect(() => parse('content', []))
- .toThrowError(`Template parse errors:
- element cannot have content. must be immediately followed by ("[ERROR ->]content"): TestComp@0:0`);
- });
-
- it('should treat *attr on a template element as valid',
- () => { expect(() => parse('', [])).not.toThrowError(); });
-
- it('should treat template attribute on a template element as valid',
- () => { expect(() => parse('', [])).not.toThrowError(); });
-
- it('should report when mutliple *attrs are used on the same element', () => {
- expect(() => parse('
', [])).toThrowError(`Template parse errors:
-Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("
]*ngFor>"): TestComp@0:11`);
- });
-
- it('should report when mix of template and *attrs are used on the same element', () => {
- expect(() => parse('', []))
- .toThrowError(`Template parse errors:
-Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("]*ngFor>"): TestComp@0:22`);
- });
-
- it('should report invalid property names', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
-Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("
][invalidProp]>
"): TestComp@0:5`);
- });
-
- it('should report invalid host property names', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: 'div',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- host: {'[invalidProp]': 'someProp'}
- });
- expect(() => parse('', [dirA])).toThrowError(`Template parse errors:
-Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]"): TestComp@0:0, Directive DirA`);
- });
-
- it('should report errors in expressions', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
-Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("
][prop]="a b">
"): TestComp@0:5`);
- });
-
- it('should not throw on invalid property names if the property is used by a directive',
- () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: 'div',
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- inputs: ['invalidProp']
- });
- expect(() => parse('', [dirA])).not.toThrow();
- });
-
- it('should not allow more than 1 component per element', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: 'div',
- isComponent: true,
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- });
- var dirB = CompileDirectiveMetadata.create({
- selector: 'div',
- isComponent: true,
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- });
- expect(() => parse('
', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirB,DirA ("[ERROR ->]
"): TestComp@0:0`);
- });
+ });
- it('should not allow components or element bindings nor dom events on explicit embedded templates',
- () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- isComponent: true,
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- });
- expect(() => parse('', [dirA]))
- .toThrowError(`Template parse errors:
+ it('should not allow components or element bindings nor dom events on explicit embedded templates',
+ () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ isComponent: true,
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ template: new CompileTemplateMetadata({ngContentSelectors: []})
+ });
+ expect(() => parse('', [dirA]))
+ .toThrowError(`Template parse errors:
Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "directives" section. ("](e)="f">"): TestComp@0:18
Components on an embedded template: DirA ("[ERROR ->]"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section. ("[ERROR ->]"): TestComp@0:0`);
- });
+ });
- it('should not allow components or element bindings on inline embedded templates', () => {
- var dirA = CompileDirectiveMetadata.create({
- selector: '[a]',
- isComponent: true,
- type: new CompileTypeMetadata(
- {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- });
- expect(() => parse('', [dirA])).toThrowError(`Template parse errors:
+ it('should not allow components or element bindings on inline embedded templates', () => {
+ var dirA = CompileDirectiveMetadata.create({
+ selector: '[a]',
+ isComponent: true,
+ type: new CompileTypeMetadata(
+ {moduleUrl: someModuleUrl, name: 'DirA', reference: {} as Type}),
+ template: new CompileTemplateMetadata({ngContentSelectors: []})
+ });
+ expect(() => parse('', [dirA])).toThrowError(`Template parse errors:
Components on an embedded template: DirA ("[ERROR ->]"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section. ("[ERROR ->]"): TestComp@0:0`);
- });
- });
-
- describe('ignore elements', () => {
- it('should ignore a', []))).toEqual([[TextAst, 'a']]);
-
- });
-
- it('should ignore a', []))).toEqual([[TextAst, 'a']]);
- });
-
- describe('', () => {
-
- it('should keep elements if they have an absolute non package: url',
- () => {
- expect(humanizeTplAst(parse('a', [])))
- .toEqual([
- [ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'],
- [AttrAst, 'href', 'http://someurl'], [TextAst, 'a']
- ]);
- });
-
- it('should keep elements if they have no uri', () => {
- expect(humanizeTplAst(parse('a', [
- ]))).toEqual([[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], [TextAst, 'a']]);
- expect(humanizeTplAst(parse('a', [
- ]))).toEqual([[ElementAst, 'link'], [AttrAst, 'REL', 'stylesheet'], [TextAst, 'a']]);
+ });
});
- it('should ignore elements if they have a relative uri', () => {
- expect(humanizeTplAst(parse('a', [
- ]))).toEqual([[TextAst, 'a']]);
- expect(humanizeTplAst(parse('a', [
- ]))).toEqual([[TextAst, 'a']]);
+ describe('ignore elements', () => {
+ it('should ignore a', []))).toEqual([[TextAst, 'a']]);
+
+ });
+
+ it('should ignore a', []))).toEqual([[TextAst, 'a']]);
+ });
+
+ describe('', () => {
+
+ it('should keep elements if they have an absolute non package: url',
+ () => {
+ expect(humanizeTplAst(parse('a', [])))
+ .toEqual([
+ [ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'],
+ [AttrAst, 'href', 'http://someurl'], [TextAst, 'a']
+ ]);
+ });
+
+ it('should keep elements if they have no uri', () => {
+ expect(humanizeTplAst(parse('a', [
+ ]))).toEqual([[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], [TextAst, 'a']]);
+ expect(humanizeTplAst(parse('a', [
+ ]))).toEqual([[ElementAst, 'link'], [AttrAst, 'REL', 'stylesheet'], [TextAst, 'a']]);
+ });
+
+ it('should ignore elements if they have a relative uri', () => {
+ expect(humanizeTplAst(parse('a', [
+ ]))).toEqual([[TextAst, 'a']]);
+ expect(humanizeTplAst(parse('a', [
+ ]))).toEqual([[TextAst, 'a']]);
+ });
+
+ it('should ignore elements if they have a package: uri', () => {
+ expect(humanizeTplAst(parse('a', [
+ ]))).toEqual([[TextAst, 'a']]);
+ });
+
+ });
+
+ it('should ignore bindings on children of elements with ngNonBindable', () => {
+ expect(humanizeTplAst(parse('
{{b}}
', [
+ ]))).toEqual([[ElementAst, 'div'], [AttrAst, 'ngNonBindable', ''], [TextAst, '{{b}}']]);
+ });
+
+ it('should keep nested children of elements with ngNonBindable', () => {
+ expect(humanizeTplAst(parse('