/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {AttributeMarker} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; /** * These tests are codified version of the tests in compiler_canonical_spec.ts. Every * test in compiler_canonical_spec.ts should have a corresponding test here. */ describe('compiler compliance', () => { const angularFiles = setup({ compileAngular: false, compileAnimations: false, compileFakeCore: true, }); describe('elements', () => { it('should handle SVG', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`

test

\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The factory should look like this: const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … consts: [["title", "Hello", ${AttributeMarker.Classes}, "my-app"], ["cx", "20", "cy", "30", "r", "50"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 0); $r3$.ɵɵnamespaceSVG(); $r3$.ɵɵelementStart(1, "svg"); $r3$.ɵɵelement(2, "circle", 1); $r3$.ɵɵelementEnd(); $r3$.ɵɵnamespaceHTML(); $r3$.ɵɵelementStart(3, "p"); $r3$.ɵɵtext(4, "test"); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementEnd(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should handle MathML', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`

test

\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The factory should look like this: const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … consts: [["title", "Hello", ${AttributeMarker.Classes}, "my-app"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 0); $r3$.ɵɵnamespaceMathML(); $r3$.ɵɵelementStart(1, "math"); $r3$.ɵɵelement(2, "infinity"); $r3$.ɵɵelementEnd(); $r3$.ɵɵnamespaceHTML(); $r3$.ɵɵelementStart(3, "p"); $r3$.ɵɵtext(4, "test"); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementEnd(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should translate DOM structure', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
Hello World!
\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The factory should look like this: const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … consts: [["title", "Hello", ${AttributeMarker.Classes}, "my-app"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 0); $r3$.ɵɵtext(1, "Hello "); $r3$.ɵɵelementStart(2, "b"); $r3$.ɵɵtext(3, "World"); $r3$.ɵɵelementEnd(); $r3$.ɵɵtext(4, "!"); $r3$.ɵɵelementEnd(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); // TODO(https://github.com/angular/angular/issues/24426): We need to support the parser actually // building the proper attributes based off of xmlns attributes. xit('should support namespaced attributes', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
Hello World!
\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The factory should look like this: const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … consts: [["class", "my-app", 0, "http://someuri/foo", "foo:bar", "baz", "title", "Hello", 0, "http://someuri/foo", "foo:qux", "quacks"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 0); $r3$.ɵɵtext(1, "Hello "); $r3$.ɵɵelementStart(2, "b"); $r3$.ɵɵtext(3, "World"); $r3$.ɵɵelementEnd(); $r3$.ɵɵtext(4, "!"); $r3$.ɵɵelementEnd(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should support ', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`in a container\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementContainerStart(0); i0.ɵɵelementStart(1, "span"); i0.ɵɵtext(2, "in a "); i0.ɵɵelementEnd(); i0.ɵɵtext(3, "container"); i0.ɵɵelementContainerEnd(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should generate self-closing elementContainer instruction for empty ', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementContainer(0); } } `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should bind to element properties', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
\` }) export class MyComponent { id = 'one'; } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` … consts: [[${AttributeMarker.Bindings}, "id"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", 0); } if (rf & 2) { $r3$.ɵɵproperty("id", ctx.id); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should reserve slots for pure functions', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
\` }) export class MyComponent { id = 'one'; } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; /////////////// // TODO(FW-1273): The code generated below is adding extra parens, and we need to stop // generating those. // // For example: // `$r3$.ɵɵproperty("ternary", (ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$));` /////////////// const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", 0); $r3$.ɵɵpipe(1, "pipe"); } if (rf & 2) { $r3$.ɵɵproperty("ternary", ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $r3$.ɵɵpureFunction0(10, $c1$))("pipe", $r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2))("and", ctx.cond && $r3$.ɵɵpureFunction1(11, $c0$, ctx.b))("or", ctx.cond || $r3$.ɵɵpureFunction1(13, $c0$, ctx.c)); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should reserve slots for pure functions in host binding function', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Input} from '@angular/core'; @Component({ selector: 'my-component', template: '...', host: { '[@expansionHeight]': \`{ value: getExpandedState(), params: { collapsedHeight: collapsedHeight, expandedHeight: expandedHeight } }\`, '[@expansionWidth]': \`{ value: getExpandedState(), params: { collapsedWidth: collapsedWidth, expandedWidth: expandedWidth } }\` } }) export class MyComponent { @Input() expandedHeight: string; @Input() collapsedHeight: string; @Input() expandedWidth: string; @Input() collapsedWidth: string; getExpandedState() { return 'expanded'; } } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const hostBindingsDef = ` const $_c0$ = function (a0, a1) { return { collapsedHeight: a0, expandedHeight: a1 }; }; const $_c1$ = function (a0, a1) { return { value: a0, params: a1 }; }; const $_c2$ = function (a0, a1) { return { collapsedWidth: a0, expandedWidth: a1 }; }; … hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { if (rf & 1) { $r3$.ɵɵallocHostVars(14); } if (rf & 2) { $r3$.ɵɵupdateSyntheticHostBinding("@expansionHeight", $r3$.ɵɵpureFunction2(5, $_c1$, ctx.getExpandedState(), $r3$.ɵɵpureFunction2(2, $_c0$, ctx.collapsedHeight, ctx.expandedHeight) ) )("@expansionWidth", $r3$.ɵɵpureFunction2(11, $_c1$, ctx.getExpandedState(), $r3$.ɵɵpureFunction2(8, $_c2$, ctx.collapsedWidth, ctx.expandedWidth) ) ); } }, … `; const result = compile(files, angularFiles); expectEmit(result.source, hostBindingsDef, 'Incorrect "hostBindings" function'); }); it('should bind to class and style names', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
\` }) export class MyComponent { error = true; color = 'red'; } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const factory = 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; const template = ` MyComponent.ɵcmp = i0.ɵɵdefineComponent({type:MyComponent,selectors:[["my-component"]], decls: 1, vars: 2, template: function MyComponent_Template(rf,ctx){ if (rf & 1) { $r3$.ɵɵelement(0, "div"); } if (rf & 2) { $r3$.ɵɵstyleProp("background-color", ctx.color); $r3$.ɵɵclassProp("error", ctx.error); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); expectEmit(result.source, template, 'Incorrect template'); }); it('should de-duplicate attribute arrays', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
\` }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` … consts: [["title", "hi"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", 0); $r3$.ɵɵelement(1, "span", 0); } … } `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); }); describe('components & directives', () => { it('should instantiate directives', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule} from '@angular/core'; @Component({selector: 'child', template: 'child-view'}) export class ChildComponent {} @Directive({selector: '[some-directive]'}) export class SomeDirective {} @Component({selector: 'my-component', template: '!'}) export class MyComponent {} @NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]}) export class MyModule{} ` } }; // ChildComponent definition should be: const ChildComponentDefinition = ` ChildComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ChildComponent, selectors: [["child"]], decls: 1, vars: 0, template: function ChildComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0, "child-view"); } }, encapsulation: 2 });`; const ChildComponentFactory = `ChildComponent.ɵfac = function ChildComponent_Factory(t) { return new (t || ChildComponent)(); };`; // SomeDirective definition should be: const SomeDirectiveDefinition = ` SomeDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: SomeDirective, selectors: [["", "some-directive", ""]] }); `; const SomeDirectiveFactory = `SomeDirective.ɵfac = function SomeDirective_Factory(t) {return new (t || SomeDirective)(); };`; // MyComponent definition should be: const MyComponentDefinition = ` … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 2, vars: 0, consts: [["some-directive", ""]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "child", 0); $r3$.ɵɵtext(1, "!"); } }, directives: [ChildComponent, SomeDirective], encapsulation: 2 }); `; const MyComponentFactory = `MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ɵcmp'); expectEmit(source, ChildComponentFactory, 'Incorrect ChildComponent.ɵfac'); expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); expectEmit(source, SomeDirectiveFactory, 'Incorrect SomeDirective.ɵfac'); expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ɵcmp'); expectEmit(source, MyComponentFactory, 'Incorrect MyComponentDefinition.ɵfac'); }); it('should support complex selectors', () => { const files = { app: { 'spec.ts': ` import {Directive, NgModule} from '@angular/core'; @Directive({selector: 'div.foo[some-directive]:not([title]):not(.baz)'}) export class SomeDirective {} @Directive({selector: ':not(span[title]):not(.baz)'}) export class OtherDirective {} @NgModule({declarations: [SomeDirective, OtherDirective]}) export class MyModule{} ` } }; // SomeDirective definition should be: const SomeDirectiveDefinition = ` SomeDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: SomeDirective, selectors: [["div", "some-directive", "", 8, "foo", 3, "title", "", 9, "baz"]] }); `; const SomeDirectiveFactory = `SomeDirective.ɵfac = function SomeDirective_Factory(t) {return new (t || SomeDirective)(); };`; // OtherDirective definition should be: const OtherDirectiveDefinition = ` OtherDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: OtherDirective, selectors: [["", 5, "span", "title", "", 9, "baz"]] }); `; const OtherDirectiveFactory = `OtherDirective.ɵfac = function OtherDirective_Factory(t) {return new (t || OtherDirective)(); };`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); expectEmit(source, SomeDirectiveFactory, 'Incorrect SomeDirective.ɵfac'); expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ɵdir'); expectEmit(source, OtherDirectiveFactory, 'Incorrect OtherDirective.ɵfac'); }); it('should convert #my-app selector to ["", "id", "my-app"]', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({selector: '#my-app', template: ''}) export class SomeComponent {} @NgModule({declarations: [SomeComponent]}) export class MyModule{} ` } }; // SomeDirective definition should be: const SomeDirectiveDefinition = ` SomeComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: SomeComponent, selectors: [["", "id", "my-app"]], … }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeComponent.ɵcomp'); }); it('should support components without selector', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule} from '@angular/core'; @Component({template: ''}) export class EmptyOutletComponent {} @NgModule({declarations: [EmptyOutletComponent]}) export class MyModule{} ` } }; // EmptyOutletComponent definition should be: const EmptyOutletComponentDefinition = ` … EmptyOutletComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: EmptyOutletComponent, selectors: [["ng-component"]], decls: 1, vars: 0, template: function EmptyOutletComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "router-outlet"); } }, encapsulation: 2 }); `; const EmptyOutletComponentFactory = `EmptyOutletComponent.ɵfac = function EmptyOutletComponent_Factory(t) { return new (t || EmptyOutletComponent)(); };`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ɵcmp'); expectEmit(source, EmptyOutletComponentFactory, 'Incorrect EmptyOutletComponent.ɵfac'); }); it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, ElementRef, ChangeDetectorRef, ViewContainerRef} from '@angular/core'; @Component({ selector: 'my-component', template: '' }) export class MyComponent { constructor(public el: ElementRef, public vcr: ViewContainerRef, public cdr: ChangeDetectorRef) {} } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const MyComponentDefinition = ` … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 0, vars: 0, template: function MyComponent_Template(rf, ctx) {}, encapsulation: 2 });`; const MyComponentFactory = `MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)( $r3$.ɵɵdirectiveInject($i$.ElementRef), $r3$.ɵɵdirectiveInject($i$.ViewContainerRef), $r3$.ɵɵdirectiveInject($i$.ChangeDetectorRef)); };`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac'); }); it('should support structural directives', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; @Directive({selector: '[if]'}) export class IfDirective { constructor(template: TemplateRef) { } } @Component({ selector: 'my-component', template: '
  • {{salutation}} {{foo}}
' }) export class MyComponent { salutation = 'Hello'; } @NgModule({declarations: [IfDirective, MyComponent]}) export class MyModule {} ` } }; const IfDirectiveDefinition = ` IfDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: IfDirective, selectors: [["", "if", ""]] });`; const IfDirectiveFactory = `IfDirective.ɵfac = function IfDirective_Factory(t) { return new (t || IfDirective)($r3$.ɵɵdirectiveInject($i$.TemplateRef)); };`; const MyComponentDefinition = ` function MyComponent_li_2_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "li"); $r3$.ɵɵtext(1); $r3$.ɵɵelementEnd(); } if (rf & 2) { const $myComp$ = $r3$.ɵɵnextContext(); const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵadvance(1); $r3$.ɵɵtextInterpolate2("", $myComp$.salutation, " ", $foo$, ""); } } … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 3, vars: 0, consts: [["foo", ""], [${AttributeMarker.Template}, "if"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "ul", null, 0); $r3$.ɵɵtemplate(2, MyComponent_li_2_Template, 2, 2, "li", 1); $r3$.ɵɵelementEnd(); } }, directives: [IfDirective], encapsulation: 2 });`; const MyComponentFactory = `MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ɵdir'); expectEmit(source, IfDirectiveFactory, 'Incorrect IfDirective.ɵfac'); expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac'); }); describe('value composition', () => { it('should support array literals', () => { const files = { app: { 'spec.ts': ` import {Component, Input, NgModule} from '@angular/core'; @Component({ selector: 'my-comp', template: \`

{{ names[0] }}

{{ names[1] }}

\` }) export class MyComp { @Input() names: string[]; } @Component({ selector: 'my-app', template: \` \` }) export class MyApp { customName = 'Bess'; } @NgModule({declarations: [MyComp, MyApp]}) export class MyModule { } ` } }; const MyAppDeclaration = ` const $e0_ff$ = function ($v$) { return ["Nancy", $v$]; }; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 1, vars: 3, consts: [[${AttributeMarker.Bindings}, "names"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "my-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("names", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.customName)); } }, directives: [MyComp], encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDeclaration, 'Invalid array emit'); }); it('should support 9+ bindings in array literals', () => { const files = { app: { 'spec.ts': ` import {Component, Input, NgModule} from '@angular/core'; @Component({ selector: 'my-comp', template: \` {{ names[0] }} {{ names[1] }} {{ names[3] }} {{ names[4] }} {{ names[5] }} {{ names[6] }} {{ names[7] }} {{ names[8] }} {{ names[9] }} {{ names[10] }} {{ names[11] }} \` }) export class MyComp { @Input() names: string[]; } @Component({ selector: 'my-app', template: \` \` }) export class MyApp { n0 = 'a'; n1 = 'b'; n2 = 'c'; n3 = 'd'; n4 = 'e'; n5 = 'f'; n6 = 'g'; n7 = 'h'; n8 = 'i'; } @NgModule({declarations: [MyComp, MyApp]}) export class MyModule {} ` } }; const MyAppDefinition = ` const $e0_ff$ = function ($v0$, $v1$, $v2$, $v3$, $v4$, $v5$, $v6$, $v7$, $v8$) { return ["start-", $v0$, $v1$, $v2$, $v3$, $v4$, "-middle-", $v5$, $v6$, $v7$, $v8$, "-end"]; } … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 1, vars: 11, consts: [[${AttributeMarker.Bindings}, "names"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "my-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("names", $r3$.ɵɵpureFunctionV(1, $e0_ff$, [ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8])); } }, directives: [MyComp], encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid array binding'); }); it('should support object literals', () => { const files = { app: { 'spec.ts': ` import {Component, Input, NgModule} from '@angular/core'; @Component({ selector: 'object-comp', template: \`

{{ config['duration'] }}

{{ config.animation }}

\` }) export class ObjectComp { @Input() config: {[key: string]: any}; } @Component({ selector: 'my-app', template: \` \` }) export class MyApp { name = 'slide'; } @NgModule({declarations: [ObjectComp, MyApp]}) export class MyModule {} ` } }; const MyAppDefinition = ` const $e0_ff$ = function ($v$) { return {"duration": 500, animation: $v$}; }; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 1, vars: 3, consts: [[${AttributeMarker.Bindings}, "config"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "object-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("config", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.name)); } }, directives: [ObjectComp], encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid object literal binding'); }); it('should support expressions nested deeply in object/array literals', () => { const files = { app: { 'spec.ts': ` import {Component, Input, NgModule} from '@angular/core'; @Component({ selector: 'nested-comp', template: \`

{{ config.animation }}

{{config.actions[0].opacity }}

{{config.actions[1].duration }}

\` }) export class NestedComp { @Input() config: {[key: string]: any}; } @Component({ selector: 'my-app', template: \` \` }) export class MyApp { name = 'slide'; duration = 100; } @NgModule({declarations: [NestedComp, MyApp]}) export class MyModule {} ` } }; const MyAppDefinition = ` const $c0$ = function () { return {opacity: 0, duration: 0}; }; const $e0_ff$ = function ($v$) { return {opacity: 1, duration: $v$}; }; const $e0_ff_1$ = function ($v1$, $v2$) { return [$v1$, $v2$]; }; const $e0_ff_2$ = function ($v1$, $v2$) { return {animation: $v1$, actions: $v2$}; }; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 1, vars: 10, consts: [[${AttributeMarker.Bindings}, "config"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "nested-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("config", $r3$.ɵɵpureFunction2(7, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction2(4, $e0_ff_1$, $r3$.ɵɵpureFunction0(1, $c0$), $r3$.ɵɵpureFunction1(2, $e0_ff$, ctx.duration)))); } }, directives: [NestedComp], encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid array/object literal binding'); }); }); describe('content projection', () => { it('should support content projection in root template', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; @Component({selector: 'simple', template: '
'}) export class SimpleComponent {} @Component({ selector: 'complex', template: \`
\` }) export class ComplexComponent { } @NgModule({declarations: [SimpleComponent, ComplexComponent]}) export class MyModule {} @Component({ selector: 'my-app', template: 'content ' }) export class MyApp {} ` } }; const SimpleComponentDefinition = ` SimpleComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: SimpleComponent, selectors: [["simple"]], ngContentSelectors: $c0$, decls: 2, vars: 0, template: function SimpleComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef(); $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵprojection(1); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 });`; const ComplexComponentDefinition = ` const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]]; … ComplexComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ComplexComponent, selectors: [["complex"]], ngContentSelectors: $c2$, decls: 4, vars: 0, consts: [["id","first"], ["id","second"]], template: function ComplexComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef($c1$); $r3$.ɵɵelementStart(0, "div", 0); $r3$.ɵɵprojection(1); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementStart(2, "div", 1); $r3$.ɵɵprojection(3, 1); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit( result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); expectEmit( result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition'); }); it('should support multi-slot content projection with multiple wildcard slots', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ template: \` \`, }) class Cmp {} @NgModule({ declarations: [Cmp] }) class Module {} `, } }; const output = ` const $c0$ = ["*", [["", "spacer", ""]], "*"]; const $c1$ = ["*", "[spacer]", "*"]; … Cmp.ɵcmp = $r3$.ɵɵdefineComponent({ type: Cmp, selectors: [["ng-component"]], ngContentSelectors: $c1$, decls: 3, vars: 0, template: function Cmp_Template(rf, ctx) { if (rf & 1) { i0.ɵɵprojectionDef($c0$); i0.ɵɵprojection(0); i0.ɵɵprojection(1, 1); i0.ɵɵprojection(2, 2); } }, encapsulation: 2 }); `; const {source} = compile(files, angularFiles); expectEmit(source, output, 'Invalid content projection instructions generated'); }); it('should support content projection in nested templates', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ template: \`
No ng-content, no instructions generated.
'*' selector: \`, }) class Cmp {} @NgModule({ declarations: [Cmp] }) class Module {} ` } }; const output = ` function Cmp_div_0_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 2); $r3$.ɵɵprojection(1); $r3$.ɵɵelementEnd(); } } function Cmp_div_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", 3); $r3$.ɵɵtext(1, " No ng-content, no instructions generated. "); $r3$.ɵɵelementEnd(); } } function Cmp_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0, " '*' selector: "); $r3$.ɵɵprojection(1, 1); } } const $_c4$ = [[["span", "title", "tofirst"]], "*"]; … consts: [["id", "second", ${AttributeMarker.Template}, "ngIf"], ["id", "third", ${AttributeMarker.Template}, "ngIf"], ["id", "second"], ["id", "third"]], template: function Cmp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef($_c4$); $r3$.ɵɵtemplate(0, Cmp_div_0_Template, 2, 0, "div", 0); $r3$.ɵɵtemplate(1, Cmp_div_1_Template, 2, 0, "div", 1); $r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template"); } if (rf & 2) { $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵadvance(1); $r3$.ɵɵproperty("ngIf", ctx.visible); } } `; const {source} = compile(files, angularFiles); expectEmit(source, output, 'Invalid content projection instructions generated'); }); it('should support content projection in both the root and nested templates', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ template: \` '*' selector in a template: \`, }) class Cmp {} @NgModule({ declarations: [Cmp] }) class Module {} ` } }; const output = ` function Cmp_ng_template_1_ng_template_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojection(0, 3); } } function Cmp_ng_template_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojection(0, 2); $r3$.ɵɵtemplate(1, Cmp_ng_template_1_ng_template_1_Template, 1, 0, "ng-template"); } } function Cmp_ng_template_2_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0, " '*' selector in a template: "); $r3$.ɵɵprojection(1, 4); } } const $_c0$ = [[["", "id", "tomainbefore"]], [["", "id", "tomainafter"]], [["", "id", "totemplate"]], [["", "id", "tonestedtemplate"]], "*"]; const $_c1$ = ["[id=toMainBefore]", "[id=toMainAfter]", "[id=toTemplate]", "[id=toNestedTemplate]", "*"]; … template: function Cmp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef($_c0$); $r3$.ɵɵprojection(0); $r3$.ɵɵtemplate(1, Cmp_ng_template_1_Template, 2, 0, "ng-template"); $r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template"); $r3$.ɵɵprojection(3, 1); } } `; const {source} = compile(files, angularFiles); expectEmit(source, output, 'Invalid content projection instructions generated'); }); it('should parse the selector that is passed into ngProjectAs', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'simple', template: '
' }) export class SimpleComponent {} @NgModule({declarations: [SimpleComponent]}) export class MyModule {} @Component({ selector: 'my-app', template: '

' }) export class MyApp {} ` } }; // Note that the c0 and c1 constants aren't being used in this particular test, // but they are used in some of the logic that is folded under the ellipsis. const SimpleComponentDefinition = ` const $_c0$ = [[["", "title", ""]]]; const $_c1$ = ["[title]"]; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 2, vars: 0, consts: [["ngProjectAs", "[title]", 5, ["", "title", ""]]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "simple"); $r3$.ɵɵelement(1, "h1", 0); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 })`; const result = compile(files, angularFiles); expectEmit( result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); }); it('should take the first selector if multiple values are passed into ngProjectAs', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'simple', template: '
' }) export class SimpleComponent {} @NgModule({declarations: [SimpleComponent]}) export class MyModule {} @Component({ selector: 'my-app', template: '

' }) export class MyApp {} ` } }; // Note that the c0 and c1 constants aren't being used in this particular test, // but they are used in some of the logic that is folded under the ellipsis. const SimpleComponentDefinition = ` const $_c0$ = [[["", "title", ""]]]; const $_c1$ = ["[title]"]; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 2, vars: 0, consts: [["ngProjectAs", "[title],[header]", 5, ["", "title", ""]]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "simple"); $r3$.ɵɵelement(1, "h1", 0); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 })`; const result = compile(files, angularFiles); expectEmit( result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); }); it('should include parsed ngProjectAs selectors into template attrs', () => { const files = { app: { 'spec.ts': ` import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: '
' }) export class MyApp { show = true; } ` } }; const SimpleComponentDefinition = ` MyApp.ɵcmp = i0.ɵɵdefineComponent({ type: MyApp, selectors: [ ["my-app"] ], decls: 1, vars: 1, consts: [ ["ngProjectAs", ".someclass", ${AttributeMarker.ProjectAs}, ["", 8, "someclass"], ${AttributeMarker.Template}, "ngIf"], ["ngProjectAs", ".someclass", ${AttributeMarker.ProjectAs}, ["", 8, "someclass"]] ], template: function MyApp_Template(rf, ctx) { if (rf & 1) { i0.ɵɵtemplate(0, MyApp_div_0_Template, 1, 0, "div", 0); } if (rf & 2) { i0.ɵɵproperty("ngIf", ctx.show); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, SimpleComponentDefinition, 'Incorrect MyApp definition'); }); }); describe('queries', () => { const directive = { 'some.directive.ts': ` import {Directive} from '@angular/core'; @Directive({ selector: '[someDir]', }) export class SomeDirective { } ` }; it('should support view queries with directives', () => { const files = { app: { ...directive, 'view_query.component.ts': ` import {Component, NgModule, ViewChild, ViewChildren} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'view-query-component', template: \`
\` }) export class ViewQueryComponent { @ViewChild(SomeDirective) someDir: SomeDirective; @ViewChildren(SomeDirective) someDirs: QueryList; } @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) export class MyModule {} ` } }; const ViewQueryComponentDefinition = ` … ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ViewQueryComponent, selectors: [["view-query-component"]], viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery(SomeDirective, true); $r3$.ɵɵviewQuery(SomeDirective, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); } }, decls: 1, vars: 0, consts: [["someDir",""]], template: function ViewQueryComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", 0); } }, directives: function () { return [SomeDirective]; }, encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); }); it('should support view queries with local refs', () => { const files = { app: { 'view_query.component.ts': ` import {Component, NgModule, ViewChild, ViewChildren, QueryList} from '@angular/core'; @Component({ selector: 'view-query-component', template: \`
\` }) export class ViewQueryComponent { @ViewChild('myRef') myRef: any; @ViewChildren('myRef1, myRef2, myRef3') myRefs: QueryList; } @NgModule({declarations: [ViewQueryComponent]}) export class MyModule {} ` } }; const ViewQueryComponentDefinition = ` const $e0_attrs$ = ["myRef"]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ … viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery($e0_attrs$, true); $r3$.ɵɵviewQuery($e1_attrs$, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); } }, … });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); }); it('should support static view queries', () => { const files = { app: { ...directive, 'view_query.component.ts': ` import {Component, NgModule, ViewChild} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'view-query-component', template: \`
\` }) export class ViewQueryComponent { @ViewChild(SomeDirective, {static: true}) someDir !: SomeDirective; @ViewChild('foo') foo !: ElementRef; } @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) export class MyModule {} ` } }; const ViewQueryComponentDefinition = ` const $refs$ = ["foo"]; … ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ViewQueryComponent, selectors: [["view-query-component"]], viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵstaticViewQuery(SomeDirective, true); $r3$.ɵɵviewQuery($refs$, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.foo = $tmp$.first); } }, decls: 1, vars: 0, consts: [["someDir",""]], template: function ViewQueryComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", 0); } }, directives: function () { return [SomeDirective]; }, encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); }); it('should support view queries with read tokens specified', () => { const files = { app: { ...directive, 'view_query.component.ts': ` import {Component, NgModule, ViewChild, ViewChildren, QueryList, ElementRef, TemplateRef} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'view-query-component', template: \`
\` }) export class ViewQueryComponent { @ViewChild('myRef', {read: TemplateRef}) myRef: TemplateRef; @ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; @ViewChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; @ViewChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; } @NgModule({declarations: [ViewQueryComponent]}) export class MyModule {} ` } }; const ViewQueryComponentDefinition = ` const $e0_attrs$ = ["myRef"]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ … viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery($e0_attrs$, true, TemplateRef); $r3$.ɵɵviewQuery(SomeDirective, true, ElementRef); $r3$.ɵɵviewQuery($e1_attrs$, true, ElementRef); $r3$.ɵɵviewQuery(SomeDirective, true, TemplateRef); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); } }, … });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); }); it('should support content queries with directives', () => { const files = { app: { ...directive, 'content_query.ts': ` import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'content-query-component', template: \`
\` }) export class ContentQueryComponent { @ContentChild(SomeDirective) someDir: SomeDirective; @ContentChildren(SomeDirective) someDirList !: QueryList; } @Component({ selector: 'my-app', template: \`
\` }) export class MyApp { } @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]}) export class MyModule { } ` } }; const ContentQueryComponentDefinition = ` ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ContentQueryComponent, selectors: [["content-query-component"]], contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true); $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirList = $tmp$); } }, ngContentSelectors: _c0, decls: 2, vars: 0, template: function ContentQueryComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef(); $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵprojection(1); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); }); it('should support content queries with local refs', () => { const files = { app: { 'content_query.component.ts': ` import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; @Component({ selector: 'content-query-component', template: \`
\` }) export class ContentQueryComponent { @ContentChild('myRef') myRef: any; @ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList; } @NgModule({declarations: [ContentQueryComponent]}) export class MyModule {} ` } }; const ContentQueryComponentDefinition = ` const $e0_attrs$ = ["myRef"]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ … contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true); $r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); } }, … });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); }); it('should support static content queries', () => { const files = { app: { ...directive, 'content_query.ts': ` import {Component, ContentChild, NgModule} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'content-query-component', template: \`
\` }) export class ContentQueryComponent { @ContentChild(SomeDirective, {static: true}) someDir !: SomeDirective; @ContentChild('foo') foo !: ElementRef; } @Component({ selector: 'my-app', template: \`
\` }) export class MyApp { } @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]}) export class MyModule { } ` } }; const ContentQueryComponentDefinition = ` ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: ContentQueryComponent, selectors: [["content-query-component"]], contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, true); $r3$.ɵɵcontentQuery(dirIndex, $ref0$, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.foo = $tmp$.first); } }, ngContentSelectors: $_c1$, decls: 2, vars: 0, template: function ContentQueryComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵprojectionDef(); $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵprojection(1); $r3$.ɵɵelementEnd(); } }, encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); }); it('should support content queries with read tokens specified', () => { const files = { app: { ...directive, 'content_query.component.ts': ` import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef, TemplateRef} from '@angular/core'; import {SomeDirective} from './some.directive'; @Component({ selector: 'content-query-component', template: \`
\` }) export class ContentQueryComponent { @ContentChild('myRef', {read: TemplateRef}) myRef: TemplateRef; @ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; @ContentChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; @ContentChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; } @NgModule({declarations: [ContentQueryComponent]}) export class MyModule {} ` } }; const ContentQueryComponentDefinition = ` const $e0_attrs$ = ["myRef"]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({ … contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, TemplateRef); $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true, ElementRef); $r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false, ElementRef); $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, TemplateRef); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); } }, … });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); }); }); describe('pipes', () => { it('should render pipes', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; @Pipe({ name: 'myPipe', pure: false }) export class MyPipe implements PipeTransform, OnDestroy { transform(value: any, ...args: any[]) { return value; } ngOnDestroy(): void { } } @Pipe({ name: 'myPurePipe', pure: true, }) export class MyPurePipe implements PipeTransform { transform(value: any, ...args: any[]) { return value; } } @Component({ selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}

{{ name | myPipe:1:2:3:4:5 }} {{ name ? 1 : 2 | myPipe }}

' }) export class MyApp { name = 'World'; size = 0; } @NgModule({declarations:[MyPipe, MyPurePipe, MyApp]}) export class MyModule {} ` } }; const MyPipeDefinition = ` MyPipe.ɵpipe = $r3$.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: false }); `; const MyPipeFactoryDef = ` MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)(); }; `; const MyPurePipeDefinition = ` MyPurePipe.ɵpipe = $r3$.ɵɵdefinePipe({ name: "myPurePipe", type: MyPurePipe, pure: true });`; const MyPurePipeFactoryDef = ` MyPurePipe.ɵfac = function MyPurePipe_Factory(t) { return new (t || MyPurePipe)(); }; `; const MyAppDefinition = ` const $c0$ = function ($a0$) { return [$a0$, 1, 2, 3, 4, 5]; }; // ... MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 7, vars: 20, template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0); $r3$.ɵɵpipe(1, "myPurePipe"); $r3$.ɵɵpipe(2, "myPipe"); $r3$.ɵɵelementStart(3, "p"); $r3$.ɵɵtext(4); $r3$.ɵɵpipe(5, "myPipe"); $r3$.ɵɵpipe(6, "myPipe"); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵtextInterpolate($r3$.ɵɵpipeBind2(1, 3, $r3$.ɵɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size)); $r3$.ɵɵadvance(4); $r3$.ɵɵtextInterpolate2("", $r3$.ɵɵpipeBindV(5, 9, $r3$.ɵɵpureFunction1(18, $c0$, ctx.name)), " ", ctx.name ? 1 : $r3$.ɵɵpipeBind1(6, 16, 2), ""); } }, pipes: [MyPurePipe, MyPipe], encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyPipeDefinition, 'Invalid pipe definition'); expectEmit(source, MyPipeFactoryDef, 'Invalid pipe factory function'); expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition'); expectEmit(source, MyPurePipeFactoryDef, 'Invalid pure pipe factory function'); expectEmit(source, MyAppDefinition, 'Invalid MyApp definition'); }); it('should use appropriate function for a given no of pipe arguments', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; @Pipe({ name: 'myPipe', pure: false }) export class MyPipe implements PipeTransform, OnDestroy { transform(value: any, ...args: any[]) { return value; } ngOnDestroy(): void { } } @Component({ selector: 'my-app', template: '0:{{name | myPipe}}1:{{name | myPipe:1}}2:{{name | myPipe:1:2}}3:{{name | myPipe:1:2:3}}4:{{name | myPipe:1:2:3:4}}' }) export class MyApp { } @NgModule({declarations:[MyPipe, MyApp]}) export class MyModule {} ` } }; const MyAppDefinition = ` // ... MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 6, vars: 27, template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0); $r3$.ɵɵpipe(1, "myPipe"); $r3$.ɵɵpipe(2, "myPipe"); $r3$.ɵɵpipe(3, "myPipe"); $r3$.ɵɵpipe(4, "myPipe"); $r3$.ɵɵpipe(5, "myPipe"); } if (rf & 2) { $r3$.ɵɵtextInterpolate5( "0:", i0.ɵɵpipeBind1(1, 5, ctx.name), "1:", i0.ɵɵpipeBind2(2, 7, ctx.name, 1), "2:", i0.ɵɵpipeBind3(3, 10, ctx.name, 1, 2), "3:", i0.ɵɵpipeBind4(4, 14, ctx.name, 1, 2, 3), "4:", i0.ɵɵpipeBindV(5, 19, $r3$.ɵɵpureFunction1(25, $c0$, ctx.name)), "" ); } }, pipes: [MyPipe], encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid MyApp definition'); }); it('should generate the proper instruction when injecting ChangeDetectorRef into a pipe', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Pipe, PipeTransform, ChangeDetectorRef, Optional} from '@angular/core'; @Pipe({name: 'myPipe'}) export class MyPipe implements PipeTransform { constructor(changeDetectorRef: ChangeDetectorRef) {} transform(value: any, ...args: any[]) { return value; } } @Pipe({name: 'myOtherPipe'}) export class MyOtherPipe implements PipeTransform { constructor(@Optional() changeDetectorRef: ChangeDetectorRef) {} transform(value: any, ...args: any[]) { return value; } } @Component({ selector: 'my-app', template: '{{name | myPipe }}

{{ name | myOtherPipe }}

' }) export class MyApp { name = 'World'; } @NgModule({declarations:[MyPipe, MyOtherPipe, MyApp]}) export class MyModule {} ` } }; const MyPipeDefinition = ` MyPipe.ɵpipe = $r3$.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: true }); `; const MyPipeFactory = ` MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)($r3$.ɵɵinjectPipeChangeDetectorRef()); }; `; const MyOtherPipeDefinition = ` MyOtherPipe.ɵpipe = $r3$.ɵɵdefinePipe({ name: "myOtherPipe", type: MyOtherPipe, pure: true });`; const MyOtherPipeFactory = ` MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($r3$.ɵɵinjectPipeChangeDetectorRef(8)); }; `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyPipeDefinition, 'Invalid pipe definition'); expectEmit(source, MyPipeFactory, 'Invalid pipe factory function'); expectEmit(source, MyOtherPipeDefinition, 'Invalid alternate pipe definition'); expectEmit(source, MyOtherPipeFactory, 'Invalid alternate pipe factory function'); }); }); it('local reference', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({selector: 'my-component', template: 'Hello {{user.value}}!'}) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const MyComponentDefinition = ` … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 3, vars: 1, consts: [["user", ""]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "input", null, 0); $r3$.ɵɵtext(2); } if (rf & 2) { const $user$ = $r3$.ɵɵreference(1); $r3$.ɵɵadvance(2); $r3$.ɵɵtextInterpolate1("Hello ", $user$.value, "!"); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); }); it('local references in nested views', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; @Directive({selector: '[if]'}) export class IfDirective { constructor(template: TemplateRef) { } } @Component({ selector: 'my-component', template: \`
{{foo}}
{{foo}}-{{bar}} {{foo}}-{{bar}}-{{baz}}
\` }) export class MyComponent {} @NgModule({declarations: [IfDirective, MyComponent]}) export class MyModule {} ` } }; const MyComponentDefinition = ` function MyComponent_div_3_span_2_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "span"); $r3$.ɵɵtext(1); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵnextContext(); const $bar$ = $r3$.ɵɵreference(4); $r3$.ɵɵnextContext(); const $foo$ = $r3$.ɵɵreference(1); const $baz$ = $r3$.ɵɵreference(5); $r3$.ɵɵadvance(1); $r3$.ɵɵtextInterpolate3("", $foo$, "-", $bar$, "-", $baz$, ""); } } function MyComponent_div_3_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵtext(1); $r3$.ɵɵtemplate(2, MyComponent_div_3_span_2_Template, 2, 3, "span", 1); $r3$.ɵɵelement(3, "span", null, 3); $r3$.ɵɵelementEnd(); } if (rf & 2) { const $bar$ = $r3$.ɵɵreference(4); $r3$.ɵɵnextContext(); const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵadvance(1); $r3$.ɵɵtextInterpolate2(" ", $foo$, "-", $bar$, " "); } } … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 6, vars: 1, consts: [["foo", ""], [${AttributeMarker.Template}, "if"], ["baz", ""], ["bar", ""]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "div", null, 0); $r3$.ɵɵtext(2); $r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 5, 2, "div", 1); $r3$.ɵɵelement(4, "div", null, 2); } if (rf & 2) { const $foo$ = $r3$.ɵɵreference(1); $r3$.ɵɵadvance(2); $r3$.ɵɵtextInterpolate1(" ", $foo$, " "); } }, directives:[IfDirective], encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); }); it('should support local refs mixed with context assignments', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`
{{ foo }} - {{ item }}
\` }) export class MyComponent {} @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` function MyComponent_div_0_span_3_Template(rf, ctx) { if (rf & 1) { $i0$.ɵɵelementStart(0, "span"); $i0$.ɵɵtext(1); $i0$.ɵɵelementEnd(); } if (rf & 2) { const $item$ = $i0$.ɵɵnextContext().$implicit; const $foo$ = $i0$.ɵɵreference(2); $r3$.ɵɵadvance(1); $i0$.ɵɵtextInterpolate2(" ", $foo$, " - ", $item$, " "); } } function MyComponent_div_0_Template(rf, ctx) { if (rf & 1) { $i0$.ɵɵelementStart(0, "div"); $i0$.ɵɵelement(1, "div", null, 1); $i0$.ɵɵtemplate(3, MyComponent_div_0_span_3_Template, 2, 2, "span", 2); $i0$.ɵɵelementEnd(); } if (rf & 2) { const $app$ = $i0$.ɵɵnextContext(); $r3$.ɵɵadvance(3); $i0$.ɵɵproperty("ngIf", $app$.showing); } } // ... consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"], ["foo", ""], [${AttributeMarker.Template}, "ngIf"]], template:function MyComponent_Template(rf, ctx){ if (rf & 1) { $i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 4, 1, "div", 0); } if (rf & 2) { $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); describe('lifecycle hooks', () => { const files = { app: { 'spec.ts': ` import {Component, Input, NgModule} from '@angular/core'; let events: string[] = []; @Component({selector: 'lifecycle-comp', template: ''}) export class LifecycleComp { @Input('name') nameMin: string; ngOnChanges() { events.push('changes' + this.nameMin); } ngOnInit() { events.push('init' + this.nameMin); } ngDoCheck() { events.push('check' + this.nameMin); } ngAfterContentInit() { events.push('content init' + this.nameMin); } ngAfterContentChecked() { events.push('content check' + this.nameMin); } ngAfterViewInit() { events.push('view init' + this.nameMin); } ngAfterViewChecked() { events.push('view check' + this.nameMin); } ngOnDestroy() { events.push(this.nameMin); } } @Component({ selector: 'simple-layout', template: \` \` }) export class SimpleLayout { name1 = '1'; name2 = '2'; } @NgModule({declarations: [LifecycleComp, SimpleLayout]}) export class LifecycleModule {} ` } }; it('should gen hooks with a few simple components', () => { const LifecycleCompDefinition = ` LifecycleComp.ɵcmp = $r3$.ɵɵdefineComponent({ type: LifecycleComp, selectors: [["lifecycle-comp"]], inputs: {nameMin: ["name", "nameMin"]}, features: [$r3$.ɵɵNgOnChangesFeature()], decls: 0, vars: 0, template: function LifecycleComp_Template(rf, ctx) {}, encapsulation: 2 });`; const SimpleLayoutDefinition = ` SimpleLayout.ɵcmp = $r3$.ɵɵdefineComponent({ type: SimpleLayout, selectors: [["simple-layout"]], decls: 2, vars: 2, consts: [[3, "name"]], template: function SimpleLayout_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "lifecycle-comp", 0); $r3$.ɵɵelement(1, "lifecycle-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("name", ctx.name1); $r3$.ɵɵadvance(1); $r3$.ɵɵproperty("name", ctx.name2); } }, directives: [LifecycleComp], encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition'); expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition'); }); }); describe('template variables', () => { const shared = { shared: { 'for_of.ts': ` import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core'; export interface ForOfContext { $implicit: any; index: number; even: boolean; odd: boolean; } @Directive({selector: '[forOf]'}) export class ForOfDirective { private previous: any[]; constructor(private view: ViewContainerRef, private template: TemplateRef) {} @Input() forOf: any[]; ngOnChanges(simpleChanges: SimpleChanges) { if ('forOf' in simpleChanges) { this.update(); } } ngDoCheck(): void { const previous = this.previous; const current = this.forOf; if (!previous || previous.length != current.length || previous.some((value: any, index: number) => current[index] !== previous[index])) { this.update(); } } private update() { // TODO(chuckj): Not implemented yet // this.view.clear(); if (this.forOf) { const current = this.forOf; for (let i = 0; i < current.length; i++) { const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1}; // TODO(chuckj): Not implemented yet // this.view.createEmbeddedView(this.template, context); } this.previous = [...this.forOf]; } } } ` } }; it('should support embedded views in the SVG namespace', () => { const files = { app: { ...shared, 'spec.ts': ` import {Component, NgModule} from '@angular/core'; import {ForOfDirective} from './shared/for_of'; @Component({ selector: 'my-component', template: \`\` }) export class MyComponent { items = [{ data: 42 }, { data: 42 }]; } @NgModule({ declarations: [MyComponent, ForOfDirective] }) export class MyModule {} ` } }; // TODO(benlesh): Enforce this when the directives are specified const ForDirectiveDefinition = ` ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: ForOfDirective, selectors: [["", "forOf", ""]], features: [$r3$.ɵɵNgOnChangesFeature()], inputs: {forOf: "forOf"} }); `; const ForDirectiveFactory = `ForOfDirective.ɵfac = function ForOfDirective_Factory(t) { return new (t || ForOfDirective)($r3$.ɵɵdirectiveInject(ViewContainerRef), $r3$.ɵɵdirectiveInject(TemplateRef)); };`; const MyComponentDefinition = ` function MyComponent__svg_g_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵnamespaceSVG(); $r3$.ɵɵelementStart(0,"g"); $r3$.ɵɵelement(1,"circle"); $r3$.ɵɵelementEnd(); } } … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 2, vars: 1, consts: [[${AttributeMarker.Template}, "for", "forOf"]], template: function MyComponent_Template(rf, ctx){ if (rf & 1) { $r3$.ɵɵnamespaceSVG(); $r3$.ɵɵelementStart(0,"svg"); $r3$.ɵɵtemplate(1, MyComponent__svg_g_1_Template, 2, 0, "g", 0); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵadvance(1); $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function() { return [ForOfDirective]; }, encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; // TODO(benlesh): Enforce this when the directives are specified // expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition'); // expectEmit(source, ForDirectiveFactory, 'Invalid directive factory'); expectEmit(source, MyComponentDefinition, 'Invalid component definition'); }); it('should support a let variable and reference', () => { const files = { app: { ...shared, 'spec.ts': ` import {Component, NgModule} from '@angular/core'; import {ForOfDirective} from './shared/for_of'; @Component({ selector: 'my-component', template: \`
  • {{item.name}}
