', []))).toEqual([
+ [ElementAst, 'div'],
+ [BoundElementPropertyAst, PropertyBindingType.Style, 'someStyle', 'v', null]
+ ]);
+ });
+
+ 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 throw error when binding to an invalid property', () => {
- expect(() => parse('', []))
- .toThrowError(`Template parse errors:
+ 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 throw error when binding to an invalid attribute', () => {
- expect(() => parse('', []))
- .toThrowError(`Template parse errors:
+ 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- 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- 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 {{...}} and not report them as attributes',
- () => {
- expect(humanizeTplAst(parse('
', []))).toEqual([
+ it('should parse bound properties via bind-animate- and not report them as attributes',
+ () => {
+ expect(humanizeTplAst(parse('
', [], [], [])))
+ .toEqual([
[ElementAst, 'div'],
[
- BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null
+ BoundElementPropertyAst, PropertyBindingType.Animation, 'someAnimation',
+ 'value2', null
]
]);
- });
-
- 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 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 not issue a warning when host attributes contain a valid property-bound animation trigger',
- () => {
- const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'div',
- template: new CompileTemplateMetadata({animations: animationEntries}),
- type: createTypeMeta({
- reference: {filePath: someModuleUrl, name: 'DirA'},
- }),
- host: {'[@prop]': 'expr'}
- })
- .toSummary();
-
- humanizeTplAst(parse('', [dirA]));
- expect(console.warnings.length).toEqual(0);
- });
-
- it('should throw descriptive error when a host binding is not a string expression', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'broken',
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- host: {'[class.foo]': null}
- })
- .toSummary();
-
- 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 event is not a string expression', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'broken',
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- host: {'(click)': null}
- })
- .toSummary();
-
- 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(
+ /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 not issue a warning when host attributes contain a valid property-bound animation trigger',
+ () => {
+ const animationEntries = [new CompileAnimationEntryMetadata('prop', [])];
+ const dirA =
+ CompileDirectiveMetadata
+ .create({
+ selector: 'div',
+ template: new CompileTemplateMetadata({animations: animationEntries}),
+ type: createTypeMeta({
+ reference: {filePath: someModuleUrl, name: 'DirA'},
+ }),
+ host: {'[@prop]': 'expr'}
+ })
+ .toSummary();
+
+ humanizeTplAst(parse('', [dirA]));
+ expect(console.warnings.length).toEqual(0);
+ });
+
+ it('should throw descriptive error when a host binding is not a string expression', () => {
+ const dirA =
+ CompileDirectiveMetadata
+ .create({
+ selector: 'broken',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
+ host: {'[class.foo]': null}
+ })
+ .toSummary();
- describe('events', () => {
+ 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 parse bound events with a target', () => {
- 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('
', [dirA, dirB]))
- .toThrowError(
- `Template parse errors:\n` +
- `Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]
', [
- ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'someA', null]]);
- });
-
- it('should assign references with empty value to the element', () => {
- expect(humanizeTplAst(parse('', [
- ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
- });
-
- it('should assign references to directives via exportAs', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: '[a]',
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- exportAs: 'dirA'
- })
- .toSummary();
- expect(humanizeTplAst(parse('', [dirA]))).toEqual([
- [ElementAst, 'div'],
- [AttrAst, 'a', ''],
- [ReferenceAst, 'a', identifierToken(dirA.type)],
- [DirectiveAst, dirA],
- ]);
- });
+ 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 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 report references with values that dont match a directive as errors', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
-There is no directive with "exportAs" set to "dirA" ("
]#a="dirA">
"): TestComp@0:5`);
- });
+ });
- it('should report invalid reference names', () => {
- expect(() => parse('', [])).toThrowError(`Template parse errors:
-"-" is not allowed in reference names ("
]#a-b>
"): TestComp@0:5`);
- });
+ describe('directives', () => {
+ it('should order directives by the directives array in the View and match them only once',
+ () => {
+ const dirA =
+ CompileDirectiveMetadata
+ .create({
+ selector: '[a]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}})
+ })
+ .toSummary();
+ const dirB =
+ CompileDirectiveMetadata
+ .create({
+ selector: '[b]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}})
+ })
+ .toSummary();
+ const dirC =
+ CompileDirectiveMetadata
+ .create({
+ selector: '[c]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirC'}})
+ })
+ .toSummary();
+ expect(humanizeTplAst(parse('
', [dirA]))).toEqual([
+ [ElementAst, 'div'], [BoundEventAst, 'a', null, 'b'], [DirectiveAst, dirA]
+ ]);
+ });
- });
-
- it('should not throw error when there is same reference name in different templates',
- () => {
- expect(() => parse('
', [dirA]))).toEqual([
- [ElementAst, 'div'], [ReferenceAst, 'a', null]
- ]);
- });
-
- });
-
- it('should work with *... and use the attribute name as property binding name', () => {
- expect(humanizeTplAst(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 match the element when there is an inline template', () => {
- expect(humanizeContentProjection(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);
});
- describe('error cases', () => {
- it('should report when ng-content has non WS content', () => {
- expect(() => parse('content', []))
- .toThrowError(
- `Template parse errors:\n` +
- ` element cannot have content. ("[ERROR ->]content"): TestComp@0:0`);
- });
+ it('should report missing @Self() deps as errors', () => {
+ const dirA = createDir('[dirA]', {deps: ['self:provider0']});
+ expect(() => parse('', [dirA]))
+ .toThrowError(
+ 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
+ });
- it('should treat *attr on a template element as valid',
- () => { expect(() => parse('', [])).not.toThrowError(); });
+ it('should change missing @Self() that are optional to nulls', () => {
+ const dirA = createDir('[dirA]', {deps: ['optional:self:provider0']});
+ const 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 treat template attribute on a template element as valid',
- () => { expect(() => parse('', [])).not.toThrowError(); });
+ it('should report missing @Host() deps as errors', () => {
+ const dirA = createDir('[dirA]', {deps: ['host:provider0']});
+ expect(() => parse('', [dirA]))
+ .toThrowError(
+ 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]"): TestComp@0:0');
+ });
- 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 change missing @Host() that are optional to nulls', () => {
+ const dirA = createDir('[dirA]', {deps: ['optional:host:provider0']});
+ const 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 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`);
- });
+ describe('references', () => {
- 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 parse references via #... and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
+ });
- it('should report invalid host property names', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'div',
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- host: {'[invalidProp]': 'someProp'}
- })
- .toSummary();
- 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 parse references via ref-... and not report them as attributes', () => {
+ expect(humanizeTplAst(parse('
', [
+ ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
+ });
- 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 ("
', [
+ ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'someA', null]]);
+ });
- it('should not throw on invalid property names if the property is used by a directive',
- () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'div',
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- inputs: ['invalidProp']
- })
- .toSummary();
- expect(() => parse('', [dirA])).not.toThrow();
- });
-
- it('should not allow more than 1 component per element', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: 'div',
- isComponent: true,
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- })
- .toSummary();
- const dirB =
- CompileDirectiveMetadata
- .create({
- selector: 'div',
- isComponent: true,
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirB'}}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- })
- .toSummary();
- expect(() => parse('
', [dirB, dirA]))
- .toThrowError(
- `Template parse errors:\n` +
- `More than one component matched on this element.\n` +
- `Make sure that only one component's selector can match a given element.\n` +
- `Conflicting components: DirB,DirA ("[ERROR ->]
"): TestComp@0:0`);
- });
+ it('should assign references with empty value to the element', () => {
+ expect(humanizeTplAst(parse('', [
+ ]))).toEqual([[ElementAst, 'div'], [ReferenceAst, 'a', null]]);
+ });
- it('should not allow components or element bindings nor dom events on explicit embedded templates',
- () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: '[a]',
- isComponent: true,
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- })
- .toSummary();
- 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 "@NgModule.declarations". ("](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 "@NgModule.declarations". ("[ERROR ->]"): TestComp@0:0`);
- });
+ it('should assign references to directives via exportAs', () => {
+ const dirA =
+ CompileDirectiveMetadata
+ .create({
+ selector: '[a]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
+ exportAs: 'dirA'
+ })
+ .toSummary();
+ expect(humanizeTplAst(parse('', [dirA]))).toEqual([
+ [ElementAst, 'div'],
+ [AttrAst, 'a', ''],
+ [ReferenceAst, 'a', identifierToken(dirA.type)],
+ [DirectiveAst, dirA],
+ ]);
+ });
- it('should not allow components or element bindings on inline embedded templates', () => {
- const dirA =
- CompileDirectiveMetadata
- .create({
- selector: '[a]',
- isComponent: true,
- type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
- template: new CompileTemplateMetadata({ngContentSelectors: []})
- })
- .toSummary();
- 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 "@NgModule.declarations". ("[ERROR ->]"): TestComp@0:0`);
- });
+ it('should report references with values that dont match a directive as errors', () => {
+ expect(() => parse('', [])).toThrowError(`Template parse errors:
+There is no directive with "exportAs" set to "dirA" ("