/** * @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 {MockDirectory, setup} from '../aot/test_util'; import {compile, expectEmit} from './mock_compile'; describe('r3_view_compiler', () => { const angularFiles = setup({ compileAngular: true, compileAnimations: false, compileCommon: true, }); describe('hello world', () => { it('should be able to generate the hello world component', () => { const files: MockDirectory = { app: { 'hello.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'hello-world', template: 'Hello, world!' }) export class HelloWorldComponent { } @NgModule({ declarations: [HelloWorldComponent] }) export class HelloWorldModule {} ` } }; compile(files, angularFiles); }); }); it('should be able to generate the example', () => { const files: MockDirectory = { app: { 'example.ts': ` import {Component, OnInit, OnDestroy, ElementRef, Input, NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; @Component({ selector: 'my-app', template: '' }) export class MyApp implements OnInit { list: any[] = []; constructor(public elementRef: ElementRef) {} ngOnInit(): void { } } @Component({ selector: 'todo', template: '' }) export class TodoComponent implements OnInit, OnDestroy { @Input() data: any[] = []; myTitle: string; constructor(public elementRef: ElementRef) {} ngOnInit(): void {} ngOnDestroy(): void {} } @NgModule({ declarations: [TodoComponent, MyApp], imports: [CommonModule] }) export class TodoModule{} ` } }; const result = compile(files, angularFiles); expect(result.source).toContain('@angular/core'); }); describe('interpolations', () => { // Regression #21927 it('should generate a correct call to bV with more than 8 interpolations', () => { const files: MockDirectory = { app: { 'example.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-app', template: ' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} ' }) export class MyApp { list: any[] = []; } @NgModule({declarations: [MyApp]}) export class MyModule {}` } }; const bV_call = `$r3$.ɵbV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3], ' ',ctx.list[4],' ',ctx.list[5],' ',ctx.list[6],' ',ctx.list[7],' ',ctx.list[8], ' '])`; const result = compile(files, angularFiles); expectEmit(result.source, bV_call, 'Incorrect bV call'); }); }); /* 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 conformance', () => { describe('elements', () => { 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 = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; // The template should look like this (where IDENT is a wild card for an identifier): const template = ` const $c1$ = ['class', 'my-app', 'title', 'Hello']; … template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'div', $c1$); $r3$.ɵT(1, 'Hello '); $r3$.ɵE(2, 'b'); $r3$.ɵT(3, 'World'); $r3$.ɵe(); $r3$.ɵT(4, '!'); $r3$.ɵe(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, factory, 'Incorrect factory'); 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 = ` static ngComponentDef = $r3$.ɵdefineComponent({ type: ChildComponent, tag: 'child', factory: function ChildComponent_Factory() { return new ChildComponent(); }, template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵT(0, 'child-view'); } } });`; // SomeDirective definition should be: const SomeDirectiveDefinition = ` static ngDirectiveDef = $r3$.ɵdefineDirective({ type: SomeDirective, factory: function SomeDirective_Factory() {return new SomeDirective(); } }); `; // MyComponent definition should be: const MyComponentDefinition = ` const $c1$ = ['some-directive', '']; const $c2$ = [SomeDirective]; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, tag: 'my-component', factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, ChildComponent, IDENT, IDENT); $r3$.ɵe(); $r3$.ɵT(3, '!'); } ChildComponent.ngComponentDef.h(1, 0); SomeDirective.ngDirectiveDef.h(2, 0); $r3$.ɵr(1, 0); $r3$.ɵr(2, 0); } }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef'); expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef'); expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef'); }); 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: '' }) export class MyComponent { salutation = 'Hello'; } @NgModule({declarations: [IfDirective, MyComponent]}) export class MyModule {} ` } }; const IfDirectiveDefinition = ` static ngDirectiveDef = $r3$.ɵdefineDirective({ type: IfDirective, factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); } });`; const MyComponentDefinition = ` const $c1$ = ['foo', '']; const $c2$ = [IfDirective]; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, tag: 'my-component', factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'ul', null, null, $c1$); $r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2); $r3$.ɵe(); } const $foo$ = $r3$.ɵm(1); IfDirective.ngDirectiveDef.h(3,2); $r3$.ɵcR(2); $r3$.ɵr(3,2); $r3$.ɵcr(); function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'li'); $r3$.ɵT(1); $r3$.ɵe(); } $r3$.ɵt(1, $r3$.ɵb2('', ctx.salutation, ' ', $foo$, '')); } } });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef'); expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); }); it('should support content projection', () => { 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 = ` static ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleComponent, tag: 'simple', factory: function SimpleComponent_Factory() { return new SimpleComponent(); }, template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵpD(0); $r3$.ɵE(1, 'div'); $r3$.ɵP(2, 0); $r3$.ɵe(); } } });`; const ComplexComponentDefinition = ` const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]]; const $c2$ = ['id','first']; const $c3$ = ['id','second']; … static ngComponentDef = $r3$.ɵdefineComponent({ type: ComplexComponent, tag: 'complex', factory: function ComplexComponent_Factory() { return new ComplexComponent(); }, template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵpD(0, $c1$); $r3$.ɵE(1, 'div', $c2$); $r3$.ɵP(2, 0, 1); $r3$.ɵe(); $r3$.ɵE(3, 'div', $c3$); $r3$.ɵP(4, 0, 2); $r3$.ɵe(); } } }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); expectEmit( result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition'); }); 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 = ` const $c1$ = ['user', '']; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, tag: 'my-component', factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'input', null, null, $c1$); $r3$.ɵe(); $r3$.ɵT(2); } const $user$ = $r3$.ɵm(1); $r3$.ɵt(2, $r3$.ɵb1('Hello ', $user$.value, '!')); } }); `; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); }); 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 = ` static ngComponentDef = $r3$.ɵdefineComponent({ type: LifecycleComp, tag: 'lifecycle-comp', factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {}, inputs: {nameMin: 'name'}, features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)] });`; const SimpleLayoutDefinition = ` const $c1$ = LifecycleComp.ngComponentDef; … static ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleLayout, tag: 'simple-layout', factory: function SimpleLayout_Factory() { return new SimpleLayout(); }, template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, LifecycleComp); $r3$.ɵe(); $r3$.ɵE(2, LifecycleComp); $r3$.ɵe(); } $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); $r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2)); $c1$.h(1, 0); $c1$.h(3, 2); $r3$.ɵr(1, 0); $r3$.ɵr(3, 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 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: \`\` }) 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 = ` static ngDirectiveDef = $r3$.ɵdefineDirective({ type: ForOfDirective, factory: function ForOfDirective_Factory() { return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); }, features: [$r3$.ɵNgOnChangesFeature(NgForOf)], inputs: {forOf: 'forOf'} }); `; const MyComponentDefinition = ` const $c1$ = [ForOfDirective]; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, tag: 'my-component', factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'ul'); $r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1); $r3$.ɵe(); } $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); ForOfDirective.ngDirectiveDef.h(2, 1); $r3$.ɵcR(1); $r3$.ɵr(2, 1); $r3$.ɵcr(); function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'li'); $r3$.ɵT(1); $r3$.ɵe(); } const $item$ = ctx0.$implicit; $r3$.ɵt(1, $r3$.ɵb1('', $item$.name, '')); } } }); `; 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, 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: \` \` }) 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 = ` const $c1$ = [ForOfDirective]; const $c2$ = ForOfDirective.ngDirectiveDef; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, tag: 'my-component', factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(ctx: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'ul'); $r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1); $r3$.ɵe(); } $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); $c2$.h(2,1); $r3$.ɵcR(1); $r3$.ɵr(2, 1); $r3$.ɵcr(); function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'li'); $r3$.ɵE(1, 'div'); $r3$.ɵT(2); $r3$.ɵe(); $r3$.ɵE(3, 'ul'); $r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4); $r3$.ɵe(); $r3$.ɵe(); } const $item$ = ctx0.$implicit; $r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos)); $c2$.h(5,4); $r3$.ɵt(2, $r3$.ɵb1('', IDENT.name, '')); $r3$.ɵcR(4); $r3$.ɵr(5, 4); $r3$.ɵcr(); function MyComponent_ForOfDirective_ForOfDirective_Template_4( ctx1: IDENT, cm: IDENT) { if (cm) { $r3$.ɵE(0, 'li'); $r3$.ɵT(1); $r3$.ɵe(); } const $info$ = ctx1.$implicit; $r3$.ɵt(1, $r3$.ɵb2(' ', $item$.name, ': ', $info$.description, ' ')); } } } });`; const result = compile(files, angularFiles); const source = result.source; expectEmit(source, MyComponentDefinition, 'Invalid component definition'); }); }); }); });