\` }) export class MyComponent { items = [{name: 'one'}, {name: 'two'}]; } @NgModule({ declarations: [MyComponent, ForOfDirective] }) export class MyModule {} ` } }; // TODO(chuckj): Enforce this when the directives are specified const ForDirectiveDefinition = ` ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: ForOfDirective, selectors: [["", "forOf", ""]], features: [$r3$.ɵɵNgOnChangesFeature()], inputs: {forOf: "forOf"} }); `; const ForDirectiveFactory = ` ForOfDirective.ɵfac = function ForOfDirective_Factory(t) { return new (t || ForOfDirective)($r3$.ɵɵdirectiveInject(ViewContainerRef), $r3$.ɵɵdirectiveInject(TemplateRef)); }; `; const MyComponentDefinition = ` function MyComponent_li_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "li"); $r3$.ɵɵtext(1); $r3$.ɵɵelementEnd(); } if (rf & 2) { const $item$ = ctx.$implicit; $r3$.ɵɵadvance(1); $r3$.ɵɵtextInterpolate($item$.name); } } … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 2, vars: 1, consts: [[${AttributeMarker.Template}, "for", "forOf"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "ul"); $r3$.ɵɵtemplate(1, MyComponent_li_1_Template, 2, 1, "li", 0); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵadvance(1); $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function() { return [ForOfDirective]; }, encapsulation: 2 }); `; const result = compile(files, angularFiles); const source = result.source; // TODO(chuckj): Enforce this when the directives are specified // expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition'); // expectEmit(source, ForDirectiveFactory, 'Invalid directive factory'); expectEmit(source, MyComponentDefinition, 'Invalid component definition'); }); it('should support accessing parent template variables', () => { const files = { app: { ...shared, 'spec.ts': ` import {Component, NgModule} from '@angular/core'; import {ForOfDirective} from './shared/for_of'; @Component({ selector: 'my-component', template: \`
  • {{item.name}}
    • {{item.name}}: {{info.description}}
