From 9f43f5f09e41b9b4049763a71f4a8c8c70c71703 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 8 Jan 2018 22:06:19 -0800 Subject: [PATCH] test(ivy): add canonical template translation examples (#21374) This change creates a spec file which contains canonical examples of how the template compiler will translate templates into expected output. PR Close #21374 --- packages/core/src/render3/index.ts | 1 + .../test/render3/compiler_canonical_spec.ts | 265 ++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 packages/core/test/render3/compiler_canonical_spec.ts diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index bec2d6bef0..ad772c6c53 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -84,3 +84,4 @@ export { defineDirective, }; export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent}; +export {InjectFlags} from './di'; \ No newline at end of file diff --git a/packages/core/test/render3/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical_spec.ts new file mode 100644 index 0000000000..22a0ca2e65 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical_spec.ts @@ -0,0 +1,265 @@ +/** + * @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 {Component, Directive, Type, NgModule, Injectable, Optional, TemplateRef} from '../../src/core'; +import * as r3 from '../../src/render3/index'; + +import {containerEl, renderComponent, requestAnimationFrame, toHtml} from './render_util'; + +/** + * NORMATIVE => /NORMATIVE: Designates what the compiler is expected to generate. + * + * All local variable names are considered non-normative (informative). + */ + +describe('compiler specification', () => { + describe('elements', () => { + it('should translate DOM structure', () => { + @Component({ + selector: 'my-component', + template: `
Hello World!
` + }) + class MyComponent { + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + type: MyComponent, + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: MyComponent, cm: boolean) { + if (cm) { + r3.E(0, 'div', e0_attrs); + r3.T(1, 'Hello '); + r3.E(2, 'b'); + r3.T(3, 'World'); + r3.e(); + r3.T(4, '!'); + r3.e(); + } + } + }); + // /NORMATIVE + } + // Important: keep arrays outside of function to not create new instances. + const e0_attrs = ['class', 'my-app', 'title', 'Hello']; + + expect(renderComp(MyComponent)) + .toEqual('
Hello World!
'); + }); + }); + + describe('components & directives', () => { + it('should instantiate directives', () => { + const log: string[] = []; + @Component({selector: 'child', template: 'child-view'}) + class ChildComponent { + constructor() { log.push('ChildComponent'); } + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + type: ChildComponent, + tag: `child`, + factory: () => new ChildComponent(), + template: function(ctx: ChildComponent, cm: boolean) { + if (cm) { + r3.T(0, 'child-view'); + } + } + }); + // /NORMATIVE + } + + @Directive({ + selector: 'some-directive', + }) + class SomeDirective { + constructor() { log.push('SomeDirective'); } + // NORMATIVE + static ngDirectiveDef = r3.defineDirective({ + type: ChildComponent, + factory: () => new SomeDirective(), + }); + // /NORMATIVE + } + + @Component({selector: 'my-component', template: `!`}) + class MyComponent { + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: MyComponent, cm: boolean) { + if (cm) { + r3.E(0, ChildComponent, e0_attrs, e0_dirs); + r3.e(); + r3.T(3, '!'); + } + ChildComponent.ngComponentDef.r(1, 0); + } + }); + // /NORMATIVE + } + // Important: keep arrays outside of function to not create new instances. + // NORMATIVE + const e0_attrs = ['some-directive', '']; + const e0_dirs = [SomeDirective]; + // /NORMATIVE + + expect(renderComp(MyComponent)).toEqual('child-view!'); + expect(log).toEqual(['ChildComponent', 'SomeDirective']); + }); + + xit('should support structural directives', () => { + const log: string[] = []; + @Directive({ + selector: 'if', + }) + class IfDirective { + constructor(template: TemplateRef) { log.push('ifDirective'); } + // NORMATIVE + static ngDirectiveDef = r3.defineDirective({ + type: IfDirective, + factory: () => new IfDirective(r3.injectTemplateRef()), + }); + // /NORMATIVE + } + + @Component({selector: 'my-component', template: ``}) + class MyComponent { + salutation = 'Hello'; + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: MyComponent, cm: boolean) { + if (cm) { + r3.E(0, 'ul', null, null, e0_locals); + r3.C(2, c1_dirs, C1); + r3.e(); + } + let foo = r3.m(1); + r3.cR(2); + IfDirective.ngDirectiveDef.r(3, 2); + r3.cr(); + + function C1(ctx1: any, cm: boolean) { + if (cm) { + r3.E(0, 'li'); + r3.T(1); + r3.e(); + } + r3.t(1, r3.b2('', ctx.salutation, ' ', foo, '')); + } + } + }); + // /NORMATIVE + } + // Important: keep arrays outside of function to not create new instances. + // NORMATIVE + const e0_locals = ['foo', '']; + const c1_dirs = [IfDirective]; + // /NORMATIVE + + expect(renderComp(MyComponent)).toEqual('child-view!'); + expect(log).toEqual(['ChildComponent', 'SomeDirective']); + }); + }); + + describe('local references', () => { + // TODO(misko): currently disabled until local refs are working + xit('should translate DOM structure', () => { + @Component({selector: 'my-component', template: `Hello {{user.value}}!`}) + class MyComponent { + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + tag: 'my-component', + factory: () => new MyComponent, + template: function(ctx: MyComponent, cm: boolean) { + if (cm) { + r3.E(0, 'input', null, null, ['user', '']); + r3.e(); + r3.T(2); + } + r3.t(2, r3.b1('Hello ', r3.m(1).value, '!')); + } + }); + // NORMATIVE + } + + expect(renderComp(MyComponent)) + .toEqual('
Hello World!
'); + }); + }); + +}); + +xdescribe('NgModule', () => { + interface Injectable { + scope?: /*InjectorDefType*/any; + factory: Function; + } + + function defineInjectable(opts: Injectable): Injectable { + // This class should be imported from https://github.com/angular/angular/pull/20850 + return opts; + } + function defineInjector(opts: any): any { + // This class should be imported from https://github.com/angular/angular/pull/20850 + return opts; + } + it('should convert module', () => { + @Injectable() + class Toast { + constructor(name: String) {} + // NORMATIVE + static ngInjectableDef = defineInjectable({ + factory: () => new Toast(inject(String)), + }); + // /NORMATIVE + } + + class CommonModule { + // NORMATIVE + static ngInjectorDef = defineInjector({}); + // /NORMATIVE + } + + @NgModule({ + providers: [Toast, {provide: String, useValue: 'Hello'}], + imports: [CommonModule], + }) + class MyModule { + constructor(toast: Toast) {} + // NORMATIVE + static ngInjectorDef = defineInjector({ + factory: () => new MyModule(inject(Toast)), + provider: [ + {provide: Toast, deps: [String]}, // If Toast has matadata generate this line + Toast, // If toast has not metadata generate this line. + {provide: String, useValue: 'Hello'} + ], + imports: [CommonModule] + }); + // /NORMATIVE + } + + @Injectable(/*{MyModule}*/) + class BurntToast{ + constructor(@Optional() toast: Toast|null, name: String) {} + // NORMATIVE + static ngInjectableDef = defineInjectable({ + scope: MyModule, + factory: () => new BurntToast(inject(Toast, r3.InjectFlags.Optional), inject(String)), + }); + // /NORMATIVE + } + + }); +}); + +function renderComp(type: r3.ComponentType): string { + return toHtml(renderComponent(type)); +}