/** * @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 {C, D, E, NC, T, V, a, b, b1, b2, b3, b4, b5, b6, b7, b8, bV, c, cR, cr, defineComponent, e, k, p, r, s, t, v} from '../../src/render3/index'; import {NO_CHANGE} from '../../src/render3/instructions'; import {containerEl, renderToHtml} from './render_util'; describe('render3 integration test', () => { describe('render', () => { it('should render basic template', () => { expect(renderToHtml(Template, {})).toEqual('Greetings'); function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span', ['title', 'Hello']); { T(1, 'Greetings'); } e(); } } }); it('should render and update basic "Hello, World" template', () => { expect(renderToHtml(Template, 'World')).toEqual('

Hello, World!

'); expect(renderToHtml(Template, 'New World')).toEqual('

Hello, New World!

'); function Template(name: string, cm: boolean) { if (cm) { E(0, 'h1'); { T(1); } e(); } t(1, b1('Hello, ', name, '!')); } }); }); describe('text bindings', () => { it('should render "undefined" as "" when used with `bind()`', () => { function Template(name: string, cm: boolean) { if (cm) { T(0); } t(0, b(name)); } expect(renderToHtml(Template, 'benoit')).toEqual('benoit'); expect(renderToHtml(Template, undefined)).toEqual(''); }); it('should render "null" as "" when used with `bind()`', () => { function Template(name: string, cm: boolean) { if (cm) { T(0); } t(0, b(name)); } expect(renderToHtml(Template, 'benoit')).toEqual('benoit'); expect(renderToHtml(Template, null)).toEqual(''); }); it('should support creation-time values in text nodes', () => { function Template(value: string, cm: boolean) { if (cm) { T(0); } t(0, cm ? value : NO_CHANGE); } expect(renderToHtml(Template, 'once')).toEqual('once'); expect(renderToHtml(Template, 'twice')).toEqual('once'); }); it('should support creation-time bindings in interpolations', () => { function Template(v: string, cm: boolean) { if (cm) { T(0); T(1); T(2); T(3); T(4); T(5); T(6); T(7); T(8); } t(0, b1('', cm ? v : NC, '|')); t(1, b2('', v, '_', cm ? v : NC, '|')); t(2, b3('', v, '_', v, '_', cm ? v : NC, '|')); t(3, b4('', v, '_', v, '_', v, '_', cm ? v : NC, '|')); t(4, b5('', v, '_', v, '_', v, '_', v, '_', cm ? v : NC, '|')); t(5, b6('', v, '_', v, '_', v, '_', v, '_', v, '_', cm ? v : NC, '|')); t(6, b7('', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', cm ? v : NC, '|')); t(7, b8('', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', cm ? v : NC, '|')); t(8, bV([ '', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', v, '_', cm ? v : NC, '' ])); } expect(renderToHtml(Template, 'a')) .toEqual( 'a|a_a|a_a_a|a_a_a_a|a_a_a_a_a|a_a_a_a_a_a|a_a_a_a_a_a_a|a_a_a_a_a_a_a_a|a_a_a_a_a_a_a_a_a'); expect(renderToHtml(Template, 'A')) .toEqual( 'a|A_a|A_A_a|A_A_A_a|A_A_A_A_a|A_A_A_A_A_a|A_A_A_A_A_A_a|A_A_A_A_A_A_A_a|A_A_A_A_A_A_A_A_a'); }); }); describe('Siblings update', () => { it('should handle a flat list of static/bound text nodes', () => { function Template(name: string, cm: boolean) { if (cm) { T(0, 'Hello '); T(1); T(2, '!'); } t(1, b(name)); } expect(renderToHtml(Template, 'world')).toEqual('Hello world!'); expect(renderToHtml(Template, 'monde')).toEqual('Hello monde!'); }); it('should handle a list of static/bound text nodes as element children', () => { function Template(name: string, cm: boolean) { if (cm) { E(0, 'b'); { T(1, 'Hello '); T(2); T(3, '!'); } e(); } t(2, b(name)); } expect(renderToHtml(Template, 'world')).toEqual('Hello world!'); expect(renderToHtml(Template, 'mundo')).toEqual('Hello mundo!'); }); it('should render/update text node as a child of a deep list of elements', () => { function Template(name: string, cm: boolean) { if (cm) { E(0, 'b'); { E(1, 'b'); { E(2, 'b'); { E(3, 'b'); { T(4); } e(); } e(); } e(); } e(); } t(4, b1('Hello ', name, '!')); } expect(renderToHtml(Template, 'world')).toEqual('Hello world!'); expect(renderToHtml(Template, 'mundo')).toEqual('Hello mundo!'); }); it('should update 2 sibling elements', () => { function Template(id: any, cm: boolean) { if (cm) { E(0, 'b'); { E(1, 'span'); e(); E(2, 'span', ['class', 'foo']); {} e(); } e(); } a(2, 'id', b(id)); } expect(renderToHtml(Template, 'foo')) .toEqual(''); expect(renderToHtml(Template, 'bar')) .toEqual(''); }); it('should handle sibling text node after element with child text node', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'p'); { T(1, 'hello'); } e(); T(2, 'world'); } } expect(renderToHtml(Template, null)).toEqual('

hello

world'); }); }); describe('basic components', () => { class TodoComponent { value = ' one'; static ngComponentDef = defineComponent({ type: TodoComponent, tag: 'todo', template: function TodoTemplate(ctx: any, cm: boolean) { if (cm) { E(0, 'p'); { T(1, 'Todo'); T(2); } e(); } t(2, b(ctx.value)); }, factory: () => new TodoComponent }); } it('should support a basic component template', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, TodoComponent.ngComponentDef); { D(1, TodoComponent.ngComponentDef.n(), TodoComponent.ngComponentDef); } e(); } TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.r(1, 0); } expect(renderToHtml(Template, null)).toEqual('

Todo one

'); }); it('should support a component template with sibling', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, TodoComponent.ngComponentDef); { D(1, TodoComponent.ngComponentDef.n(), TodoComponent.ngComponentDef); } e(); T(2, 'two'); } TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.r(1, 0); } expect(renderToHtml(Template, null)).toEqual('

Todo one

two'); }); it('should support a component template with component sibling', () => { /** * * */ function Template(ctx: any, cm: boolean) { if (cm) { E(0, TodoComponent.ngComponentDef); { D(1, TodoComponent.ngComponentDef.n(), TodoComponent.ngComponentDef); } e(); E(2, TodoComponent.ngComponentDef); { D(3, TodoComponent.ngComponentDef.n(), TodoComponent.ngComponentDef); } e(); } TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.h(3, 2); TodoComponent.ngComponentDef.r(1, 0); TodoComponent.ngComponentDef.r(3, 2); } expect(renderToHtml(Template, null)) .toEqual('

Todo one

Todo one

'); }); it('should support a component with binding on host element', () => { let cmptInstance: TodoComponentHostBinding|null; class TodoComponentHostBinding { title = 'one'; static ngComponentDef = defineComponent({ type: TodoComponentHostBinding, tag: 'todo', template: function TodoComponentHostBindingTemplate( ctx: TodoComponentHostBinding, cm: boolean) { if (cm) { T(0); } t(0, b(ctx.title)); }, factory: () => cmptInstance = new TodoComponentHostBinding, hostBindings: function(directiveIndex: number, elementIndex: number): void { // host bindings p(elementIndex, 'title', b(D(directiveIndex).title)); } }); } function Template(ctx: any, cm: boolean) { if (cm) { E(0, TodoComponentHostBinding.ngComponentDef); { D(1, TodoComponentHostBinding.ngComponentDef.n(), TodoComponentHostBinding.ngComponentDef); } e(); } TodoComponentHostBinding.ngComponentDef.h(1, 0); TodoComponentHostBinding.ngComponentDef.r(1, 0); } expect(renderToHtml(Template, {})).toEqual('one'); cmptInstance !.title = 'two'; expect(renderToHtml(Template, {})).toEqual('two'); }); it('should support component with bindings in template', () => { /**

{{ name }}

*/ class MyComp { name = 'Bess'; static ngComponentDef = defineComponent({ type: MyComp, tag: 'comp', template: function MyCompTemplate(ctx: any, cm: boolean) { if (cm) { E(0, 'p'); { T(1); } e(); } t(1, b(ctx.name)); }, factory: () => new MyComp }); } function Template(ctx: any, cm: boolean) { if (cm) { E(0, MyComp.ngComponentDef); { D(1, MyComp.ngComponentDef.n(), MyComp.ngComponentDef); } e(); } MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.r(1, 0); } expect(renderToHtml(Template, null)).toEqual('

Bess

'); }); it('should support a component with sub-views', () => { /** * % if (condition) { *
text
* % } */ class MyComp { condition: boolean; static ngComponentDef = defineComponent({ type: MyComp, tag: 'comp', template: function MyCompTemplate(ctx: any, cm: boolean) { if (cm) { C(0); c(); } cR(0); { if (ctx.condition) { if (V(0)) { E(0, 'div'); { T(1, 'text'); } e(); } v(); } } cr(); }, factory: () => new MyComp, inputs: {condition: 'condition'} }); } /** */ function Template(ctx: any, cm: boolean) { if (cm) { E(0, MyComp.ngComponentDef); { D(1, MyComp.ngComponentDef.n(), MyComp.ngComponentDef); } e(); } p(0, 'condition', b(ctx.condition)); MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.r(1, 0); } expect(renderToHtml(Template, {condition: true})).toEqual('
text
'); expect(renderToHtml(Template, {condition: false})).toEqual(''); }); }); describe('element bindings', () => { describe('elementAttribute', () => { it('should support attribute bindings', () => { const ctx: {title: string | null} = {title: 'Hello'}; function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); e(); } a(0, 'title', b(ctx.title)); } // initial binding expect(renderToHtml(Template, ctx)).toEqual(''); // update binding ctx.title = 'Hi!'; expect(renderToHtml(Template, ctx)).toEqual(''); // remove attribute ctx.title = null; expect(renderToHtml(Template, ctx)).toEqual(''); }); it('should stringify values used attribute bindings', () => { const ctx: {title: any} = {title: NaN}; function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); e(); } a(0, 'title', b(ctx.title)); } expect(renderToHtml(Template, ctx)).toEqual(''); ctx.title = {toString: () => 'Custom toString'}; expect(renderToHtml(Template, ctx)).toEqual(''); }); it('should update bindings', () => { function Template(c: any, cm: boolean) { if (cm) { E(0, 'b'); e(); } a(0, 'a', bV(c)); a(0, 'a0', b(c[1])); a(0, 'a1', b1(c[0], c[1], c[16])); a(0, 'a2', b2(c[0], c[1], c[2], c[3], c[16])); a(0, 'a3', b3(c[0], c[1], c[2], c[3], c[4], c[5], c[16])); a(0, 'a4', b4(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[16])); a(0, 'a5', b5(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[16])); a(0, 'a6', b6(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[16])); a(0, 'a7', b7(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[12], c[13], c[16])); a(0, 'a8', b8(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15], c[16])); } let args = ['(', 0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 'g', 7, ')']; expect(renderToHtml(Template, args)) .toEqual( ''); args = args.reverse(); expect(renderToHtml(Template, args)) .toEqual( ''); args = args.reverse(); expect(renderToHtml(Template, args)) .toEqual( ''); }); it('should not update DOM if context has not changed', () => { const ctx: {title: string | null} = {title: 'Hello'}; function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); C(1); c(); e(); } a(0, 'title', b(ctx.title)); cR(1); { if (true) { let cm1 = V(1); { if (cm1) { E(0, 'b'); {} e(); } a(0, 'title', b(ctx.title)); } v(); } } cr(); } // initial binding expect(renderToHtml(Template, ctx)) .toEqual(''); // update DOM manually containerEl.querySelector('b') !.setAttribute('title', 'Goodbye'); // refresh with same binding expect(renderToHtml(Template, ctx)) .toEqual(''); // refresh again with same binding expect(renderToHtml(Template, ctx)) .toEqual(''); }); }); describe('elementStyle', () => { it('should support binding to styles', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); e(); } s(0, 'border-color', b(ctx)); } expect(renderToHtml(Template, 'red')).toEqual(''); expect(renderToHtml(Template, 'green')) .toEqual(''); expect(renderToHtml(Template, null)).toEqual(''); }); it('should support binding to styles with suffix', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); e(); } s(0, 'font-size', b(ctx), 'px'); } expect(renderToHtml(Template, '100')).toEqual(''); expect(renderToHtml(Template, 200)).toEqual(''); expect(renderToHtml(Template, null)).toEqual(''); }); }); describe('elementClass', () => { it('should support CSS class toggle', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span'); e(); } k(0, 'active', b(ctx)); } expect(renderToHtml(Template, true)).toEqual(''); expect(renderToHtml(Template, false)).toEqual(''); // truthy values expect(renderToHtml(Template, 'a_string')).toEqual(''); expect(renderToHtml(Template, 10)).toEqual(''); // falsy values expect(renderToHtml(Template, '')).toEqual(''); expect(renderToHtml(Template, 0)).toEqual(''); }); it('should work correctly with existing static classes', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, 'span', ['class', 'existing']); e(); } k(0, 'active', b(ctx)); } expect(renderToHtml(Template, true)).toEqual(''); expect(renderToHtml(Template, false)).toEqual(''); }); }); }); describe('template data', () => { it('should re-use template data and node data', () => { /** * % if (condition) { *
* % } */ function Template(ctx: any, cm: boolean) { if (cm) { C(0); c(); } cR(0); { if (ctx.condition) { if (V(0)) { E(0, 'div'); {} e(); } v(); } } cr(); } expect((Template as any).ngStaticData).toBeUndefined(); renderToHtml(Template, {condition: true}); const oldTemplateData = (Template as any).ngStaticData; const oldContainerData = (oldTemplateData as any)[0]; const oldElementData = oldContainerData.containerStatic[0][0]; expect(oldContainerData).not.toBeNull(); expect(oldElementData).not.toBeNull(); renderToHtml(Template, {condition: false}); renderToHtml(Template, {condition: true}); const newTemplateData = (Template as any).ngStaticData; const newContainerData = (oldTemplateData as any)[0]; const newElementData = oldContainerData.containerStatic[0][0]; expect(newTemplateData === oldTemplateData).toBe(true); expect(newContainerData === oldContainerData).toBe(true); expect(newElementData === oldElementData).toBe(true); }); }); });