2017-12-01 14:23:03 -08:00
/**
* @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
*/
2018-08-06 16:40:16 +02:00
import { ElementRef , TemplateRef , ViewContainerRef } from '@angular/core' ;
2018-12-13 15:51:47 -08:00
2018-12-10 23:40:19 -08:00
import { RendererType2 } from '../../src/render/api' ;
2018-10-11 13:13:57 -07:00
import { AttributeMarker , defineComponent , defineDirective , templateRefExtractor } from '../../src/render3/index' ;
2018-09-06 18:50:57 -07:00
2018-12-13 15:51:47 -08:00
import { allocHostVars , bind , container , containerRefreshEnd , containerRefreshStart , elementStart , elementAttribute , elementClassProp , elementContainerEnd , elementContainerStart , elementEnd , elementProperty , element , elementStyling , elementStylingApply , elementStyleProp , embeddedViewEnd , embeddedViewStart , interpolation1 , interpolation2 , interpolation3 , interpolation4 , interpolation5 , interpolation6 , interpolation7 , interpolation8 , interpolationV , projection , projectionDef , reference , text , textBinding , template , elementStylingMap , directiveInject , elementHostAttrs } from '../../src/render3/instructions' ;
import { RenderFlags } from '../../src/render3/interfaces/definition' ;
2018-12-10 23:40:19 -08:00
import { RElement , Renderer3 , RendererFactory3 , domRendererFactory3 } from '../../src/render3/interfaces/renderer' ;
2018-10-08 16:04:46 -07:00
import { HEADER_OFFSET , CONTEXT } from '../../src/render3/interfaces/view' ;
2018-10-18 09:23:18 +02:00
import { enableBindings , disableBindings } from '../../src/render3/state' ;
2018-05-09 15:30:16 -07:00
import { sanitizeUrl } from '../../src/sanitization/sanitization' ;
import { Sanitizer , SecurityContext } from '../../src/sanitization/security' ;
2017-12-01 14:23:03 -08:00
2018-08-01 15:19:27 +02:00
import { NgIf } from './common_with_def' ;
2018-12-10 23:40:19 -08:00
import { ComponentFixture , MockRendererFactory , TemplateFixture , createComponent , renderToHtml } from './render_util' ;
2018-11-28 15:54:38 -08:00
import { getLContext } from '../../src/render3/context_discovery' ;
2018-09-28 12:38:16 -07:00
import { StylingIndex } from '../../src/render3/interfaces/styling' ;
2018-10-12 15:02:54 -07:00
import { MONKEY_PATCH_KEY_NAME } from '../../src/render3/interfaces/context' ;
2017-12-01 14:23:03 -08:00
2018-01-03 11:42:48 +01:00
describe ( 'render3 integration test' , () => {
2017-12-01 14:23:03 -08:00
describe ( 'render' , () => {
it ( 'should render basic template' , () => {
2018-08-16 18:53:21 -07:00
expect ( renderToHtml ( Template , {}, 2 )). toEqual ( '<span title="Hello">Greetings</span>' );
2017-12-01 14:23:03 -08:00
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'span' , [ 'title' , 'Hello' ]);
{ text ( 1 , 'Greetings' ); }
elementEnd ();
2017-12-01 14:23:03 -08:00
}
}
2018-04-14 11:52:53 -07:00
expect ( ngDevMode ). toHaveProperties ({
firstTemplatePass : 1 ,
2018-05-16 05:56:01 -07:00
tNode : 3 , // 1 for div, 1 for text, 1 for host element
2018-09-13 16:07:23 -07:00
tView : 2 , // 1 for root view, 1 for template
2018-04-14 11:52:53 -07:00
rendererCreateElement : 1 ,
});
2017-12-01 14:23:03 -08:00
});
it ( 'should render and update basic "Hello, World" template' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'h1' );
{ text ( 1 ); }
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
textBinding ( 1 , interpolation1 ( 'Hello, ' , ctx . name , '!' ));
2018-04-10 20:57:09 -07:00
}
2018-08-18 11:14:50 -07:00
}, 2 , 1 );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . name = 'World' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<h1>Hello, World!</h1>' );
fixture . component . name = 'New World' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<h1>Hello, New World!</h1>' );
2017-12-01 14:23:03 -08:00
});
});
describe ( 'text bindings' , () => {
it ( 'should render "undefined" as "" when used with `bind()`' , () => {
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , name : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
text ( 0 );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( name ));
}
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( Template , 'benoit' , 1 , 1 )). toEqual ( 'benoit' );
expect ( renderToHtml ( Template , undefined , 1 , 1 )). toEqual ( '' );
2018-05-15 22:07:20 +02:00
expect ( ngDevMode ). toHaveProperties ({
firstTemplatePass : 0 ,
2018-05-30 13:53:48 -07:00
tNode : 2 ,
2018-09-13 16:07:23 -07:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 22:07:20 +02:00
rendererSetText : 2 ,
});
2017-12-01 14:23:03 -08:00
});
it ( 'should render "null" as "" when used with `bind()`' , () => {
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , name : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
text ( 0 );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( name ));
}
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( Template , 'benoit' , 1 , 1 )). toEqual ( 'benoit' );
expect ( renderToHtml ( Template , null , 1 , 1 )). toEqual ( '' );
2018-05-15 22:07:20 +02:00
expect ( ngDevMode ). toHaveProperties ({
firstTemplatePass : 0 ,
2018-05-30 13:53:48 -07:00
tNode : 2 ,
2018-09-13 16:07:23 -07:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 22:07:20 +02:00
rendererSetText : 2 ,
});
2017-12-01 14:23:03 -08:00
});
it ( 'should support creation-time values in text nodes' , () => {
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , value : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
text ( 0 );
2018-12-18 16:58:51 -08:00
textBinding ( 0 , value );
2018-04-10 20:57:09 -07:00
}
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( Template , 'once' , 1 , 1 )). toEqual ( 'once' );
expect ( renderToHtml ( Template , 'twice' , 1 , 1 )). toEqual ( 'once' );
2018-05-15 22:07:20 +02:00
expect ( ngDevMode ). toHaveProperties ({
firstTemplatePass : 0 ,
2018-05-30 13:53:48 -07:00
tNode : 2 ,
2018-09-13 16:07:23 -07:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 22:07:20 +02:00
rendererSetText : 1 ,
});
2017-12-01 14:23:03 -08:00
});
});
2018-09-26 13:19:04 -07:00
describe ( 'ngNonBindable handling' , () => {
it ( 'should keep local ref for host element' , () => {
/**
* <b ngNonBindable #myRef id="my-id">
* <i>Hello {{ name }}!</i>
* </b>
* {{ myRef.id }}
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'b' , [ 'id' , 'my-id' ], [ 'myRef' , '' ]);
2018-09-26 21:09:03 -07:00
disableBindings ();
2018-09-26 14:33:48 -07:00
elementStart ( 2 , 'i' );
text ( 3 , 'Hello {{ name }}!' );
elementEnd ();
2018-09-26 21:09:03 -07:00
enableBindings ();
2018-09-26 13:19:04 -07:00
elementEnd ();
text ( 4 );
}
if ( rf & RenderFlags . Update ) {
const ref = reference ( 1 ) as any ;
2018-09-26 14:33:48 -07:00
textBinding ( 4 , interpolation1 ( ' ' , ref . id , ' ' ));
2018-09-26 13:19:04 -07:00
}
}, 5 , 1 );
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<b id="my-id"><i>Hello {{ name }}!</i></b> my-id ' );
});
it ( 'should invoke directives for host element' , () => {
let directiveInvoked : boolean = false ;
class TestDirective {
2018-09-26 14:33:48 -07:00
ngOnInit() { directiveInvoked = true ; }
2018-09-26 13:19:04 -07:00
static ngDirectiveDef = defineDirective ({
type : TestDirective ,
selectors : [[ '' , 'directive' , '' ]],
factory : () => new TestDirective ()
});
}
/**
* <b ngNonBindable directive>
* <i>Hello {{ name }}!</i>
* </b>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'b' , [ 'directive' , '' ]);
2018-09-26 21:09:03 -07:00
disableBindings ();
2018-09-26 14:33:48 -07:00
elementStart ( 1 , 'i' );
text ( 2 , 'Hello {{ name }}!' );
elementEnd ();
2018-09-26 21:09:03 -07:00
enableBindings ();
2018-09-26 13:19:04 -07:00
elementEnd ();
}
}, 3 , 0 , [ TestDirective ]);
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<b directive=""><i>Hello {{ name }}!</i></b>' );
expect ( directiveInvoked ). toEqual ( true );
});
it ( 'should not invoke directives for nested elements' , () => {
let directiveInvoked : boolean = false ;
class TestDirective {
2018-09-26 14:33:48 -07:00
ngOnInit() { directiveInvoked = true ; }
2018-09-26 13:19:04 -07:00
static ngDirectiveDef = defineDirective ({
type : TestDirective ,
selectors : [[ '' , 'directive' , '' ]],
factory : () => new TestDirective ()
});
}
/**
* <b ngNonBindable>
* <i directive>Hello {{ name }}!</i>
* </b>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'b' );
2018-09-26 21:09:03 -07:00
disableBindings ();
2018-09-26 14:33:48 -07:00
elementStart ( 1 , 'i' , [ 'directive' , '' ]);
text ( 2 , 'Hello {{ name }}!' );
elementEnd ();
2018-09-26 21:09:03 -07:00
enableBindings ();
2018-09-26 13:19:04 -07:00
elementEnd ();
}
}, 3 , 0 , [ TestDirective ]);
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<b><i directive="">Hello {{ name }}!</i></b>' );
expect ( directiveInvoked ). toEqual ( false );
});
});
2017-12-01 14:23:03 -08:00
describe ( 'Siblings update' , () => {
it ( 'should handle a flat list of static/bound text nodes' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
text ( 0 , 'Hello ' );
text ( 1 );
text ( 2 , '!' );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
textBinding ( 1 , bind ( ctx . name ));
2018-04-10 20:57:09 -07:00
}
2018-08-18 11:14:50 -07:00
}, 3 , 1 );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . name = 'world' ;
fixture . update ();
expect ( fixture . html ). toEqual ( 'Hello world!' );
fixture . component . name = 'monde' ;
fixture . update ();
expect ( fixture . html ). toEqual ( 'Hello monde!' );
2017-12-01 14:23:03 -08:00
});
it ( 'should handle a list of static/bound text nodes as element children' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'b' );
2017-12-01 14:23:03 -08:00
{
2018-02-06 16:11:20 -08:00
text ( 1 , 'Hello ' );
text ( 2 );
text ( 3 , '!' );
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
textBinding ( 2 , bind ( ctx . name ));
2018-04-10 20:57:09 -07:00
}
2018-08-18 11:14:50 -07:00
}, 4 , 1 );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . name = 'world' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b>Hello world!</b>' );
fixture . component . name = 'mundo' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b>Hello mundo!</b>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should render/update text node as a child of a deep list of elements' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'b' );
2017-12-01 14:23:03 -08:00
{
2018-02-06 16:11:20 -08:00
elementStart ( 1 , 'b' );
2017-12-01 14:23:03 -08:00
{
2018-02-06 16:11:20 -08:00
elementStart ( 2 , 'b' );
2017-12-01 14:23:03 -08:00
{
2018-02-06 16:11:20 -08:00
elementStart ( 3 , 'b' );
{ text ( 4 ); }
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
textBinding ( 4 , interpolation1 ( 'Hello ' , ctx . name , '!' ));
2018-04-10 20:57:09 -07:00
}
2018-08-18 11:14:50 -07:00
}, 5 , 1 );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . name = 'world' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b><b><b><b>Hello world!</b></b></b></b>' );
fixture . component . name = 'mundo' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b><b><b><b>Hello mundo!</b></b></b></b>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should update 2 sibling elements' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'b' );
2017-12-01 14:23:03 -08:00
{
2018-07-27 14:28:22 -07:00
element ( 1 , 'span' );
2018-02-06 16:11:20 -08:00
elementStart ( 2 , 'span' , [ 'class' , 'foo' ]);
2017-12-01 14:23:03 -08:00
{}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
elementAttribute ( 2 , 'id' , bind ( ctx . id ));
2018-04-10 20:57:09 -07:00
}
2018-08-18 11:14:50 -07:00
}, 3 , 1 );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . id = 'foo' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b><span></span><span class="foo" id="foo"></span></b>' );
fixture . component . id = 'bar' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<b><span></span><span class="foo" id="bar"></span></b>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should handle sibling text node after element with child text node' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'p' );
{ text ( 1 , 'hello' ); }
elementEnd ();
text ( 2 , 'world' );
2017-12-01 14:23:03 -08:00
}
2018-08-16 18:53:21 -07:00
}, 3 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<p>hello</p>world' );
2017-12-01 14:23:03 -08:00
});
});
describe ( 'basic components' , () => {
class TodoComponent {
value = ' one' ;
static ngComponentDef = defineComponent ({
2018-01-22 15:27:21 -08:00
type : TodoComponent ,
2018-03-29 16:41:45 -07:00
selectors : [[ 'todo' ]],
2018-08-16 18:53:21 -07:00
consts : 3 ,
2018-08-18 11:14:50 -07:00
vars : 1 ,
2018-04-10 20:57:09 -07:00
template : function TodoTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'p' );
2017-12-01 14:23:03 -08:00
{
2018-02-06 16:11:20 -08:00
text ( 1 , 'Todo' );
text ( 2 );
2017-12-01 14:23:03 -08:00
}
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
textBinding ( 2 , bind ( ctx . value ));
}
2017-12-01 14:23:03 -08:00
},
factory : () => new TodoComponent
});
}
2018-03-29 12:58:41 -07:00
const defs = [ TodoComponent ];
2018-03-25 21:32:39 -07:00
2017-12-01 14:23:03 -08:00
it ( 'should support a basic component template' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'todo' );
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , defs );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<todo><p>Todo one</p></todo>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should support a component template with sibling' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'todo' );
2018-03-21 15:10:34 -07:00
text ( 1 , 'two' );
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 2 , 0 , defs );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<todo><p>Todo one</p></todo>two' );
2017-12-01 14:23:03 -08:00
});
it ( 'should support a component template with component sibling' , () => {
/**
* <todo></todo>
* <todo></todo>
*/
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'todo' );
element ( 1 , 'todo' );
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 2 , 0 , defs );
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<todo><p>Todo one</p></todo><todo><p>Todo one</p></todo>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should support a component with binding on host element' , () => {
let cmptInstance : TodoComponentHostBinding | null ;
class TodoComponentHostBinding {
title = 'one' ;
static ngComponentDef = defineComponent ({
2018-01-22 15:27:21 -08:00
type : TodoComponentHostBinding ,
2018-03-29 16:41:45 -07:00
selectors : [[ 'todo' ]],
2018-08-16 18:53:21 -07:00
consts : 1 ,
2018-08-18 11:14:50 -07:00
vars : 1 ,
2017-12-01 14:23:03 -08:00
template : function TodoComponentHostBindingTemplate (
2018-04-10 20:57:09 -07:00
rf : RenderFlags , ctx : TodoComponentHostBinding ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
text ( 0 );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( ctx . title ));
}
2017-12-01 14:23:03 -08:00
},
factory : () => cmptInstance = new TodoComponentHostBinding ,
2018-11-20 15:20:19 -08:00
hostBindings : function ( rf : RenderFlags , ctx : any , elementIndex : number ) : void {
2018-11-27 12:05:26 -08:00
if ( rf & RenderFlags . Create ) {
allocHostVars ( 1 );
}
2018-11-20 15:20:19 -08:00
if ( rf & RenderFlags . Update ) {
// host bindings
elementProperty ( elementIndex , 'title' , bind ( ctx . title ));
}
2017-12-01 14:23:03 -08:00
}
});
}
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'todo' );
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , [ TodoComponentHostBinding ]);
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<todo title="one">one</todo>' );
2017-12-01 14:23:03 -08:00
cmptInstance ! . title = 'two' ;
2018-08-16 18:53:21 -07:00
fixture . update ();
expect ( fixture . html ). toEqual ( '<todo title="two">two</todo>' );
2017-12-01 14:23:03 -08:00
});
2018-03-16 20:31:24 -07:00
it ( 'should support root component with host attribute' , () => {
class HostAttributeComp {
static ngComponentDef = defineComponent ({
type : HostAttributeComp ,
2018-03-29 16:41:45 -07:00
selectors : [[ 'host-attr-comp' ]],
2018-03-16 20:31:24 -07:00
factory : () => new HostAttributeComp (),
2018-08-16 18:53:21 -07:00
consts : 0 ,
2018-08-18 11:14:50 -07:00
vars : 0 ,
2018-04-10 20:57:09 -07:00
template : ( rf : RenderFlags , ctx : HostAttributeComp ) => {},
2018-03-16 20:31:24 -07:00
attributes : [ 'role' , 'button' ]
});
}
const fixture = new ComponentFixture ( HostAttributeComp );
expect ( fixture . hostElement . getAttribute ( 'role' )). toEqual ( 'button' );
});
2017-12-01 14:23:03 -08:00
it ( 'should support component with bindings in template' , () => {
/** <p> {{ name }} </p>*/
class MyComp {
name = 'Bess' ;
static ngComponentDef = defineComponent ({
2018-01-22 15:27:21 -08:00
type : MyComp ,
2018-03-29 16:41:45 -07:00
selectors : [[ 'comp' ]],
2018-08-16 18:53:21 -07:00
consts : 2 ,
2018-08-18 11:14:50 -07:00
vars : 1 ,
2018-04-10 20:57:09 -07:00
template : function MyCompTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'p' );
{ text ( 1 ); }
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
textBinding ( 1 , bind ( ctx . name ));
}
2017-12-01 14:23:03 -08:00
},
factory : () => new MyComp
});
}
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'comp' );
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , [ MyComp ]);
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '<comp><p>Bess</p></comp>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should support a component with sub-views' , () => {
/**
* % if (condition) {
* <div>text</div>
* % }
*/
class MyComp {
2018-06-18 16:38:33 -07:00
// TODO(issue/24571): remove '!'.
condition !: boolean ;
2017-12-01 14:23:03 -08:00
static ngComponentDef = defineComponent ({
2018-01-22 15:27:21 -08:00
type : MyComp ,
2018-03-29 16:41:45 -07:00
selectors : [[ 'comp' ]],
2018-08-16 18:53:21 -07:00
consts : 1 ,
2018-08-18 11:14:50 -07:00
vars : 0 ,
2018-04-10 20:57:09 -07:00
template : function MyCompTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
container ( 0 );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
{
if ( ctx . condition ) {
2018-08-18 11:14:50 -07:00
let rf1 = embeddedViewStart ( 0 , 2 , 0 );
2018-04-10 20:57:09 -07:00
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'div' );
{ text ( 1 , 'text' ); }
elementEnd ();
}
embeddedViewEnd ();
2017-12-01 14:23:03 -08:00
}
}
2018-04-10 20:57:09 -07:00
containerRefreshEnd ();
2017-12-01 14:23:03 -08:00
}
},
factory : () => new MyComp ,
inputs : { condition : 'condition' }
});
}
/** <comp [condition]="condition"></comp> */
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'comp' );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'condition' , bind ( ctx . condition ));
}
2018-08-18 11:14:50 -07:00
}, 1 , 1 , [ MyComp ]);
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . condition = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<comp><div>text</div></comp>' );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
fixture . component . condition = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<comp></comp>' );
2017-12-01 14:23:03 -08:00
});
});
2018-07-26 17:22:41 +02:00
describe ( 'ng-container' , () => {
it ( 'should insert as a child of a regular element' , () => {
/**
* <div>before|<ng-container>Greetings<span></span></ng-container>|after</div>
*/
function Template() {
elementStart ( 0 , 'div' );
{
text ( 1 , 'before|' );
elementContainerStart ( 2 );
{
text ( 3 , 'Greetings' );
element ( 4 , 'span' );
}
elementContainerEnd ();
text ( 5 , '|after' );
}
elementEnd ();
}
2018-08-16 18:53:21 -07:00
const fixture = new TemplateFixture ( Template , () => {}, 6 );
2018-07-26 17:22:41 +02:00
expect ( fixture . html ). toEqual ( '<div>before|Greetings<span></span>|after</div>' );
});
2018-08-01 15:19:27 +02:00
it ( 'should add and remove DOM nodes when ng-container is a child of a regular element' , () => {
/**
* {% if (value) { %}
* <div>
* <ng-container>content</ng-container>
* </div>
* {% } %}
*/
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags , ctx : { value : any }) {
if ( rf & RenderFlags . Create ) {
container ( 0 );
}
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
if ( ctx . value ) {
2018-08-18 11:14:50 -07:00
let rf1 = embeddedViewStart ( 0 , 3 , 0 );
2018-08-01 15:19:27 +02:00
{
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'div' );
{
elementContainerStart ( 1 );
{ text ( 2 , 'content' ); }
elementContainerEnd ();
}
elementEnd ();
}
}
embeddedViewEnd ();
}
containerRefreshEnd ();
}
2018-08-16 18:53:21 -07:00
}, 1 );
2018-08-01 15:19:27 +02:00
const fixture = new ComponentFixture ( TestCmpt );
expect ( fixture . html ). toEqual ( '' );
fixture . component . value = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<div>content</div>' );
fixture . component . value = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
it ( 'should add and remove DOM nodes when ng-container is a child of an embedded view (JS block)' ,
() => {
/**
* {% if (value) { %}
* <ng-container>content</ng-container>
* {% } %}
*/
const TestCmpt =
createComponent ( 'test-cmpt' , function ( rf : RenderFlags , ctx : { value : any }) {
if ( rf & RenderFlags . Create ) {
container ( 0 );
}
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
if ( ctx . value ) {
2018-08-18 11:14:50 -07:00
let rf1 = embeddedViewStart ( 0 , 2 , 0 );
2018-08-01 15:19:27 +02:00
{
if ( rf1 & RenderFlags . Create ) {
elementContainerStart ( 0 );
{ text ( 1 , 'content' ); }
elementContainerEnd ();
}
}
embeddedViewEnd ();
}
containerRefreshEnd ();
}
2018-08-16 18:53:21 -07:00
}, 1 );
2018-08-01 15:19:27 +02:00
const fixture = new ComponentFixture ( TestCmpt );
expect ( fixture . html ). toEqual ( '' );
fixture . component . value = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( 'content' );
fixture . component . value = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
it ( 'should add and remove DOM nodes when ng-container is a child of an embedded view (ViewContainerRef)' ,
() => {
function ngIfTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
{ text ( 1 , 'content' ); }
elementContainerEnd ();
}
}
/**
* <ng-container *ngIf="value">content</ng-container>
*/
// equivalent to:
/**
* <ng-template [ngIf]="value">
* <ng-container>
* content
* </ng-container>
* </ng-template>
*/
const TestCmpt =
createComponent ( 'test-cmpt' , function ( rf : RenderFlags , ctx : { value : any }) {
if ( rf & RenderFlags . Create ) {
2018-12-12 15:23:12 -08:00
template (
0 , ngIfTemplate , 2 , 0 , 'ng-template' , [ AttributeMarker . SelectOnly , 'ngIf' ]);
2018-08-01 15:19:27 +02:00
}
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'ngIf' , bind ( ctx . value ));
}
2018-08-18 11:14:50 -07:00
}, 1 , 1 , [ NgIf ]);
2018-08-01 15:19:27 +02:00
const fixture = new ComponentFixture ( TestCmpt );
expect ( fixture . html ). toEqual ( '' );
fixture . component . value = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( 'content' );
fixture . component . value = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
2018-08-06 16:40:16 +02:00
// https://stackblitz.com/edit/angular-tfhcz1?file=src%2Fapp%2Fapp.component.ts
it ( 'should add and remove DOM nodes when ng-container is a child of a delayed embedded view' ,
() => {
class TestDirective {
constructor ( private _tplRef : TemplateRef < any >, private _vcRef : ViewContainerRef ) {}
createAndInsert() { this . _vcRef . insert ( this . _tplRef . createEmbeddedView ({})); }
clear() { this . _vcRef . clear (); }
static ngDirectiveDef = defineDirective ({
type : TestDirective ,
selectors : [[ '' , 'testDirective' , '' ]],
2018-09-21 18:38:13 -07:00
factory :
2018-10-08 16:04:46 -07:00
() => testDirective = new TestDirective (
2018-09-21 18:38:13 -07:00
directiveInject ( TemplateRef as any ), directiveInject ( ViewContainerRef as any )),
2018-08-06 16:40:16 +02:00
});
}
function embeddedTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
{ text ( 1 , 'content' ); }
elementContainerEnd ();
}
}
let testDirective : TestDirective ;
`<ng-template testDirective>
<ng-container>
content
</ng-container>
</ng-template>` ;
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
2018-08-18 11:14:50 -07:00
template (
2018-12-12 15:23:12 -08:00
0 , embeddedTemplate , 2 , 0 , 'ng-template' ,
[ AttributeMarker . SelectOnly , 'testDirective' ]);
2018-08-06 16:40:16 +02:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , [ TestDirective ]);
2018-08-06 16:40:16 +02:00
const fixture = new ComponentFixture ( TestCmpt );
expect ( fixture . html ). toEqual ( '' );
testDirective ! . createAndInsert ();
fixture . update ();
expect ( fixture . html ). toEqual ( 'content' );
testDirective ! . clear ();
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
2018-08-01 15:19:27 +02:00
it ( 'should render at the component view root' , () => {
/**
* <ng-container>component template</ng-container>
*/
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
{ text ( 1 , 'component template' ); }
elementContainerEnd ();
}
2018-08-16 18:53:21 -07:00
}, 2 );
2018-08-01 15:19:27 +02:00
function App() { element ( 0 , 'test-cmpt' ); }
2018-08-18 11:14:50 -07:00
const fixture = new TemplateFixture ( App , () => {}, 1 , 0 , [ TestCmpt ]);
2018-08-01 15:19:27 +02:00
expect ( fixture . html ). toEqual ( '<test-cmpt>component template</test-cmpt>' );
});
2018-08-06 17:46:49 +02:00
it ( 'should render inside another ng-container' , () => {
/**
* <ng-container>
* <ng-container>
* <ng-container>
* content
* </ng-container>
* </ng-container>
* </ng-container>
*/
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
{
elementContainerStart ( 1 );
{
elementContainerStart ( 2 );
{ text ( 3 , 'content' ); }
elementContainerEnd ();
}
elementContainerEnd ();
}
elementContainerEnd ();
}
2018-08-16 18:53:21 -07:00
}, 4 );
2018-08-06 17:46:49 +02:00
function App() { element ( 0 , 'test-cmpt' ); }
2018-08-18 11:14:50 -07:00
const fixture = new TemplateFixture ( App , () => {}, 1 , 0 , [ TestCmpt ]);
2018-08-06 17:46:49 +02:00
expect ( fixture . html ). toEqual ( '<test-cmpt>content</test-cmpt>' );
});
it ( 'should render inside another ng-container at the root of a delayed view' , () => {
2018-10-08 16:04:46 -07:00
let testDirective : TestDirective ;
2018-08-06 17:46:49 +02:00
class TestDirective {
constructor ( private _tplRef : TemplateRef < any >, private _vcRef : ViewContainerRef ) {}
createAndInsert() { this . _vcRef . insert ( this . _tplRef . createEmbeddedView ({})); }
clear() { this . _vcRef . clear (); }
static ngDirectiveDef = defineDirective ({
type : TestDirective ,
selectors : [[ '' , 'testDirective' , '' ]],
2018-09-21 18:38:13 -07:00
factory :
2018-10-08 16:04:46 -07:00
() => testDirective = new TestDirective (
2018-09-21 18:38:13 -07:00
directiveInject ( TemplateRef as any ), directiveInject ( ViewContainerRef as any )),
2018-08-06 17:46:49 +02:00
});
}
function embeddedTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
{
elementContainerStart ( 1 );
{
elementContainerStart ( 2 );
{ text ( 3 , 'content' ); }
elementContainerEnd ();
}
elementContainerEnd ();
}
elementContainerEnd ();
}
}
2018-08-16 18:53:21 -07:00
/**
* <ng-template testDirective>
* <ng-container>
* <ng-container>
* <ng-container>
* content
* </ng-container>
* </ng-container>
* </ng-container>
* </ng-template>
*/
2018-08-06 17:46:49 +02:00
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
2018-12-12 15:23:12 -08:00
template (
0 , embeddedTemplate , 4 , 0 , 'ng-template' ,
[ AttributeMarker . SelectOnly , 'testDirective' ]);
2018-08-06 17:46:49 +02:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , [ TestDirective ]);
2018-08-06 17:46:49 +02:00
function App() { element ( 0 , 'test-cmpt' ); }
const fixture = new ComponentFixture ( TestCmpt );
expect ( fixture . html ). toEqual ( '' );
testDirective ! . createAndInsert ();
fixture . update ();
expect ( fixture . html ). toEqual ( 'content' );
testDirective ! . createAndInsert ();
fixture . update ();
expect ( fixture . html ). toEqual ( 'contentcontent' );
testDirective ! . clear ();
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
2018-07-26 17:22:41 +02:00
it ( 'should support directives and inject ElementRef' , () => {
class Directive {
constructor ( public elRef : ElementRef ) {}
static ngDirectiveDef = defineDirective ({
type : Directive ,
selectors : [[ '' , 'dir' , '' ]],
2018-10-08 16:04:46 -07:00
factory : () => directive = new Directive ( directiveInject ( ElementRef )),
2018-07-26 17:22:41 +02:00
});
}
let directive : Directive ;
/**
* <div><ng-container dir></ng-container></div>
*/
function Template() {
elementStart ( 0 , 'div' );
{
elementContainerStart ( 1 , [ AttributeMarker . SelectOnly , 'dir' ]);
elementContainerEnd ();
}
elementEnd ();
}
2018-08-18 11:14:50 -07:00
const fixture = new TemplateFixture ( Template , () => {}, 2 , 0 , [ Directive ]);
2018-07-26 17:22:41 +02:00
expect ( fixture . html ). toEqual ( '<div></div>' );
expect ( directive ! . elRef . nativeElement . nodeType ). toBe ( Node . COMMENT_NODE );
});
2018-10-22 14:10:42 +02:00
it ( 'should support ViewContainerRef when ng-container is at the root of a view' , () => {
function ContentTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
text ( 0 , 'Content' );
}
}
class Directive {
contentTpl : TemplateRef < {} >| null = null ;
constructor ( private _vcRef : ViewContainerRef ) {}
insertView() { this . _vcRef . createEmbeddedView ( this . contentTpl as TemplateRef < {} > ); }
clear() { this . _vcRef . clear (); }
static ngDirectiveDef = defineDirective ({
type : Directive ,
selectors : [[ '' , 'dir' , '' ]],
factory : () => directive = new Directive ( directiveInject ( ViewContainerRef as any )),
inputs : { contentTpl : 'contentTpl' },
});
}
let directive : Directive ;
/**
* <ng-container dir [contentTpl]="content">
* <ng-template #content>Content</ng-template>
* </ng-container>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 , [ AttributeMarker . SelectOnly , 'dir' ]);
2018-12-12 15:23:12 -08:00
template (
1 , ContentTemplate , 1 , 0 , 'ng-template' , null , [ 'content' , '' ], templateRefExtractor );
2018-10-22 14:10:42 +02:00
elementContainerEnd ();
}
if ( rf & RenderFlags . Update ) {
const content = reference ( 2 ) as any ;
elementProperty ( 0 , 'contentTpl' , bind ( content ));
}
}, 3 , 1 , [ Directive ]);
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '' );
directive ! . insertView ();
fixture . update ();
expect ( fixture . html ). toEqual ( 'Content' );
directive ! . clear ();
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
it ( 'should support ViewContainerRef on <ng-template> inside <ng-container>' , () => {
function ContentTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
text ( 0 , 'Content' );
}
}
class Directive {
constructor ( private _tplRef : TemplateRef < {} > , private _vcRef : ViewContainerRef ) {}
insertView() { this . _vcRef . createEmbeddedView ( this . _tplRef ); }
clear() { this . _vcRef . clear (); }
static ngDirectiveDef = defineDirective ({
type : Directive ,
selectors : [[ '' , 'dir' , '' ]],
factory :
() => directive = new Directive (
directiveInject ( TemplateRef as any ), directiveInject ( ViewContainerRef as any )),
});
}
let directive : Directive ;
/**
* <ng-container>
* <ng-template dir>Content</ng-template>
* </ng-container>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 );
template (
2018-12-12 15:23:12 -08:00
1 , ContentTemplate , 1 , 0 , 'ng-template' , [ AttributeMarker . SelectOnly , 'dir' ], [],
2018-10-22 14:10:42 +02:00
templateRefExtractor );
elementContainerEnd ();
}
}, 2 , 0 , [ Directive ]);
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( '' );
directive ! . insertView ();
fixture . update ();
expect ( fixture . html ). toEqual ( 'Content' );
directive ! . clear ();
fixture . update ();
expect ( fixture . html ). toEqual ( '' );
});
2018-07-26 17:22:41 +02:00
it ( 'should not set any attributes' , () => {
/**
* <div><ng-container id="foo"></ng-container></div>
*/
function Template() {
elementStart ( 0 , 'div' );
{
elementContainerStart ( 1 , [ 'id' , 'foo' ]);
elementContainerEnd ();
}
elementEnd ();
}
2018-08-16 18:53:21 -07:00
const fixture = new TemplateFixture ( Template , () => {}, 2 );
2018-07-26 17:22:41 +02:00
expect ( fixture . html ). toEqual ( '<div></div>' );
});
});
2018-01-25 15:32:21 +01:00
describe ( 'tree' , () => {
interface Tree {
beforeLabel? : string ;
subTrees? : Tree [];
afterLabel? : string ;
}
interface ParentCtx {
beforeTree : Tree ;
projectedTree : Tree ;
afterTree : Tree ;
}
2018-04-10 20:57:09 -07:00
function showLabel ( rf : RenderFlags , ctx : { label : string | undefined }) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
container ( 0 );
2018-01-25 15:32:21 +01:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
{
if ( ctx . label != null ) {
2018-08-18 11:14:50 -07:00
let rf1 = embeddedViewStart ( 0 , 1 , 1 );
2018-04-10 20:57:09 -07:00
if ( rf1 & RenderFlags . Create ) {
text ( 0 );
}
if ( rf1 & RenderFlags . Update ) {
textBinding ( 0 , bind ( ctx . label ));
}
embeddedViewEnd ();
2018-01-25 15:32:21 +01:00
}
}
2018-04-10 20:57:09 -07:00
containerRefreshEnd ();
2018-01-25 15:32:21 +01:00
}
}
2018-04-10 20:57:09 -07:00
function showTree ( rf : RenderFlags , ctx : { tree : Tree }) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
container ( 0 );
container ( 1 );
container ( 2 );
2018-01-25 15:32:21 +01:00
}
2018-08-18 11:14:50 -07:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
{
const rf0 = embeddedViewStart ( 0 , 1 , 0 );
{ showLabel ( rf0 , { label : ctx.tree.beforeLabel }); }
2018-02-06 17:27:16 -08:00
embeddedViewEnd ();
2018-01-25 15:32:21 +01:00
}
2018-08-18 11:14:50 -07:00
containerRefreshEnd ();
containerRefreshStart ( 1 );
{
for ( let subTree of ctx . tree . subTrees || []) {
2018-08-21 00:03:21 -07:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 );
2018-08-18 11:14:50 -07:00
{ showTree ( rf0 , { tree : subTree }); }
embeddedViewEnd ();
}
}
containerRefreshEnd ();
containerRefreshStart ( 2 );
{
const rf0 = embeddedViewStart ( 0 , 1 , 0 );
{ showLabel ( rf0 , { label : ctx.tree.afterLabel }); }
embeddedViewEnd ();
}
containerRefreshEnd ();
2018-01-25 15:32:21 +01:00
}
}
class ChildComponent {
2018-06-18 16:38:33 -07:00
// TODO(issue/24571): remove '!'.
beforeTree !: Tree ;
// TODO(issue/24571): remove '!'.
afterTree !: Tree ;
2018-01-25 15:32:21 +01:00
static ngComponentDef = defineComponent ({
2018-03-29 16:41:45 -07:00
selectors : [[ 'child' ]],
2018-01-25 15:32:21 +01:00
type : ChildComponent ,
2018-08-16 18:53:21 -07:00
consts : 3 ,
2018-08-18 11:14:50 -07:00
vars : 0 ,
2018-01-25 15:32:21 +01:00
template : function ChildComponentTemplate (
2018-04-10 20:57:09 -07:00
rf : RenderFlags , ctx : { beforeTree : Tree , afterTree : Tree }) {
if ( rf & RenderFlags . Create ) {
2018-07-03 20:04:36 -07:00
projectionDef ();
container ( 0 );
projection ( 1 );
container ( 2 );
2018-01-25 15:32:21 +01:00
}
2018-08-18 11:14:50 -07:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
{
2018-08-21 00:03:21 -07:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 );
2018-08-18 11:14:50 -07:00
{ showTree ( rf0 , { tree : ctx.beforeTree }); }
embeddedViewEnd ();
}
containerRefreshEnd ();
containerRefreshStart ( 2 );
{
2018-08-21 00:03:21 -07:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 );
2018-08-18 11:14:50 -07:00
{ showTree ( rf0 , { tree : ctx.afterTree }); }
embeddedViewEnd ();
}
containerRefreshEnd ();
2018-01-25 15:32:21 +01:00
}
},
factory : () => new ChildComponent ,
inputs : { beforeTree : 'beforeTree' , afterTree : 'afterTree' }
});
}
2018-04-10 20:57:09 -07:00
function parentTemplate ( rf : RenderFlags , ctx : ParentCtx ) {
if ( rf & RenderFlags . Create ) {
2018-03-25 21:32:39 -07:00
elementStart ( 0 , 'child' );
2018-03-21 15:10:34 -07:00
{ container ( 1 ); }
2018-02-06 16:11:20 -08:00
elementEnd ();
2018-01-25 15:32:21 +01:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'beforeTree' , bind ( ctx . beforeTree ));
elementProperty ( 0 , 'afterTree' , bind ( ctx . afterTree ));
containerRefreshStart ( 1 );
{
2018-08-21 00:03:21 -07:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 );
2018-04-10 20:57:09 -07:00
{ showTree ( rf0 , { tree : ctx.projectedTree }); }
embeddedViewEnd ();
}
containerRefreshEnd ();
2018-01-25 15:32:21 +01:00
}
}
it ( 'should work with a tree' , () => {
const ctx : ParentCtx = {
beforeTree : { subTrees : [{ beforeLabel : 'a' }]},
projectedTree : { beforeLabel : 'p' },
afterTree : { afterLabel : 'z' }
};
2018-03-29 12:58:41 -07:00
const defs = [ ChildComponent ];
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs )). toEqual ( '<child>apz</child>' );
2018-01-25 15:32:21 +01:00
ctx . projectedTree = { subTrees : [{}, {}, { subTrees : [{}, {}]}, {}]};
ctx . beforeTree . subTrees ! . push ({ afterLabel : 'b' });
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs )). toEqual ( '<child>abz</child>' );
2018-01-25 15:32:21 +01:00
ctx . projectedTree . subTrees ! [ 1 ]. afterLabel = 'h' ;
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs )). toEqual ( '<child>abhz</child>' );
2018-01-25 15:32:21 +01:00
ctx . beforeTree . subTrees ! . push ({ beforeLabel : 'c' });
2018-08-18 11:14:50 -07:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs )). toEqual ( '<child>abchz</child>' );
2018-01-25 15:32:21 +01:00
// To check the context easily:
// console.log(JSON.stringify(ctx));
});
});
2017-12-01 14:23:03 -08:00
describe ( 'element bindings' , () => {
describe ( 'elementAttribute' , () => {
it ( 'should support attribute bindings' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'span' );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ));
}
2018-08-18 11:14:50 -07:00
}, 1 , 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . title = 'Hello' ;
fixture . update ();
2017-12-01 14:23:03 -08:00
// initial binding
2018-08-16 18:53:21 -07:00
expect ( fixture . html ). toEqual ( '<span title="Hello"></span>' );
2017-12-01 14:23:03 -08:00
// update binding
2018-08-16 18:53:21 -07:00
fixture . component . title = 'Hi!' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span title="Hi!"></span>' );
2017-12-01 14:23:03 -08:00
// remove attribute
2018-08-16 18:53:21 -07:00
fixture . component . title = null ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span></span>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should stringify values used attribute bindings' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'span' );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ));
}
2018-08-18 11:14:50 -07:00
}, 1 , 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . title = NaN ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span title="NaN"></span>' );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
fixture . component . title = { toString : () => 'Custom toString' };
fixture . update ();
expect ( fixture . html ). toEqual ( '<span title="Custom toString"></span>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should update bindings' , () => {
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , c : any ) {
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'b' );
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'a' , interpolationV ( c ));
elementAttribute ( 0 , 'a0' , bind ( c [ 1 ]));
elementAttribute ( 0 , 'a1' , interpolation1 ( c [ 0 ], c [ 1 ], c [ 16 ]));
elementAttribute ( 0 , 'a2' , interpolation2 ( c [ 0 ], c [ 1 ], c [ 2 ], c [ 3 ], c [ 16 ]));
elementAttribute ( 0 , 'a3' , interpolation3 ( c [ 0 ], c [ 1 ], c [ 2 ], c [ 3 ], c [ 4 ], c [ 5 ], c [ 16 ]));
elementAttribute (
0 , 'a4' , interpolation4 ( c [ 0 ], c [ 1 ], c [ 2 ], c [ 3 ], c [ 4 ], c [ 5 ], c [ 6 ], c [ 7 ], c [ 16 ]));
elementAttribute (
0 , 'a5' ,
interpolation5 ( c [ 0 ], c [ 1 ], c [ 2 ], c [ 3 ], c [ 4 ], c [ 5 ], c [ 6 ], c [ 7 ], c [ 8 ], c [ 9 ], c [ 16 ]));
elementAttribute (
0 , 'a6' , interpolation6 (
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 ]));
elementAttribute (
0 , 'a7' , interpolation7 (
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 ]));
elementAttribute (
0 , 'a8' , interpolation8 (
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 ]));
}
2017-12-01 14:23:03 -08:00
}
let args = [ '(' , 0 , 'a' , 1 , 'b' , 2 , 'c' , 3 , 'd' , 4 , 'e' , 5 , 'f' , 6 , 'g' , 7 , ')' ];
2018-08-21 00:03:21 -07:00
expect ( renderToHtml ( Template , args , 1 , 54 ))
2017-12-01 14:23:03 -08:00
. toEqual (
'<b a="(0a1b2c3d4e5f6g7)" a0="0" a1="(0)" a2="(0a1)" a3="(0a1b2)" a4="(0a1b2c3)" a5="(0a1b2c3d4)" a6="(0a1b2c3d4e5)" a7="(0a1b2c3d4e5f6)" a8="(0a1b2c3d4e5f6g7)"></b>' );
args = args . reverse ();
2018-08-21 00:03:21 -07:00
expect ( renderToHtml ( Template , args , 1 , 54 ))
2017-12-01 14:23:03 -08:00
. toEqual (
'<b a=")7g6f5e4d3c2b1a0(" a0="7" a1=")7(" a2=")7g6(" a3=")7g6f5(" a4=")7g6f5e4(" a5=")7g6f5e4d3(" a6=")7g6f5e4d3c2(" a7=")7g6f5e4d3c2b1(" a8=")7g6f5e4d3c2b1a0("></b>' );
args = args . reverse ();
2018-08-21 00:03:21 -07:00
expect ( renderToHtml ( Template , args , 1 , 54 ))
2017-12-01 14:23:03 -08:00
. toEqual (
'<b a="(0a1b2c3d4e5f6g7)" a0="0" a1="(0)" a2="(0a1)" a3="(0a1b2)" a4="(0a1b2c3)" a5="(0a1b2c3d4)" a6="(0a1b2c3d4e5)" a7="(0a1b2c3d4e5f6)" a8="(0a1b2c3d4e5f6g7)"></b>' );
});
it ( 'should not update DOM if context has not changed' , () => {
const ctx : { title : string | null } = { title : 'Hello' };
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'span' );
container ( 1 );
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ));
containerRefreshStart ( 1 );
{
if ( true ) {
2018-08-21 00:03:21 -07:00
let rf1 = embeddedViewStart ( 1 , 1 , 1 );
2018-04-10 20:57:09 -07:00
{
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'b' );
{}
elementEnd ();
}
2018-08-21 00:03:21 -07:00
if ( rf1 & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ));
}
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
embeddedViewEnd ();
2017-12-01 14:23:03 -08:00
}
}
2018-04-10 20:57:09 -07:00
containerRefreshEnd ();
2017-12-01 14:23:03 -08:00
}
2018-08-18 11:14:50 -07:00
}, 2 , 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . title = 'Hello' ;
fixture . update ();
2017-12-01 14:23:03 -08:00
// initial binding
2018-08-16 18:53:21 -07:00
expect ( fixture . html ). toEqual ( '<span title="Hello"><b title="Hello"></b></span>' );
2017-12-01 14:23:03 -08:00
// update DOM manually
2018-08-16 18:53:21 -07:00
fixture . hostElement . querySelector ( 'b' ) ! . setAttribute ( 'title' , 'Goodbye' );
2017-12-01 14:23:03 -08:00
// refresh with same binding
2018-08-16 18:53:21 -07:00
fixture . update ();
expect ( fixture . html ). toEqual ( '<span title="Hello"><b title="Goodbye"></b></span>' );
2017-12-01 14:23:03 -08:00
// refresh again with same binding
2018-08-16 18:53:21 -07:00
fixture . update ();
expect ( fixture . html ). toEqual ( '<span title="Hello"><b title="Goodbye"></b></span>' );
2017-12-01 14:23:03 -08:00
});
2018-02-16 11:36:50 -08:00
it ( 'should support host attribute bindings' , () => {
let hostBindingDir : HostBindingDir ;
class HostBindingDir {
/* @HostBinding('attr.aria-label') */
label = 'some label' ;
static ngDirectiveDef = defineDirective ({
type : HostBindingDir ,
2018-03-29 16:41:45 -07:00
selectors : [[ '' , 'hostBindingDir' , '' ]],
2018-02-16 11:36:50 -08:00
factory : function HostBindingDir_Factory() {
return hostBindingDir = new HostBindingDir ();
},
2018-11-20 15:20:19 -08:00
hostBindings : function HostBindingDir_HostBindings (
rf : RenderFlags , ctx : any , elIndex : number ) {
2018-11-27 12:05:26 -08:00
if ( rf & RenderFlags . Create ) {
allocHostVars ( 1 );
}
2018-11-20 15:20:19 -08:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( elIndex , 'aria-label' , bind ( ctx . label ));
}
2018-02-16 11:36:50 -08:00
}
});
}
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'div' , [ 'hostBindingDir' , '' ]);
2018-02-16 11:36:50 -08:00
}
2018-08-18 11:14:50 -07:00
}, 1 , 0 , [ HostBindingDir ]);
2018-02-16 11:36:50 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
expect ( fixture . html ). toEqual ( `<div aria-label="some label" hostbindingdir=""></div>` );
2018-02-16 11:36:50 -08:00
hostBindingDir ! . label = 'other label' ;
2018-08-16 18:53:21 -07:00
fixture . update ();
expect ( fixture . html ). toEqual ( `<div aria-label="other label" hostbindingdir=""></div>` );
2018-02-16 11:36:50 -08:00
});
2017-12-01 14:23:03 -08:00
});
describe ( 'elementStyle' , () => {
it ( 'should support binding to styles' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'span' );
2018-07-11 10:58:18 -07:00
elementStyling ( null , [ 'border-color' ]);
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
elementStyleProp ( 0 , 0 , ctx . color );
2018-07-11 09:56:47 -07:00
elementStylingApply ( 0 );
2018-04-10 20:57:09 -07:00
}
2018-08-16 18:53:21 -07:00
}, 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . color = 'red' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span style="border-color: red;"></span>' );
fixture . component . color = 'green' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span style="border-color: green;"></span>' );
fixture . component . color = null ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span></span>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should support binding to styles with suffix' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'span' );
2018-07-11 10:58:18 -07:00
elementStyling ( null , [ 'font-size' ]);
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
elementStyleProp ( 0 , 0 , ctx . time , 'px' );
2018-07-11 09:56:47 -07:00
elementStylingApply ( 0 );
2018-04-10 20:57:09 -07:00
}
2018-08-16 18:53:21 -07:00
}, 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . time = '100' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span style="font-size: 100px;"></span>' );
fixture . component . time = 200 ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span style="font-size: 200px;"></span>' );
2018-11-26 14:16:18 +01:00
fixture . component . time = 0 ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span style="font-size: 0px;"></span>' );
2018-08-16 18:53:21 -07:00
fixture . component . time = null ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span></span>' );
2017-12-01 14:23:03 -08:00
});
});
2018-12-13 15:51:47 -08:00
describe ( 'class-based styling' , () => {
2017-12-01 14:23:03 -08:00
it ( 'should support CSS class toggle' , () => {
2018-10-11 13:13:57 -07:00
/** <span [class.active]="class"></span> */
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
elementStart ( 0 , 'span' );
2018-07-11 10:58:18 -07:00
elementStyling ([ 'active' ]);
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
elementClassProp ( 0 , 0 , ctx . class );
2018-07-11 09:56:47 -07:00
elementStylingApply ( 0 );
2018-04-10 20:57:09 -07:00
}
2018-08-16 18:53:21 -07:00
}, 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . class = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class="active"></span>' );
fixture . component . class = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class=""></span>' );
2017-12-01 14:23:03 -08:00
// truthy values
2018-08-16 18:53:21 -07:00
fixture . component . class = 'a_string' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class="active"></span>' );
fixture . component . class = 10 ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class="active"></span>' );
2017-12-01 14:23:03 -08:00
// falsy values
2018-08-16 18:53:21 -07:00
fixture . component . class = '' ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class=""></span>' );
fixture . component . class = 0 ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class=""></span>' );
2017-12-01 14:23:03 -08:00
});
it ( 'should work correctly with existing static classes' , () => {
2018-08-16 18:53:21 -07:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Create ) {
2018-12-13 15:51:47 -08:00
elementStart ( 0 , 'span' , [ AttributeMarker . Classes , 'existing' ]);
elementStyling ([ 'existing' , 'active' ]);
2018-02-06 16:11:20 -08:00
elementEnd ();
2017-12-01 14:23:03 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
2018-08-16 18:53:21 -07:00
elementClassProp ( 0 , 1 , ctx . class );
2018-07-11 09:56:47 -07:00
elementStylingApply ( 0 );
2018-04-10 20:57:09 -07:00
}
2018-08-16 18:53:21 -07:00
}, 1 );
2017-12-01 14:23:03 -08:00
2018-08-16 18:53:21 -07:00
const fixture = new ComponentFixture ( App );
fixture . component . class = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class="existing active"></span>' );
fixture . component . class = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<span class="existing"></span>' );
2017-12-01 14:23:03 -08:00
});
2018-10-11 13:13:57 -07:00
2018-10-12 15:02:54 -07:00
it ( 'should apply classes properly when nodes are components' , () => {
const MyComp = createComponent ( 'my-comp' , ( rf : RenderFlags , ctx : any ) => {
if ( rf & RenderFlags . Create ) {
text ( 0 , 'Comp Content' );
}
}, 1 , 0 , []);
/**
* <my-comp [class.active]="class"></my-comp>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'my-comp' );
2018-12-13 15:51:47 -08:00
elementStyling ([ 'active' ]);
2018-10-12 15:02:54 -07:00
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
elementClassProp ( 0 , 0 , ctx . class );
elementStylingApply ( 0 );
}
}, 1 , 0 , [ MyComp ]);
const fixture = new ComponentFixture ( App );
fixture . component . class = true ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<my-comp class="active">Comp Content</my-comp>' );
fixture . component . class = false ;
fixture . update ();
expect ( fixture . html ). toEqual ( '<my-comp class="">Comp Content</my-comp>' );
});
2018-10-11 13:13:57 -07:00
it ( 'should apply classes properly when nodes have LContainers' , () => {
let structuralComp !: StructuralComp ;
class StructuralComp {
tmp !: TemplateRef < any >;
constructor ( public vcr : ViewContainerRef ) {}
create() { this . vcr . createEmbeddedView ( this . tmp ); }
static ngComponentDef = defineComponent ({
type : StructuralComp ,
selectors : [[ 'structural-comp' ]],
factory : () => structuralComp =
new StructuralComp ( directiveInject ( ViewContainerRef as any )),
inputs : { tmp : 'tmp' },
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuralComp ) => {
if ( rf & RenderFlags . Create ) {
text ( 0 , 'Comp Content' );
}
}
});
}
function FooTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
text ( 0 , 'Temp Content' );
}
}
/**
* <ng-template #foo>
2018-10-12 15:02:54 -07:00
* Temp Content
2018-10-11 13:13:57 -07:00
* </ng-template>
* <structural-comp [class.active]="class" [tmp]="foo"></structural-comp>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-12-12 15:23:12 -08:00
template ( 0 , FooTemplate , 1 , 0 , 'ng-template' , null , [ 'foo' , '' ], templateRefExtractor );
2018-10-11 13:13:57 -07:00
elementStart ( 2 , 'structural-comp' );
elementStyling ([ 'active' ]);
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
const foo = reference ( 1 ) as any ;
elementClassProp ( 2 , 0 , ctx . class );
elementStylingApply ( 2 );
elementProperty ( 2 , 'tmp' , bind ( foo ));
}
}, 3 , 1 , [ StructuralComp ]);
const fixture = new ComponentFixture ( App );
fixture . component . class = true ;
fixture . update ();
expect ( fixture . html )
. toEqual ( '<structural-comp class="active">Comp Content</structural-comp>' );
structuralComp . create ();
fixture . update ();
expect ( fixture . html )
. toEqual ( '<structural-comp class="active">Comp Content</structural-comp>Temp Content' );
2018-10-12 15:02:54 -07:00
fixture . component . class = false ;
fixture . update ();
expect ( fixture . html )
. toEqual ( '<structural-comp class="">Comp Content</structural-comp>Temp Content' );
});
2018-10-18 14:47:53 -07:00
let mockClassDirective : DirWithClassDirective ;
class DirWithClassDirective {
static ngDirectiveDef = defineDirective ({
type : DirWithClassDirective ,
selectors : [[ '' , 'DirWithClass' , '' ]],
factory : () => mockClassDirective = new DirWithClassDirective (),
inputs : { 'klass' : 'class' }
});
public classesVal : string = '' ;
set klass ( value : string ) { this . classesVal = value ; }
}
2018-12-13 15:51:47 -08:00
it ( 'should delegate initial classes to a [class] input binding if present on a directive on the same element' ,
2018-10-18 14:47:53 -07:00
() => {
/**
2018-12-13 15:51:47 -08:00
* <div class="apple orange banana" DirWithClass></div>
2018-10-18 14:47:53 -07:00
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-12-13 15:51:47 -08:00
elementStart (
0 , 'div' ,
[ 'DirWithClass' , AttributeMarker . Classes , 'apple' , 'orange' , 'banana' ]);
elementStyling ();
2018-10-18 14:47:53 -07:00
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
elementStylingApply ( 0 );
}
}, 1 , 0 , [ DirWithClassDirective ]);
const fixture = new ComponentFixture ( App );
expect ( mockClassDirective ! . classesVal ). toEqual ( 'apple orange banana' );
});
it ( 'should update `[class]` and bindings in the provided directive if the input is matched' ,
() => {
/**
2018-12-13 15:51:47 -08:00
* <div DirWithClass></div>
2018-10-18 14:47:53 -07:00
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'div' , [ 'DirWithClass' ]);
elementStyling ();
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
elementStylingMap ( 0 , 'cucumber grape' );
elementStylingApply ( 0 );
}
}, 1 , 0 , [ DirWithClassDirective ]);
const fixture = new ComponentFixture ( App );
expect ( mockClassDirective ! . classesVal ). toEqual ( 'cucumber grape' );
});
2018-12-13 15:51:47 -08:00
it ( 'should apply initial styling to the element that contains the directive with host styling' ,
() => {
class DirWithInitialStyling {
static ngDirectiveDef = defineDirective ({
type : DirWithInitialStyling ,
selectors : [[ '' , 'DirWithInitialStyling' , '' ]],
factory : () => new DirWithInitialStyling (),
hostBindings : function (
rf : RenderFlags , ctx : DirWithInitialStyling , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
elementHostAttrs ( ctx , [
AttributeMarker . Classes , 'heavy' , 'golden' , AttributeMarker . Styles , 'color' ,
'purple' , 'font-weight' , 'bold'
]);
}
}
});
public classesVal : string = '' ;
}
/**
* <div DirWithInitialStyling
* class="big"
* style="color:black; * font-size:200px"></div>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [
'DirWithInitialStyling' , '' , AttributeMarker . Classes , 'big' ,
AttributeMarker . Styles , 'color' , 'black' , 'font-size' , '200px'
]);
}
}, 1 , 0 , [ DirWithInitialStyling ]);
const fixture = new ComponentFixture ( App );
const target = fixture . hostElement . querySelector ( 'div' ) ! ;
const classes = target . getAttribute ( 'class' ) ! . split ( /\s+/ ). sort ();
expect ( classes ). toEqual ([ 'big' , 'golden' , 'heavy' ]);
expect ( target . style . getPropertyValue ( 'color' )). toEqual ( 'black' );
expect ( target . style . getPropertyValue ( 'font-size' )). toEqual ( '200px' );
expect ( target . style . getPropertyValue ( 'font-weight' )). toEqual ( 'bold' );
});
it ( 'should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing' ,
() => {
let dirInstance : DirWithSingleStylingBindings ;
/**
* <DirWithInitialStyling class="def" [class.xyz] style="width:555px;" [style.width]
* [style.height]></my-comp>
*/
class DirWithSingleStylingBindings {
static ngDirectiveDef = defineDirective ({
type : DirWithSingleStylingBindings ,
selectors : [[ '' , 'DirWithSingleStylingBindings' , '' ]],
factory : () => dirInstance = new DirWithSingleStylingBindings (),
hostBindings : function (
rf : RenderFlags , ctx : DirWithSingleStylingBindings , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
elementHostAttrs (
ctx ,
[ AttributeMarker . Classes , 'def' , AttributeMarker . Styles , 'width' , '555px' ]);
elementStyling ([ 'xyz' ], [ 'width' , 'height' ], null , ctx );
}
if ( rf & RenderFlags . Update ) {
elementStyleProp ( elementIndex , 0 , ctx . width , null , ctx );
elementStyleProp ( elementIndex , 1 , ctx . height , null , ctx );
elementClassProp ( elementIndex , 0 , ctx . activateXYZClass , ctx );
elementStylingApply ( elementIndex , ctx );
}
}
});
width : null | string = null ;
height : null | string = null ;
activateXYZClass : boolean = false ;
}
/**
* <div DirWithInitialStyling class="abc" style="width:100px;
* height:200px"></div>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [
'DirWithSingleStylingBindings' , '' , AttributeMarker . Classes , 'abc' ,
AttributeMarker . Styles , 'width' , '100px' , 'height' , '200px'
]);
}
}, 1 , 0 , [ DirWithSingleStylingBindings ]);
const fixture = new ComponentFixture ( App );
const target = fixture . hostElement . querySelector ( 'div' ) ! ;
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '100px' );
expect ( target . style . getPropertyValue ( 'height' )). toEqual ( '200px' );
expect ( target . classList . contains ( 'abc' )). toBeTruthy ();
expect ( target . classList . contains ( 'def' )). toBeTruthy ();
expect ( target . classList . contains ( 'xyz' )). toBeFalsy ();
dirInstance ! . width = '444px' ;
dirInstance ! . height = '999px' ;
dirInstance ! . activateXYZClass = true ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '444px' );
expect ( target . style . getPropertyValue ( 'height' )). toEqual ( '999px' );
expect ( target . classList . contains ( 'abc' )). toBeTruthy ();
expect ( target . classList . contains ( 'def' )). toBeTruthy ();
expect ( target . classList . contains ( 'xyz' )). toBeTruthy ();
dirInstance ! . width = null ;
dirInstance ! . height = null ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '100px' );
expect ( target . style . getPropertyValue ( 'height' )). toEqual ( '200px' );
expect ( target . classList . contains ( 'abc' )). toBeTruthy ();
expect ( target . classList . contains ( 'def' )). toBeTruthy ();
expect ( target . classList . contains ( 'xyz' )). toBeTruthy ();
});
it ( 'should properly prioritize style binding collision when they exist on multiple directives' ,
() => {
let dir1Instance : Dir1WithStyle ;
/**
* Directive with host props:
* [style.width]
*/
class Dir1WithStyle {
static ngDirectiveDef = defineDirective ({
type : Dir1WithStyle ,
selectors : [[ '' , 'Dir1WithStyle' , '' ]],
factory : () => dir1Instance = new Dir1WithStyle (),
hostBindings : function ( rf : RenderFlags , ctx : Dir1WithStyle , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
elementStyling ( null , [ 'width' ], null , ctx );
}
if ( rf & RenderFlags . Update ) {
elementStyleProp ( elementIndex , 0 , ctx . width , null , ctx );
elementStylingApply ( elementIndex , ctx );
}
}
});
width : null | string = null ;
}
let dir2Instance : Dir2WithStyle ;
/**
* Directive with host props:
* [style.width]
* style="width:111px"
*/
class Dir2WithStyle {
static ngDirectiveDef = defineDirective ({
type : Dir2WithStyle ,
selectors : [[ '' , 'Dir2WithStyle' , '' ]],
factory : () => dir2Instance = new Dir2WithStyle (),
hostBindings : function ( rf : RenderFlags , ctx : Dir2WithStyle , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
elementHostAttrs ( ctx , [ AttributeMarker . Styles , 'width' , '111px' ]);
elementStyling ( null , [ 'width' ], null , ctx );
}
if ( rf & RenderFlags . Update ) {
elementStyleProp ( elementIndex , 0 , ctx . width , null , ctx );
elementStylingApply ( elementIndex , ctx );
}
}
});
width : null | string = null ;
}
/**
* <div Dir1WithStyle Dir2WithStyle [style.width]></div>
*/
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [ 'Dir1WithStyle' , '' , 'Dir2WithStyle' , '' ]);
elementStyling ( null , [ 'width' ]);
}
if ( rf & RenderFlags . Update ) {
elementStyleProp ( 0 , 0 , ctx . width );
elementStylingApply ( 0 );
}
}, 1 , 0 , [ Dir1WithStyle , Dir2WithStyle ]);
const fixture = new ComponentFixture ( App );
const target = fixture . hostElement . querySelector ( 'div' ) ! ;
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '111px' );
fixture . component . width = '999px' ;
dir1Instance ! . width = '222px' ;
dir2Instance ! . width = '333px' ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '999px' );
fixture . component . width = null ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '222px' );
dir1Instance ! . width = null ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '333px' );
dir2Instance ! . width = null ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '111px' );
dir1Instance ! . width = '666px' ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '666px' );
fixture . component . width = '777px' ;
fixture . update ();
expect ( target . style . getPropertyValue ( 'width' )). toEqual ( '777px' );
});
2017-12-01 14:23:03 -08:00
});
});
2017-12-08 11:48:54 -08:00
describe ( 'template data' , () => {
it ( 'should re-use template data and node data' , () => {
/**
* % if (condition) {
* <div></div>
* % }
*/
2018-04-10 20:57:09 -07:00
function Template ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 16:11:20 -08:00
container ( 0 );
2017-12-08 11:48:54 -08:00
}
2018-04-10 20:57:09 -07:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 );
{
if ( ctx . condition ) {
2018-08-18 11:14:50 -07:00
let rf1 = embeddedViewStart ( 0 , 1 , 0 );
2018-04-10 20:57:09 -07:00
if ( rf1 & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'div' );
2018-04-10 20:57:09 -07:00
}
embeddedViewEnd ();
2017-12-08 11:48:54 -08:00
}
}
2018-04-10 20:57:09 -07:00
containerRefreshEnd ();
2017-12-08 11:48:54 -08:00
}
}
2018-01-10 18:19:16 -08:00
expect (( Template as any ). ngPrivateData ). toBeUndefined ();
2017-12-08 11:48:54 -08:00
2018-08-16 18:53:21 -07:00
renderToHtml ( Template , { condition : true }, 1 );
2017-12-08 11:48:54 -08:00
2018-01-10 18:19:16 -08:00
const oldTemplateData = ( Template as any ). ngPrivateData ;
2018-06-07 22:42:32 -07:00
const oldContainerData = ( oldTemplateData as any ). data [ HEADER_OFFSET ];
const oldElementData = oldContainerData . tViews [ 0 ][ HEADER_OFFSET ];
2017-12-08 11:48:54 -08:00
expect ( oldContainerData ). not . toBeNull ();
expect ( oldElementData ). not . toBeNull ();
2018-08-16 18:53:21 -07:00
renderToHtml ( Template , { condition : false }, 1 );
renderToHtml ( Template , { condition : true }, 1 );
2017-12-08 11:48:54 -08:00
2018-01-10 18:19:16 -08:00
const newTemplateData = ( Template as any ). ngPrivateData ;
2018-06-07 22:42:32 -07:00
const newContainerData = ( oldTemplateData as any ). data [ HEADER_OFFSET ];
const newElementData = oldContainerData . tViews [ 0 ][ HEADER_OFFSET ];
2017-12-08 11:48:54 -08:00
expect ( newTemplateData === oldTemplateData ). toBe ( true );
expect ( newContainerData === oldContainerData ). toBe ( true );
expect ( newElementData === oldElementData ). toBe ( true );
});
});
2018-07-31 11:14:06 -07:00
describe ( 'component styles' , () => {
it ( 'should pass in the component styles directly into the underlying renderer' , () => {
class StyledComp {
static ngComponentDef = defineComponent ({
type : StyledComp ,
styles : [ 'div { color: red; }' ],
consts : 1 ,
vars : 0 ,
encapsulation : 100 ,
selectors : [[ 'foo' ]],
factory : () => new StyledComp (),
template : ( rf : RenderFlags , ctx : StyledComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' );
}
}
});
}
2018-09-06 18:50:57 -07:00
const rendererFactory = new ProxyRenderer3Factory ();
2018-07-31 11:14:06 -07:00
new ComponentFixture ( StyledComp , { rendererFactory });
expect ( rendererFactory . lastCapturedType ! . styles ). toEqual ([ 'div { color: red; }' ]);
expect ( rendererFactory . lastCapturedType ! . encapsulation ). toEqual ( 100 );
});
});
2018-09-05 15:23:59 -07:00
describe ( 'component animations' , () => {
it ( 'should pass in the component styles directly into the underlying renderer' , () => {
const animA = { name : 'a' };
const animB = { name : 'b' };
class AnimComp {
static ngComponentDef = defineComponent ({
type : AnimComp ,
consts : 0 ,
vars : 0 ,
2018-10-16 11:09:04 -07:00
data : {
2018-12-05 15:56:36 -08:00
animation : [
2018-10-16 11:09:04 -07:00
animA ,
animB ,
],
},
2018-09-05 15:23:59 -07:00
selectors : [[ 'foo' ]],
factory : () => new AnimComp (),
template : ( rf : RenderFlags , ctx : AnimComp ) => {}
});
}
2018-09-06 18:50:57 -07:00
const rendererFactory = new ProxyRenderer3Factory ();
2018-09-05 15:23:59 -07:00
new ComponentFixture ( AnimComp , { rendererFactory });
2018-12-05 15:56:36 -08:00
const capturedAnimations = rendererFactory . lastCapturedType ! . data ! [ 'animation' ];
2018-09-05 15:23:59 -07:00
expect ( Array . isArray ( capturedAnimations )). toBeTruthy ();
expect ( capturedAnimations . length ). toEqual ( 2 );
expect ( capturedAnimations ). toContain ( animA );
expect ( capturedAnimations ). toContain ( animB );
});
it ( 'should include animations in the renderType data array even if the array is empty' , () => {
class AnimComp {
static ngComponentDef = defineComponent ({
type : AnimComp ,
consts : 0 ,
vars : 0 ,
2018-10-16 11:09:04 -07:00
data : {
2018-12-05 15:56:36 -08:00
animation : [],
2018-10-16 11:09:04 -07:00
},
2018-09-05 15:23:59 -07:00
selectors : [[ 'foo' ]],
factory : () => new AnimComp (),
template : ( rf : RenderFlags , ctx : AnimComp ) => {}
});
}
2018-09-06 18:50:57 -07:00
const rendererFactory = new ProxyRenderer3Factory ();
2018-09-05 15:23:59 -07:00
new ComponentFixture ( AnimComp , { rendererFactory });
const data = rendererFactory . lastCapturedType ! . data ;
2018-12-05 15:56:36 -08:00
expect ( data . animation ). toEqual ([]);
2018-09-05 15:23:59 -07:00
});
2018-09-06 18:50:57 -07:00
it ( 'should allow [@trigger] bindings to be picked up by the underlying renderer' , () => {
class AnimComp {
static ngComponentDef = defineComponent ({
type : AnimComp ,
consts : 1 ,
vars : 1 ,
selectors : [[ 'foo' ]],
factory : () => new AnimComp (),
template : ( rf : RenderFlags , ctx : AnimComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [ AttributeMarker . SelectOnly , '@fooAnimation' ]);
}
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , '@fooAnimation' , bind ( ctx . animationValue ));
}
}
});
animationValue = '123' ;
}
const rendererFactory = new MockRendererFactory ([ 'setAttribute' ]);
const fixture = new ComponentFixture ( AnimComp , { rendererFactory });
const renderer = rendererFactory . lastRenderer ! ;
fixture . component . animationValue = '456' ;
fixture . update ();
const spy = renderer . spies [ 'setAttribute' ];
const [ elm , attr , value ] = spy . calls . mostRecent (). args ;
expect ( attr ). toEqual ( '@fooAnimation' );
expect ( value ). toEqual ( '456' );
});
it ( 'should allow creation-level [@trigger] properties to be picked up by the underlying renderer' ,
() => {
class AnimComp {
static ngComponentDef = defineComponent ({
type : AnimComp ,
consts : 1 ,
vars : 1 ,
selectors : [[ 'foo' ]],
factory : () => new AnimComp (),
template : ( rf : RenderFlags , ctx : AnimComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [ '@fooAnimation' , '' ]);
}
}
});
}
2018-12-05 15:56:36 -08:00
const rendererFactory = new MockRendererFactory ([ 'setProperty' ]);
2018-09-06 18:50:57 -07:00
const fixture = new ComponentFixture ( AnimComp , { rendererFactory });
const renderer = rendererFactory . lastRenderer ! ;
fixture . update ();
2018-12-05 15:56:36 -08:00
const spy = renderer . spies [ 'setProperty' ];
2018-09-06 18:50:57 -07:00
const [ elm , attr , value ] = spy . calls . mostRecent (). args ;
expect ( attr ). toEqual ( '@fooAnimation' );
});
2019-01-03 18:24:21 -08:00
it ( 'should allow host binding animations to be picked up and rendered' , () => {
class ChildCompWithAnim {
static ngDirectiveDef = defineDirective ({
type : ChildCompWithAnim ,
factory : () => new ChildCompWithAnim (),
selectors : [[ 'child-comp-with-anim' ]],
hostBindings : function ( rf : RenderFlags , ctx : any , elementIndex : number ) : void {
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , '@fooAnim' , ctx . exp );
}
},
});
exp = 'go' ;
}
class ParentComp {
static ngComponentDef = defineComponent ({
type : ParentComp ,
consts : 1 ,
vars : 1 ,
selectors : [[ 'foo' ]],
factory : () => new ParentComp (),
template : ( rf : RenderFlags , ctx : ParentComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'child-comp-with-anim' );
}
},
directives : [ ChildCompWithAnim ]
});
}
const rendererFactory = new MockRendererFactory ([ 'setProperty' ]);
const fixture = new ComponentFixture ( ParentComp , { rendererFactory });
const renderer = rendererFactory . lastRenderer ! ;
fixture . update ();
const spy = renderer . spies [ 'setProperty' ];
const [ elm , attr , value ] = spy . calls . mostRecent (). args ;
expect ( attr ). toEqual ( '@fooAnim' );
});
2018-09-05 15:23:59 -07:00
});
2018-08-22 16:57:40 -07:00
describe ( 'element discovery' , () => {
it ( 'should only monkey-patch immediate child nodes in a component' , () => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'div' );
elementStart ( 1 , 'p' );
elementEnd ();
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const host = fixture . hostElement ;
const parent = host . querySelector ( 'div' ) as any ;
const child = host . querySelector ( 'p' ) as any ;
expect ( parent [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( child [ MONKEY_PATCH_KEY_NAME ]). toBeFalsy ();
});
it ( 'should only monkey-patch immediate child nodes in a sub component' , () => {
class ChildComp {
static ngComponentDef = defineComponent ({
type : ChildComp ,
selectors : [[ 'child-comp' ]],
factory : () => new ChildComp (),
consts : 3 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ChildComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' );
element ( 1 , 'div' );
element ( 2 , 'div' );
}
}
});
}
class ParentComp {
static ngComponentDef = defineComponent ({
type : ParentComp ,
selectors : [[ 'parent-comp' ]],
directives : [ ChildComp ],
factory : () => new ParentComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ParentComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
elementStart ( 1 , 'child-comp' );
elementEnd ();
elementEnd ();
}
}
});
}
const fixture = new ComponentFixture ( ParentComp );
fixture . update ();
const host = fixture . hostElement ;
const child = host . querySelector ( 'child-comp' ) as any ;
2018-09-10 11:22:35 -07:00
expect ( child [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-08-22 16:57:40 -07:00
const [ kid1 , kid2 , kid3 ] = Array . from ( host . querySelectorAll ( 'child-comp > *' ));
expect ( kid1 [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( kid2 [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( kid3 [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
});
it ( 'should only monkey-patch immediate child nodes in an embedded template container' , () => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
directives : [ NgIf ],
factory : () => new StructuredComp (),
consts : 2 ,
vars : 1 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
template ( 1 , ( rf , ctx ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'div' );
element ( 1 , 'p' );
elementEnd ();
element ( 2 , 'div' );
}
2018-12-12 15:23:12 -08:00
}, 3 , 0 , 'ng-template' , [ 'ngIf' , '' ]);
2018-08-22 16:57:40 -07:00
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
elementProperty ( 1 , 'ngIf' , true );
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const host = fixture . hostElement ;
const [ section , div1 , p , div2 ] = Array . from ( host . querySelectorAll ( 'section, div, p' ));
expect ( section . nodeName . toLowerCase ()). toBe ( 'section' );
expect ( section [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( div1 . nodeName . toLowerCase ()). toBe ( 'div' );
expect ( div1 [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( p . nodeName . toLowerCase ()). toBe ( 'p' );
expect ( p [ MONKEY_PATCH_KEY_NAME ]). toBeFalsy ();
expect ( div2 . nodeName . toLowerCase ()). toBe ( 'div' );
expect ( div2 [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
});
it ( 'should return a context object from a given dom node' , () => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
directives : [ NgIf ],
factory : () => new StructuredComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'section' );
element ( 1 , 'div' );
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const section = fixture . hostElement . querySelector ( 'section' ) ! ;
2018-11-28 15:54:38 -08:00
const sectionContext = getLContext ( section ) ! ;
2018-11-21 21:14:06 -08:00
const sectionLView = sectionContext . lView ! ;
2018-10-04 20:40:39 -07:00
expect ( sectionContext . nodeIndex ). toEqual ( HEADER_OFFSET );
2018-08-22 16:57:40 -07:00
expect ( sectionLView . length ). toBeGreaterThan ( HEADER_OFFSET );
expect ( sectionContext . native ). toBe ( section );
const div = fixture . hostElement . querySelector ( 'div' ) ! ;
2018-11-28 15:54:38 -08:00
const divContext = getLContext ( div ) ! ;
2018-11-21 21:14:06 -08:00
const divLView = divContext . lView ! ;
2018-10-04 20:40:39 -07:00
expect ( divContext . nodeIndex ). toEqual ( HEADER_OFFSET + 1 );
2018-08-22 16:57:40 -07:00
expect ( divLView . length ). toBeGreaterThan ( HEADER_OFFSET );
expect ( divContext . native ). toBe ( div );
expect ( divLView ). toBe ( sectionLView );
});
it ( 'should cache the element context on a element was pre-emptively monkey-patched' , () => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'section' );
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
const result1 = section [ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( result1 )). toBeTruthy ();
2018-11-28 15:54:38 -08:00
const context = getLContext ( section ) ! ;
2018-08-22 16:57:40 -07:00
const result2 = section [ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( result2 )). toBeFalsy ();
expect ( result2 ). toBe ( context );
2018-11-21 21:14:06 -08:00
expect ( result2 . lView ). toBe ( result1 );
2018-08-22 16:57:40 -07:00
});
it ( 'should cache the element context on an intermediate element that isn\'t pre-emptively monkey-patched' ,
() => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
element ( 1 , 'p' );
elementEnd ();
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
expect ( section [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
const p = fixture . hostElement . querySelector ( 'p' ) ! as any ;
expect ( p [ MONKEY_PATCH_KEY_NAME ]). toBeFalsy ();
2018-11-28 15:54:38 -08:00
const pContext = getLContext ( p ) ! ;
2018-08-22 16:57:40 -07:00
expect ( pContext . native ). toBe ( p );
expect ( p [ MONKEY_PATCH_KEY_NAME ]). toBe ( pContext );
});
it ( 'should be able to pull in element context data even if the element is decorated using styling' ,
() => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
elementStyling ([ 'class-foo' ]);
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
elementStylingApply ( 0 );
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
const result1 = section [ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( result1 )). toBeTruthy ();
const elementResult = result1 [ HEADER_OFFSET ]; // first element
expect ( Array . isArray ( elementResult )). toBeTruthy ();
2018-10-12 18:49:00 -07:00
expect ( elementResult [ StylingIndex . ElementPosition ]). toBe ( section );
2018-08-22 16:57:40 -07:00
2018-11-28 15:54:38 -08:00
const context = getLContext ( section ) ! ;
2018-08-22 16:57:40 -07:00
const result2 = section [ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( result2 )). toBeFalsy ();
expect ( context . native ). toBe ( section );
});
it ( 'should monkey-patch immediate child nodes in a content-projected region with a reference to the parent component' ,
() => {
/*
<!-- DOM view -->
<section>
<projection-comp>
welcome
<header>
<h1>
<p>this content is projected</p>
this content is projected also
</h1>
</header>
</projection-comp>
</section>
*/
class ProjectorComp {
static ngComponentDef = defineComponent ({
type : ProjectorComp ,
selectors : [[ 'projector-comp' ]],
factory : () => new ProjectorComp (),
consts : 4 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ProjectorComp ) => {
if ( rf & RenderFlags . Create ) {
projectionDef ();
text ( 0 , 'welcome' );
elementStart ( 1 , 'header' );
elementStart ( 2 , 'h1' );
projection ( 3 );
elementEnd ();
elementEnd ();
}
if ( rf & RenderFlags . Update ) {
}
}
});
}
class ParentComp {
static ngComponentDef = defineComponent ({
type : ParentComp ,
selectors : [[ 'parent-comp' ]],
directives : [ ProjectorComp ],
factory : () => new ParentComp (),
consts : 5 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ParentComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
elementStart ( 1 , 'projector-comp' );
elementStart ( 2 , 'p' );
text ( 3 , 'this content is projected' );
elementEnd ();
text ( 4 , 'this content is projected also' );
elementEnd ();
elementEnd ();
}
}
});
}
const fixture = new ComponentFixture ( ParentComp );
fixture . update ();
const host = fixture . hostElement ;
const textNode = host . firstChild as any ;
const section = host . querySelector ( 'section' ) ! as any ;
const projectorComp = host . querySelector ( 'projector-comp' ) ! as any ;
const header = host . querySelector ( 'header' ) ! as any ;
const h1 = host . querySelector ( 'h1' ) ! as any ;
const p = host . querySelector ( 'p' ) ! as any ;
const pText = p . firstChild as any ;
const projectedTextNode = p . nextSibling ;
expect ( projectorComp . children ). toContain ( header );
expect ( h1 . children ). toContain ( p );
expect ( textNode [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( section [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-09-10 11:22:35 -07:00
expect ( projectorComp [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-08-22 16:57:40 -07:00
expect ( header [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( h1 [ MONKEY_PATCH_KEY_NAME ]). toBeFalsy ();
expect ( p [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
expect ( pText [ MONKEY_PATCH_KEY_NAME ]). toBeFalsy ();
expect ( projectedTextNode [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-11-28 15:54:38 -08:00
const parentContext = getLContext ( section ) ! ;
const shadowContext = getLContext ( header ) ! ;
const projectedContext = getLContext ( p ) ! ;
2018-08-22 16:57:40 -07:00
2018-11-21 21:14:06 -08:00
const parentComponentData = parentContext . lView ;
const shadowComponentData = shadowContext . lView ;
const projectedComponentData = projectedContext . lView ;
2018-08-22 16:57:40 -07:00
expect ( projectedComponentData ). toBe ( parentComponentData );
expect ( shadowComponentData ). not . toBe ( parentComponentData );
});
it ( 'should return `null` when an element context is retrieved that isn\'t situated in Angular' ,
() => {
const elm1 = document . createElement ( 'div' );
2018-11-28 15:54:38 -08:00
const context1 = getLContext ( elm1 );
2018-08-22 16:57:40 -07:00
expect ( context1 ). toBeFalsy ();
const elm2 = document . createElement ( 'div' );
document . body . appendChild ( elm2 );
2018-11-28 15:54:38 -08:00
const context2 = getLContext ( elm2 );
2018-08-22 16:57:40 -07:00
expect ( context2 ). toBeFalsy ();
});
it ( 'should return `null` when an element context is retrieved that is a DOM node that was not created by Angular' ,
() => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'section' );
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
const manuallyCreatedElement = document . createElement ( 'div' );
section . appendChild ( manuallyCreatedElement );
2018-11-28 15:54:38 -08:00
const context = getLContext ( manuallyCreatedElement );
2018-08-22 16:57:40 -07:00
expect ( context ). toBeFalsy ();
});
2018-08-29 13:52:03 -07:00
it ( 'should by default monkey-patch the bootstrap component with context details' , () => {
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
factory : () => new StructuredComp (),
consts : 0 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const hostElm = fixture . hostElement ;
const component = fixture . component ;
2018-11-21 21:14:06 -08:00
const componentLView = ( component as any )[ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( componentLView )). toBeTruthy ();
2018-08-29 13:52:03 -07:00
2018-11-21 21:14:06 -08:00
const hostLView = ( hostElm as any )[ MONKEY_PATCH_KEY_NAME ];
expect ( hostLView ). toBe ( componentLView );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
const context1 = getLContext ( hostElm ) ! ;
2018-11-21 21:14:06 -08:00
expect ( context1 . lView ). toBe ( hostLView );
2018-08-29 13:52:03 -07:00
expect ( context1 . native ). toEqual ( hostElm );
2018-11-28 15:54:38 -08:00
const context2 = getLContext ( component ) ! ;
2018-08-29 13:52:03 -07:00
expect ( context2 ). toBe ( context1 );
2018-11-21 21:14:06 -08:00
expect ( context2 . lView ). toBe ( hostLView );
2018-08-29 13:52:03 -07:00
expect ( context2 . native ). toEqual ( hostElm );
});
2018-11-21 21:14:06 -08:00
it ( 'should by default monkey-patch the directives with LView so that they can be examined' ,
2018-08-29 13:52:03 -07:00
() => {
let myDir1Instance : MyDir1 | null = null ;
let myDir2Instance : MyDir2 | null = null ;
let myDir3Instance : MyDir2 | null = null ;
class MyDir1 {
static ngDirectiveDef = defineDirective ({
type : MyDir1 ,
selectors : [[ '' , 'my-dir-1' , '' ]],
factory : () => myDir1Instance = new MyDir1 ()
});
}
class MyDir2 {
static ngDirectiveDef = defineDirective ({
type : MyDir2 ,
selectors : [[ '' , 'my-dir-2' , '' ]],
factory : () => myDir2Instance = new MyDir2 ()
});
}
class MyDir3 {
static ngDirectiveDef = defineDirective ({
type : MyDir3 ,
selectors : [[ '' , 'my-dir-3' , '' ]],
factory : () => myDir3Instance = new MyDir2 ()
});
}
class StructuredComp {
static ngComponentDef = defineComponent ({
type : StructuredComp ,
selectors : [[ 'structured-comp' ]],
directives : [ MyDir1 , MyDir2 , MyDir3 ],
factory : () => new StructuredComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : StructuredComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [ 'my-dir-1' , '' , 'my-dir-2' , '' ]);
element ( 1 , 'div' , [ 'my-dir-3' ]);
}
}
});
}
const fixture = new ComponentFixture ( StructuredComp );
fixture . update ();
const hostElm = fixture . hostElement ;
const div1 = hostElm . querySelector ( 'div:first-child' ) ! as any ;
const div2 = hostElm . querySelector ( 'div:last-child' ) ! as any ;
2018-11-28 15:54:38 -08:00
const context = getLContext ( hostElm ) ! ;
2018-11-21 21:14:06 -08:00
const componentView = context . lView [ context . nodeIndex ];
2018-08-29 13:52:03 -07:00
2018-10-12 15:02:54 -07:00
expect ( componentView ). toContain ( myDir1Instance );
expect ( componentView ). toContain ( myDir2Instance );
expect ( componentView ). toContain ( myDir3Instance );
2018-08-29 13:52:03 -07:00
expect ( Array . isArray (( myDir1Instance as any )[ MONKEY_PATCH_KEY_NAME ])). toBeTruthy ();
expect ( Array . isArray (( myDir2Instance as any )[ MONKEY_PATCH_KEY_NAME ])). toBeTruthy ();
expect ( Array . isArray (( myDir3Instance as any )[ MONKEY_PATCH_KEY_NAME ])). toBeTruthy ();
2018-11-28 15:54:38 -08:00
const d1Context = getLContext ( myDir1Instance ) ! ;
const d2Context = getLContext ( myDir2Instance ) ! ;
const d3Context = getLContext ( myDir3Instance ) ! ;
2018-08-29 13:52:03 -07:00
2018-11-21 21:14:06 -08:00
expect ( d1Context . lView ). toEqual ( componentView );
expect ( d2Context . lView ). toEqual ( componentView );
expect ( d3Context . lView ). toEqual ( componentView );
2018-08-29 13:52:03 -07:00
expect (( myDir1Instance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( d1Context );
expect (( myDir2Instance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( d2Context );
expect (( myDir3Instance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( d3Context );
2018-10-04 20:40:39 -07:00
expect ( d1Context . nodeIndex ). toEqual ( HEADER_OFFSET );
2018-08-29 13:52:03 -07:00
expect ( d1Context . native ). toBe ( div1 );
expect ( d1Context . directives as any []). toEqual ([ myDir1Instance , myDir2Instance ]);
2018-10-04 20:40:39 -07:00
expect ( d2Context . nodeIndex ). toEqual ( HEADER_OFFSET );
2018-08-29 13:52:03 -07:00
expect ( d2Context . native ). toBe ( div1 );
expect ( d2Context . directives as any []). toEqual ([ myDir1Instance , myDir2Instance ]);
2018-10-04 20:40:39 -07:00
expect ( d3Context . nodeIndex ). toEqual ( HEADER_OFFSET + 1 );
2018-08-29 13:52:03 -07:00
expect ( d3Context . native ). toBe ( div2 );
expect ( d3Context . directives as any []). toEqual ([ myDir3Instance ]);
});
it ( 'should monkey-patch the exact same context instance of the DOM node, component and any directives on the same element' ,
() => {
let myDir1Instance : MyDir1 | null = null ;
let myDir2Instance : MyDir2 | null = null ;
let childComponentInstance : ChildComp | null = null ;
class MyDir1 {
static ngDirectiveDef = defineDirective ({
type : MyDir1 ,
selectors : [[ '' , 'my-dir-1' , '' ]],
factory : () => myDir1Instance = new MyDir1 ()
});
}
class MyDir2 {
static ngDirectiveDef = defineDirective ({
type : MyDir2 ,
selectors : [[ '' , 'my-dir-2' , '' ]],
factory : () => myDir2Instance = new MyDir2 ()
});
}
class ChildComp {
static ngComponentDef = defineComponent ({
type : ChildComp ,
selectors : [[ 'child-comp' ]],
factory : () => childComponentInstance = new ChildComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ChildComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' );
}
}
});
}
class ParentComp {
static ngComponentDef = defineComponent ({
type : ParentComp ,
selectors : [[ 'parent-comp' ]],
directives : [ ChildComp , MyDir1 , MyDir2 ],
factory : () => new ParentComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ParentComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'child-comp' , [ 'my-dir-1' , '' , 'my-dir-2' , '' ]);
}
}
});
}
const fixture = new ComponentFixture ( ParentComp );
fixture . update ();
const childCompHostElm = fixture . hostElement . querySelector ( 'child-comp' ) ! as any ;
2018-11-21 21:14:06 -08:00
const lView = childCompHostElm [ MONKEY_PATCH_KEY_NAME ];
expect ( Array . isArray ( lView )). toBeTruthy ();
expect (( myDir1Instance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( lView );
expect (( myDir2Instance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( lView );
expect (( childComponentInstance as any )[ MONKEY_PATCH_KEY_NAME ]). toBe ( lView );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
const childNodeContext = getLContext ( childCompHostElm ) ! ;
2018-08-29 13:52:03 -07:00
expect ( childNodeContext . component ). toBeFalsy ();
expect ( childNodeContext . directives ). toBeFalsy ();
2018-11-21 21:14:06 -08:00
assertMonkeyPatchValueIsLView ( myDir1Instance );
assertMonkeyPatchValueIsLView ( myDir2Instance );
assertMonkeyPatchValueIsLView ( childComponentInstance );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
expect ( getLContext ( myDir1Instance )). toBe ( childNodeContext );
2018-08-29 13:52:03 -07:00
expect ( childNodeContext . component ). toBeFalsy ();
expect ( childNodeContext . directives ! . length ). toEqual ( 2 );
2018-11-21 21:14:06 -08:00
assertMonkeyPatchValueIsLView ( myDir1Instance , false );
assertMonkeyPatchValueIsLView ( myDir2Instance , false );
assertMonkeyPatchValueIsLView ( childComponentInstance );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
expect ( getLContext ( myDir2Instance )). toBe ( childNodeContext );
2018-08-29 13:52:03 -07:00
expect ( childNodeContext . component ). toBeFalsy ();
expect ( childNodeContext . directives ! . length ). toEqual ( 2 );
2018-11-21 21:14:06 -08:00
assertMonkeyPatchValueIsLView ( myDir1Instance , false );
assertMonkeyPatchValueIsLView ( myDir2Instance , false );
assertMonkeyPatchValueIsLView ( childComponentInstance );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
expect ( getLContext ( childComponentInstance )). toBe ( childNodeContext );
2018-08-29 13:52:03 -07:00
expect ( childNodeContext . component ). toBeTruthy ();
expect ( childNodeContext . directives ! . length ). toEqual ( 2 );
2018-11-21 21:14:06 -08:00
assertMonkeyPatchValueIsLView ( myDir1Instance , false );
assertMonkeyPatchValueIsLView ( myDir2Instance , false );
assertMonkeyPatchValueIsLView ( childComponentInstance , false );
2018-08-29 13:52:03 -07:00
2018-11-21 21:14:06 -08:00
function assertMonkeyPatchValueIsLView ( value : any , yesOrNo = true ) {
2018-08-29 13:52:03 -07:00
expect ( Array . isArray (( value as any )[ MONKEY_PATCH_KEY_NAME ])). toBe ( yesOrNo );
}
});
it ( 'should monkey-patch sub components with the view data and then replace them with the context result once a lookup occurs' ,
() => {
class ChildComp {
static ngComponentDef = defineComponent ({
type : ChildComp ,
selectors : [[ 'child-comp' ]],
factory : () => new ChildComp (),
consts : 3 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ChildComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' );
element ( 1 , 'div' );
element ( 2 , 'div' );
}
}
});
}
class ParentComp {
static ngComponentDef = defineComponent ({
type : ParentComp ,
selectors : [[ 'parent-comp' ]],
directives : [ ChildComp ],
factory : () => new ParentComp (),
consts : 2 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : ParentComp ) => {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'section' );
elementStart ( 1 , 'child-comp' );
elementEnd ();
elementEnd ();
}
}
});
}
const fixture = new ComponentFixture ( ParentComp );
fixture . update ();
const host = fixture . hostElement ;
const child = host . querySelector ( 'child-comp' ) as any ;
2018-09-10 11:22:35 -07:00
expect ( child [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
const context = getLContext ( child ) ! ;
2018-08-29 13:52:03 -07:00
expect ( child [ MONKEY_PATCH_KEY_NAME ]). toBeTruthy ();
2018-11-21 21:14:06 -08:00
const componentData = context . lView [ context . nodeIndex ];
2018-08-29 13:52:03 -07:00
const component = componentData [ CONTEXT ];
expect ( component instanceof ChildComp ). toBeTruthy ();
2018-11-21 21:14:06 -08:00
expect ( component [ MONKEY_PATCH_KEY_NAME ]). toBe ( context . lView );
2018-08-29 13:52:03 -07:00
2018-11-28 15:54:38 -08:00
const componentContext = getLContext ( component ) ! ;
2018-08-29 13:52:03 -07:00
expect ( component [ MONKEY_PATCH_KEY_NAME ]). toBe ( componentContext );
2018-10-04 20:40:39 -07:00
expect ( componentContext . nodeIndex ). toEqual ( context . nodeIndex );
2018-08-29 13:52:03 -07:00
expect ( componentContext . native ). toEqual ( context . native );
2018-11-21 21:14:06 -08:00
expect ( componentContext . lView ). toEqual ( context . lView );
2018-08-29 13:52:03 -07:00
});
2018-08-22 16:57:40 -07:00
});
2018-05-09 15:30:16 -07:00
describe ( 'sanitization' , () => {
it ( 'should sanitize data using the provided sanitization interface' , () => {
class SanitizationComp {
static ngComponentDef = defineComponent ({
type : SanitizationComp ,
selectors : [[ 'sanitize-this' ]],
factory : () => new SanitizationComp (),
2018-08-16 18:53:21 -07:00
consts : 1 ,
2018-08-18 11:14:50 -07:00
vars : 1 ,
2018-05-09 15:30:16 -07:00
template : ( rf : RenderFlags , ctx : SanitizationComp ) => {
if ( rf & RenderFlags . Create ) {
2018-07-27 14:28:22 -07:00
element ( 0 , 'a' );
2018-05-09 15:30:16 -07:00
}
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'href' , bind ( ctx . href ), sanitizeUrl );
}
}
});
private href = '' ;
updateLink ( href : any ) { this . href = href ; }
}
const sanitizer = new LocalSanitizer (( value ) => { return 'http://bar' ; });
const fixture = new ComponentFixture ( SanitizationComp , { sanitizer });
fixture . component . updateLink ( 'http://foo' );
fixture . update ();
2018-07-27 14:28:22 -07:00
const anchor = fixture . hostElement . querySelector ( 'a' ) ! ;
expect ( anchor . getAttribute ( 'href' )). toEqual ( 'http://bar' );
2018-05-09 15:30:16 -07:00
fixture . component . updateLink ( sanitizer . bypassSecurityTrustUrl ( 'http://foo' ));
fixture . update ();
2018-07-27 14:28:22 -07:00
expect ( anchor . getAttribute ( 'href' )). toEqual ( 'http://foo' );
2018-05-09 15:30:16 -07:00
});
2019-01-03 10:04:06 -08:00
it ( 'should sanitize HostBindings data using provided sanitization interface' , () => {
let hostBindingDir : UnsafeUrlHostBindingDir ;
class UnsafeUrlHostBindingDir {
// @HostBinding()
cite : any = 'http://cite-dir-value' ;
static ngDirectiveDef = defineDirective ({
type : UnsafeUrlHostBindingDir ,
selectors : [[ '' , 'unsafeUrlHostBindingDir' , '' ]],
factory : () => hostBindingDir = new UnsafeUrlHostBindingDir (),
hostBindings : ( rf : RenderFlags , ctx : any , elementIndex : number ) => {
if ( rf & RenderFlags . Create ) {
allocHostVars ( 1 );
}
if ( rf & RenderFlags . Update ) {
elementProperty ( elementIndex , 'cite' , bind ( ctx . cite ), sanitizeUrl , true );
}
}
});
}
class SimpleComp {
static ngComponentDef = defineComponent ({
type : SimpleComp ,
selectors : [[ 'sanitize-this' ]],
factory : () => new SimpleComp (),
consts : 1 ,
vars : 0 ,
template : ( rf : RenderFlags , ctx : SimpleComp ) => {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'blockquote' , [ 'unsafeUrlHostBindingDir' , '' ]);
}
},
directives : [ UnsafeUrlHostBindingDir ]
});
}
const sanitizer = new LocalSanitizer (( value ) => 'http://bar' );
const fixture = new ComponentFixture ( SimpleComp , { sanitizer });
hostBindingDir ! . cite = 'http://foo' ;
fixture . update ();
const anchor = fixture . hostElement . querySelector ( 'blockquote' ) ! ;
expect ( anchor . getAttribute ( 'cite' )). toEqual ( 'http://bar' );
hostBindingDir ! . cite = sanitizer . bypassSecurityTrustUrl ( 'http://foo' );
fixture . update ();
expect ( anchor . getAttribute ( 'cite' )). toEqual ( 'http://foo' );
});
2018-05-09 15:30:16 -07:00
});
2017-12-01 14:23:03 -08:00
});
2018-05-09 15:30:16 -07:00
class LocalSanitizedValue {
constructor ( public value : any ) {}
toString() { return this . value ; }
}
class LocalSanitizer implements Sanitizer {
constructor ( private _interceptor : ( value : string | null | any ) => string ) {}
sanitize ( context : SecurityContext , value : LocalSanitizedValue | string | null ) : string | null {
if ( value instanceof LocalSanitizedValue ) {
return value . toString ();
}
return this . _interceptor ( value );
}
bypassSecurityTrustHtml ( value : string ) {}
bypassSecurityTrustStyle ( value : string ) {}
bypassSecurityTrustScript ( value : string ) {}
bypassSecurityTrustResourceUrl ( value : string ) {}
bypassSecurityTrustUrl ( value : string ) { return new LocalSanitizedValue ( value ); }
}
2018-07-31 11:14:06 -07:00
2018-09-06 18:50:57 -07:00
class ProxyRenderer3Factory implements RendererFactory3 {
2018-07-31 11:14:06 -07:00
lastCapturedType : RendererType2 | null = null ;
createRenderer ( hostElement : RElement | null , rendererType : RendererType2 | null ) : Renderer3 {
this . lastCapturedType = rendererType ;
return domRendererFactory3 . createRenderer ( hostElement , rendererType );
}
2018-12-13 15:51:47 -08:00
}