\` }) export class MyComponent { items = [ {name: 'one', infos: [{description: '11'}, {description: '12'}]}, {name: 'two', infos: [{description: '21'}, {description: '22'}]} ]; } @NgModule({ declarations: [MyComponent, ForOfDirective] }) export class MyModule {} ` } }; const MyComponentDefinition = ` function MyComponent_li_1_li_4_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "li"); $r3$.ɵɵtext(1); $r3$.ɵɵelementEnd(); } if (rf & 2) { const $info$ = ctx.$implicit; const $item$ = $r3$.ɵɵnextContext().$implicit; $r3$.ɵɵadvance(1); $r3$.ɵɵtextInterpolate2(" ", $item$.name, ": ", $info$.description, " "); } } function MyComponent_li_1_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "li"); $r3$.ɵɵelementStart(1, "div"); $r3$.ɵɵtext(2); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementStart(3, "ul"); $r3$.ɵɵtemplate(4, MyComponent_li_1_li_4_Template, 2, 2, "li", 0); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementEnd(); } if (rf & 2) { const $item$ = ctx.$implicit; $r3$.ɵɵadvance(2); $r3$.ɵɵtextInterpolate(IDENT.name); $r3$.ɵɵadvance(2); $r3$.ɵɵproperty("forOf", IDENT.infos); } } … MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 2, vars: 1, consts: [[${AttributeMarker.Template}, "for", "forOf"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "ul"); $r3$.ɵɵtemplate(1, MyComponent_li_1_Template, 5, 2, "li", 0); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵadvance(1); $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function () { return [ForOfDirective]; }, encapsulation: 2 });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Invalid component definition'); }); }); it('should instantiate directives in a closure when they are forward referenced', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Directive} from '@angular/core'; @Component({ selector: 'host-binding-comp', template: \` \` }) export class HostBindingComp { } @Directive({ selector: 'my-forward-directive' }) class MyForwardDirective {} @NgModule({declarations: [HostBindingComp, MyForwardDirective]}) export class MyModule {} ` } }; const MyAppDefinition = ` … directives: function () { return [MyForwardDirective]; } … `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid component definition'); }); it('should instantiate pipes in a closure when they are forward referenced', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Pipe} from '@angular/core'; @Component({ selector: 'host-binding-comp', template: \`
...
\` }) export class HostBindingComp { } @Pipe({ name: 'my_forward_pipe' }) class MyForwardPipe {} @NgModule({declarations: [HostBindingComp, MyForwardPipe]}) export class MyModule {} ` } }; const MyAppDefinition = ` … pipes: function () { return [MyForwardPipe]; } … `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid component definition'); }); it('should split multiple `exportAs` values into an array', () => { const files = { app: { 'spec.ts': ` import {Directive, NgModule} from '@angular/core'; @Directive({selector: '[some-directive]', exportAs: 'someDir, otherDir'}) export class SomeDirective {} @NgModule({declarations: [SomeDirective]}) export class MyModule{} ` } }; // SomeDirective definition should be: const SomeDirectiveDefinition = ` SomeDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: SomeDirective, selectors: [["", "some-directive", ""]], exportAs: ["someDir", "otherDir"] }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); }); it('should not throw for empty property bindings on ng-template', () => { const files = { app: { 'example.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-app', template: '' }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {}` } }; expect(() => compile(files, angularFiles)).not.toThrow(); }); it('should not generate a selectors array if the directive does not have a selector', () => { const files = { app: { 'spec.ts': ` import {Directive} from '@angular/core'; @Directive() export class AbstractDirective { } ` } }; const expectedOutput = ` // ... AbstractDirective.ɵdir = $r3$.ɵɵdefineDirective({ type: AbstractDirective }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should generate a pure function for constant object literals', () => { const files = { app: { 'spec.ts': ` import {Component} from '@angular/core'; @Component({ template: '' }) export class MyApp { } ` } }; const MyAppDeclaration = ` const $c0$ = function () { return {}; }; const $c1$ = function () { return { a: 1, b: 2 }; }; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["ng-component"]], decls: 1, vars: 4, consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "some-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$)); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); }); it('should generate a pure function for constant array literals', () => { const files = { app: { 'spec.ts': ` import {Component} from '@angular/core'; @Component({ template: '' }) export class MyApp { } ` } }; const MyAppDeclaration = ` const $c0$ = function () { return []; }; const $c1$ = function () { return [0, 1, 2]; }; … MyApp.ɵcmp = $r3$.ɵɵdefineComponent({ type: MyApp, selectors: [["ng-component"]], decls: 1, vars: 4, consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]], template: function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelement(0, "some-comp", 0); } if (rf & 2) { $r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$)); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); }); }); describe('inherited base classes', () => { const directive = { 'some.directive.ts': ` import {Directive} from '@angular/core'; @Directive({ selector: '[someDir]', }) export class SomeDirective { } ` }; it('should add an abstract directive if one or more @Input is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Input} from '@angular/core'; export class BaseClass { @Input() input1 = 'test'; @Input('alias2') input2 = 'whatever'; } @Component({ selector: 'my-component', template: \`
{{input1}} {{input2}}
\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, inputs: { input1: "input1", input2: ["alias2", "input2"] } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if one or more @Output is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Output, EventEmitter} from '@angular/core'; export class BaseClass { @Output() output1 = new EventEmitter(); @Output() output2 = new EventEmitter(); clicked() { this.output1.emit('test'); this.output2.emit('test'); } } @Component({ selector: 'my-component', template: \`\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, outputs: { output1: "output1", output2: "output2" } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a mixture of @Input and @Output props are present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Input, Output, EventEmitter} from '@angular/core'; export class BaseClass { @Output() output1 = new EventEmitter(); @Output() output2 = new EventEmitter(); @Input() input1 = 'test'; @Input('whatever') input2 = 'blah'; clicked() { this.output1.emit('test'); this.output2.emit('test'); } } @Component({ selector: 'my-component', template: \`\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, inputs: { input1: "input1", input2: ["whatever", "input2"] }, outputs: { output1: "output1", output2: "output2" } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a ViewChild query is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, ViewChild} from '@angular/core'; export class BaseClass { @ViewChild('something') something: any; } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` const $e0_attrs$ = ["something"]; // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, viewQuery: function BaseClass_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery($e0_attrs$, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.something = $tmp$.first); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a ViewChildren query is present', () => { const files = { app: { ...directive, 'spec.ts': ` import {Component, NgModule, ViewChildren} from '@angular/core'; import {SomeDirective} from './some.directive'; export class BaseClass { @ViewChildren(SomeDirective) something: QueryList; } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent, SomeDirective] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, viewQuery: function BaseClass_Query(rf, ctx) { if (rf & 1) { $r3$.ɵɵviewQuery(SomeDirective, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.something = $tmp$); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a ContentChild query is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, ContentChild} from '@angular/core'; export class BaseClass { @ContentChild('something') something: any; } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` const $e0_attrs$ = ["something"]; // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, contentQueries: function BaseClass_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.something = $tmp$.first); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a ContentChildren query is present', () => { const files = { app: { ...directive, 'spec.ts': ` import {Component, NgModule, ContentChildren} from '@angular/core'; import {SomeDirective} from './some.directive'; export class BaseClass { @ContentChildren(SomeDirective) something: QueryList; } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent, SomeDirective] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, contentQueries: function BaseClass_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) { $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false); } if (rf & 2) { var $tmp$; $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.something = $tmp$); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a host binding is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, HostBinding} from '@angular/core'; export class BaseClass { @HostBinding('attr.tabindex') tabindex = -1; } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) { if (rf & 1) { $r3$.ɵɵallocHostVars(1); } if (rf & 2) { $r3$.ɵɵattribute("tabindex", ctx.tabindex); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive if a host listener is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, HostListener} from '@angular/core'; export class BaseClass { @HostListener('mousedown', ['$event']) handleMousedown(event: any) {} } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) { if (rf & 1) { $r3$.ɵɵlistener("mousedown", function BaseClass_mousedown_HostBindingHandler($event) { return ctx.handleMousedown($event); }); } } }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive when using any lifecycle hook', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Input} from '@angular/core'; export class BaseClass { ngAfterContentChecked() {} } @Component({ selector: 'my-component', template: \`
{{input1}} {{input2}}
\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should add an abstract directive when using ngOnChanges', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Input} from '@angular/core'; export class BaseClass { ngOnChanges() {} } @Component({ selector: 'my-component', template: \`
{{input1}} {{input2}}
\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const expectedOutput = ` // ... BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ type: BaseClass, features: [$r3$.ɵɵNgOnChangesFeature()] }); // ... `; const result = compile(files, angularFiles); expectEmit(result.source, expectedOutput, 'Invalid directive definition'); }); it('should NOT add an abstract directive if @Component is present', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, Output, EventEmitter} from '@angular/core'; @Component({ selector: 'whatever', template: '' }) export class BaseClass { @Output() output1 = new EventEmitter(); @Input() input1 = 'whatever'; clicked() { this.output1.emit('test'); } } @Component({ selector: 'my-component', template: \`
What is this developer doing?
\` }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const result = compile(files, angularFiles); expect(result.source).not.toContain('ɵdir'); }); it('should NOT add an abstract directive if @Directive is present', () => { const files = { app: { 'spec.ts': ` import {Component, Directive, NgModule, Output, EventEmitter} from '@angular/core'; @Directive({ selector: 'whatever', }) export class BaseClass { @Output() output1 = new EventEmitter(); @Input() input1 = 'whatever'; clicked() { this.output1.emit('test'); } } @Component({ selector: 'my-component', template: '' }) export class MyComponent extends BaseClass { } @NgModule({ declarations: [MyComponent] }) export class MyModule {} ` } }; const result = compile(files, angularFiles); expect(result.source.match(/ɵdir/g) !.length).toBe(1); }); }); });