/**
* @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);
});
});
});