2017-12-01 17:23:03 -05: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 10:40:16 -04:00
import { ElementRef , TemplateRef , ViewContainerRef } from '@angular/core' ;
2018-12-13 18:51:47 -05:00
2018-12-11 02:40:19 -05:00
import { RendererType2 } from '../../src/render/api' ;
2018-10-11 16:13:57 -04:00
import { AttributeMarker , defineComponent , defineDirective , templateRefExtractor } from '../../src/render3/index' ;
2018-09-06 21:50:57 -04:00
2019-03-15 16:45:08 -04: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 , elementHostStyleProp , elementHostStyling , elementHostClassProp , elementHostStylingApply , elementHostStylingMap } from '../../src/render3/instructions/all' ;
2018-12-13 18:51:47 -05:00
import { RenderFlags } from '../../src/render3/interfaces/definition' ;
2018-12-11 02:40:19 -05:00
import { RElement , Renderer3 , RendererFactory3 , domRendererFactory3 } from '../../src/render3/interfaces/renderer' ;
2018-10-08 19:04:46 -04:00
import { HEADER_OFFSET , CONTEXT } from '../../src/render3/interfaces/view' ;
2018-10-18 03:23:18 -04:00
import { enableBindings , disableBindings } from '../../src/render3/state' ;
2018-05-09 18:30:16 -04:00
import { sanitizeUrl } from '../../src/sanitization/sanitization' ;
import { Sanitizer , SecurityContext } from '../../src/sanitization/security' ;
2017-12-01 17:23:03 -05:00
2018-08-01 09:19:27 -04:00
import { NgIf } from './common_with_def' ;
2018-12-11 02:40:19 -05:00
import { ComponentFixture , MockRendererFactory , TemplateFixture , createComponent , renderToHtml } from './render_util' ;
2018-11-28 18:54:38 -05:00
import { getLContext } from '../../src/render3/context_discovery' ;
2018-09-28 15:38:16 -04:00
import { StylingIndex } from '../../src/render3/interfaces/styling' ;
2018-10-12 18:02:54 -04:00
import { MONKEY_PATCH_KEY_NAME } from '../../src/render3/interfaces/context' ;
2017-12-01 17:23:03 -05:00
2018-01-03 05:42:48 -05:00
describe ( 'render3 integration test' , ( ) = > {
2017-12-01 17:23:03 -05:00
describe ( 'render' , ( ) = > {
it ( 'should render basic template' , ( ) = > {
2018-08-16 21:53:21 -04:00
expect ( renderToHtml ( Template , { } , 2 ) ) . toEqual ( '<span title="Hello">Greetings</span>' ) ;
2017-12-01 17:23:03 -05:00
2018-04-10 23:57:09 -04:00
function Template ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'span' , [ 'title' , 'Hello' ] ) ;
{ text ( 1 , 'Greetings' ) ; }
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
}
2018-04-14 14:52:53 -04:00
expect ( ngDevMode ) . toHaveProperties ( {
firstTemplatePass : 1 ,
2018-05-16 08:56:01 -04:00
tNode : 3 , // 1 for div, 1 for text, 1 for host element
2018-09-13 19:07:23 -04:00
tView : 2 , // 1 for root view, 1 for template
2018-04-14 14:52:53 -04:00
rendererCreateElement : 1 ,
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should render and update basic "Hello, World" template' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'h1' ) ;
{ text ( 1 ) ; }
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
textBinding ( 1 , interpolation1 ( 'Hello, ' , ctx . name , '!' ) ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-18 14:14:50 -04:00
} , 2 , 1 ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
} ) ;
describe ( 'text bindings' , ( ) = > {
it ( 'should render "undefined" as "" when used with `bind()`' , ( ) = > {
2018-04-10 23:57:09 -04:00
function Template ( rf : RenderFlags , name : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
text ( 0 ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( name ) ) ;
}
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( Template , 'benoit' , 1 , 1 ) ) . toEqual ( 'benoit' ) ;
expect ( renderToHtml ( Template , undefined , 1 , 1 ) ) . toEqual ( '' ) ;
2018-05-15 16:07:20 -04:00
expect ( ngDevMode ) . toHaveProperties ( {
firstTemplatePass : 0 ,
2018-05-30 16:53:48 -04:00
tNode : 2 ,
2018-09-13 19:07:23 -04:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 16:07:20 -04:00
rendererSetText : 2 ,
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should render "null" as "" when used with `bind()`' , ( ) = > {
2018-04-10 23:57:09 -04:00
function Template ( rf : RenderFlags , name : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
text ( 0 ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( name ) ) ;
}
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( Template , 'benoit' , 1 , 1 ) ) . toEqual ( 'benoit' ) ;
expect ( renderToHtml ( Template , null , 1 , 1 ) ) . toEqual ( '' ) ;
2018-05-15 16:07:20 -04:00
expect ( ngDevMode ) . toHaveProperties ( {
firstTemplatePass : 0 ,
2018-05-30 16:53:48 -04:00
tNode : 2 ,
2018-09-13 19:07:23 -04:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 16:07:20 -04:00
rendererSetText : 2 ,
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should support creation-time values in text nodes' , ( ) = > {
2018-04-10 23:57:09 -04:00
function Template ( rf : RenderFlags , value : string ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
text ( 0 ) ;
2018-12-18 19:58:51 -05:00
textBinding ( 0 , value ) ;
2018-04-10 23:57:09 -04:00
}
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( Template , 'once' , 1 , 1 ) ) . toEqual ( 'once' ) ;
expect ( renderToHtml ( Template , 'twice' , 1 , 1 ) ) . toEqual ( 'once' ) ;
2018-05-15 16:07:20 -04:00
expect ( ngDevMode ) . toHaveProperties ( {
firstTemplatePass : 0 ,
2018-05-30 16:53:48 -04:00
tNode : 2 ,
2018-09-13 19:07:23 -04:00
tView : 2 , // 1 for root view, 1 for template
2018-05-15 16:07:20 -04:00
rendererSetText : 1 ,
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
} ) ;
2018-09-26 16:19:04 -04: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-27 00:09:03 -04:00
disableBindings ( ) ;
2018-09-26 17:33:48 -04:00
elementStart ( 2 , 'i' ) ;
text ( 3 , 'Hello {{ name }}!' ) ;
elementEnd ( ) ;
2018-09-27 00:09:03 -04:00
enableBindings ( ) ;
2018-09-26 16:19:04 -04:00
elementEnd ( ) ;
text ( 4 ) ;
}
if ( rf & RenderFlags . Update ) {
const ref = reference ( 1 ) as any ;
2018-09-26 17:33:48 -04:00
textBinding ( 4 , interpolation1 ( ' ' , ref . id , ' ' ) ) ;
2018-09-26 16:19:04 -04: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 17:33:48 -04:00
ngOnInit() { directiveInvoked = true ; }
2018-09-26 16:19:04 -04: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-27 00:09:03 -04:00
disableBindings ( ) ;
2018-09-26 17:33:48 -04:00
elementStart ( 1 , 'i' ) ;
text ( 2 , 'Hello {{ name }}!' ) ;
elementEnd ( ) ;
2018-09-27 00:09:03 -04:00
enableBindings ( ) ;
2018-09-26 16:19:04 -04: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 17:33:48 -04:00
ngOnInit() { directiveInvoked = true ; }
2018-09-26 16:19:04 -04: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-27 00:09:03 -04:00
disableBindings ( ) ;
2018-09-26 17:33:48 -04:00
elementStart ( 1 , 'i' , [ 'directive' , '' ] ) ;
text ( 2 , 'Hello {{ name }}!' ) ;
elementEnd ( ) ;
2018-09-27 00:09:03 -04:00
enableBindings ( ) ;
2018-09-26 16:19:04 -04: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 17:23:03 -05:00
describe ( 'Siblings update' , ( ) = > {
it ( 'should handle a flat list of static/bound text nodes' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
text ( 0 , 'Hello ' ) ;
text ( 1 ) ;
text ( 2 , '!' ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
textBinding ( 1 , bind ( ctx . name ) ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-18 14:14:50 -04:00
} , 3 , 1 ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
it ( 'should handle a list of static/bound text nodes as element children' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'b' ) ;
2017-12-01 17:23:03 -05:00
{
2018-02-06 19:11:20 -05:00
text ( 1 , 'Hello ' ) ;
text ( 2 ) ;
text ( 3 , '!' ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
textBinding ( 2 , bind ( ctx . name ) ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-18 14:14:50 -04:00
} , 4 , 1 ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
it ( 'should render/update text node as a child of a deep list of elements' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'b' ) ;
2017-12-01 17:23:03 -05:00
{
2018-02-06 19:11:20 -05:00
elementStart ( 1 , 'b' ) ;
2017-12-01 17:23:03 -05:00
{
2018-02-06 19:11:20 -05:00
elementStart ( 2 , 'b' ) ;
2017-12-01 17:23:03 -05:00
{
2018-02-06 19:11:20 -05:00
elementStart ( 3 , 'b' ) ;
{ text ( 4 ) ; }
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
textBinding ( 4 , interpolation1 ( 'Hello ' , ctx . name , '!' ) ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-18 14:14:50 -04:00
} , 5 , 1 ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
it ( 'should update 2 sibling elements' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'b' ) ;
2017-12-01 17:23:03 -05:00
{
2018-07-27 17:28:22 -04:00
element ( 1 , 'span' ) ;
2018-02-06 19:11:20 -05:00
elementStart ( 2 , 'span' , [ 'class' , 'foo' ] ) ;
2017-12-01 17:23:03 -05:00
{ }
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
elementAttribute ( 2 , 'id' , bind ( ctx . id ) ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-18 14:14:50 -04:00
} , 3 , 1 ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
it ( 'should handle sibling text node after element with child text node' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'p' ) ;
{ text ( 1 , 'hello' ) ; }
elementEnd ( ) ;
text ( 2 , 'world' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-16 21:53:21 -04:00
} , 3 ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( '<p>hello</p>world' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
} ) ;
describe ( 'basic components' , ( ) = > {
class TodoComponent {
value = ' one' ;
static ngComponentDef = defineComponent ( {
2018-01-22 18:27:21 -05:00
type : TodoComponent ,
2018-03-29 19:41:45 -04:00
selectors : [ [ 'todo' ] ] ,
2018-08-16 21:53:21 -04:00
consts : 3 ,
2018-08-18 14:14:50 -04:00
vars : 1 ,
2018-04-10 23:57:09 -04:00
template : function TodoTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'p' ) ;
2017-12-01 17:23:03 -05:00
{
2018-02-06 19:11:20 -05:00
text ( 1 , 'Todo' ) ;
text ( 2 ) ;
2017-12-01 17:23:03 -05:00
}
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
textBinding ( 2 , bind ( ctx . value ) ) ;
}
2017-12-01 17:23:03 -05:00
} ,
factory : ( ) = > new TodoComponent
} ) ;
}
2018-03-29 15:58:41 -04:00
const defs = [ TodoComponent ] ;
2018-03-26 00:32:39 -04:00
2017-12-01 17:23:03 -05:00
it ( 'should support a basic component template' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'todo' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , defs ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( '<todo><p>Todo one</p></todo>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should support a component template with sibling' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'todo' ) ;
2018-03-21 18:10:34 -04:00
text ( 1 , 'two' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 2 , 0 , defs ) ;
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( '<todo><p>Todo one</p></todo>two' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should support a component template with component sibling' , ( ) = > {
/ * *
* < todo > < / todo >
* < todo > < / todo >
* /
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'todo' ) ;
element ( 1 , 'todo' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 2 , 0 , defs ) ;
2018-08-16 21:53:21 -04: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 17:23:03 -05: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 18:27:21 -05:00
type : TodoComponentHostBinding ,
2018-03-29 19:41:45 -04:00
selectors : [ [ 'todo' ] ] ,
2018-08-16 21:53:21 -04:00
consts : 1 ,
2018-08-18 14:14:50 -04:00
vars : 1 ,
2017-12-01 17:23:03 -05:00
template : function TodoComponentHostBindingTemplate (
2018-04-10 23:57:09 -04:00
rf : RenderFlags , ctx : TodoComponentHostBinding ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
text ( 0 ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
textBinding ( 0 , bind ( ctx . title ) ) ;
}
2017-12-01 17:23:03 -05:00
} ,
factory : ( ) = > cmptInstance = new TodoComponentHostBinding ,
2018-11-20 18:20:19 -05:00
hostBindings : function ( rf : RenderFlags , ctx : any , elementIndex : number ) : void {
2018-11-27 15:05:26 -05:00
if ( rf & RenderFlags . Create ) {
allocHostVars ( 1 ) ;
}
2018-11-20 18:20:19 -05:00
if ( rf & RenderFlags . Update ) {
// host bindings
elementProperty ( elementIndex , 'title' , bind ( ctx . title ) ) ;
}
2017-12-01 17:23:03 -05:00
}
} ) ;
}
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'todo' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , [ TodoComponentHostBinding ] ) ;
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( '<todo title="one">one</todo>' ) ;
2017-12-01 17:23:03 -05:00
cmptInstance ! . title = 'two' ;
2018-08-16 21:53:21 -04:00
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<todo title="two">two</todo>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
2018-03-16 23:31:24 -04:00
it ( 'should support root component with host attribute' , ( ) = > {
class HostAttributeComp {
static ngComponentDef = defineComponent ( {
type : HostAttributeComp ,
2018-03-29 19:41:45 -04:00
selectors : [ [ 'host-attr-comp' ] ] ,
2018-03-16 23:31:24 -04:00
factory : ( ) = > new HostAttributeComp ( ) ,
2018-08-16 21:53:21 -04:00
consts : 0 ,
2018-08-18 14:14:50 -04:00
vars : 0 ,
2019-01-11 17:03:37 -05:00
hostBindings : function ( rf , ctx , elIndex ) {
if ( rf & RenderFlags . Create ) {
2019-03-15 16:45:08 -04:00
elementHostAttrs ( [ 'role' , 'button' ] ) ;
2019-01-11 17:03:37 -05:00
}
} ,
2018-04-10 23:57:09 -04:00
template : ( rf : RenderFlags , ctx : HostAttributeComp ) = > { } ,
2018-03-16 23:31:24 -04:00
} ) ;
}
const fixture = new ComponentFixture ( HostAttributeComp ) ;
expect ( fixture . hostElement . getAttribute ( 'role' ) ) . toEqual ( 'button' ) ;
} ) ;
2017-12-01 17:23:03 -05:00
it ( 'should support component with bindings in template' , ( ) = > {
/** <p> {{ name }} </p>*/
class MyComp {
name = 'Bess' ;
static ngComponentDef = defineComponent ( {
2018-01-22 18:27:21 -05:00
type : MyComp ,
2018-03-29 19:41:45 -04:00
selectors : [ [ 'comp' ] ] ,
2018-08-16 21:53:21 -04:00
consts : 2 ,
2018-08-18 14:14:50 -04:00
vars : 1 ,
2018-04-10 23:57:09 -04:00
template : function MyCompTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'p' ) ;
{ text ( 1 ) ; }
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
textBinding ( 1 , bind ( ctx . name ) ) ;
}
2017-12-01 17:23:03 -05:00
} ,
factory : ( ) = > new MyComp
} ) ;
}
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'comp' ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , [ MyComp ] ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( '<comp><p>Bess</p></comp>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should support a component with sub-views' , ( ) = > {
/ * *
* % if ( condition ) {
* < div > text < / div >
* % }
* /
class MyComp {
2018-06-18 19:38:33 -04:00
// TODO(issue/24571): remove '!'.
condition ! : boolean ;
2017-12-01 17:23:03 -05:00
static ngComponentDef = defineComponent ( {
2018-01-22 18:27:21 -05:00
type : MyComp ,
2018-03-29 19:41:45 -04:00
selectors : [ [ 'comp' ] ] ,
2018-08-16 21:53:21 -04:00
consts : 1 ,
2018-08-18 14:14:50 -04:00
vars : 0 ,
2018-04-10 23:57:09 -04:00
template : function MyCompTemplate ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
container ( 0 ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 ) ;
{
if ( ctx . condition ) {
2018-08-18 14:14:50 -04:00
let rf1 = embeddedViewStart ( 0 , 2 , 0 ) ;
2018-04-10 23:57:09 -04:00
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'div' ) ;
{ text ( 1 , 'text' ) ; }
elementEnd ( ) ;
}
embeddedViewEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
}
2018-04-10 23:57:09 -04:00
containerRefreshEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
} ,
factory : ( ) = > new MyComp ,
inputs : { condition : 'condition' }
} ) ;
}
/** <comp [condition]="condition"></comp> */
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'comp' ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'condition' , bind ( ctx . condition ) ) ;
}
2018-08-18 14:14:50 -04:00
} , 1 , 1 , [ MyComp ] ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
fixture . component . condition = true ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<comp><div>text</div></comp>' ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
fixture . component . condition = false ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<comp></comp>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
} ) ;
2018-07-26 11:22:41 -04:00
describe ( 'ng-container' , ( ) = > {
it ( 'should insert as a child of a regular element' , ( ) = > {
/ * *
* < div > before | < ng - container > Greetings < span > < / span > < / n g - c o n t a i n e r > | a f t e r < / d i v >
* /
function Template() {
elementStart ( 0 , 'div' ) ;
{
text ( 1 , 'before|' ) ;
elementContainerStart ( 2 ) ;
{
text ( 3 , 'Greetings' ) ;
element ( 4 , 'span' ) ;
}
elementContainerEnd ( ) ;
text ( 5 , '|after' ) ;
}
elementEnd ( ) ;
}
2018-08-16 21:53:21 -04:00
const fixture = new TemplateFixture ( Template , ( ) = > { } , 6 ) ;
2018-07-26 11:22:41 -04:00
expect ( fixture . html ) . toEqual ( '<div>before|Greetings<span></span>|after</div>' ) ;
} ) ;
2018-08-01 09:19:27 -04:00
it ( 'should add and remove DOM nodes when ng-container is a child of a regular element' , ( ) = > {
/ * *
* { % if ( value ) { % }
* < div >
* < ng - container > content < / n g - c o n t a i n e r >
* < / 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 14:14:50 -04:00
let rf1 = embeddedViewStart ( 0 , 3 , 0 ) ;
2018-08-01 09:19:27 -04:00
{
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'div' ) ;
{
elementContainerStart ( 1 ) ;
{ text ( 2 , 'content' ) ; }
elementContainerEnd ( ) ;
}
elementEnd ( ) ;
}
}
embeddedViewEnd ( ) ;
}
containerRefreshEnd ( ) ;
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
2018-08-01 09:19:27 -04: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 < / n g - c o n t a i n e r >
* { % } % }
* /
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 14:14:50 -04:00
let rf1 = embeddedViewStart ( 0 , 2 , 0 ) ;
2018-08-01 09:19:27 -04:00
{
if ( rf1 & RenderFlags . Create ) {
elementContainerStart ( 0 ) ;
{ text ( 1 , 'content' ) ; }
elementContainerEnd ( ) ;
}
}
embeddedViewEnd ( ) ;
}
containerRefreshEnd ( ) ;
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
2018-08-01 09:19:27 -04: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 < / n g - c o n t a i n e r >
* /
// equivalent to:
/ * *
* < ng - template [ ngIf ] = " value " >
* < ng - container >
* content
* < / n g - c o n t a i n e r >
* < / n g - t e m p l a t e >
* /
const TestCmpt =
createComponent ( 'test-cmpt' , function ( rf : RenderFlags , ctx : { value : any } ) {
if ( rf & RenderFlags . Create ) {
2019-03-07 03:31:31 -05:00
template ( 0 , ngIfTemplate , 2 , 0 , 'ng-template' , [ AttributeMarker . Bindings , 'ngIf' ] ) ;
2018-08-01 09:19:27 -04:00
}
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'ngIf' , bind ( ctx . value ) ) ;
}
2018-08-18 14:14:50 -04:00
} , 1 , 1 , [ NgIf ] ) ;
2018-08-01 09:19:27 -04: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 10:40:16 -04: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 21:38:13 -04:00
factory :
2018-10-08 19:04:46 -04:00
( ) = > testDirective = new TestDirective (
2018-09-21 21:38:13 -04:00
directiveInject ( TemplateRef as any ) , directiveInject ( ViewContainerRef as any ) ) ,
2018-08-06 10:40:16 -04: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
< / n g - c o n t a i n e r >
< / n g - t e m p l a t e > ` ;
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
2018-08-18 14:14:50 -04:00
template (
2018-12-12 18:23:12 -05:00
0 , embeddedTemplate , 2 , 0 , 'ng-template' ,
2019-03-07 03:31:31 -05:00
[ AttributeMarker . Bindings , 'testDirective' ] ) ;
2018-08-06 10:40:16 -04:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , [ TestDirective ] ) ;
2018-08-06 10:40:16 -04: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 09:19:27 -04:00
it ( 'should render at the component view root' , ( ) = > {
/ * *
* < ng - container > component template < / n g - c o n t a i n e r >
* /
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 ) ;
{ text ( 1 , 'component template' ) ; }
elementContainerEnd ( ) ;
}
2018-08-16 21:53:21 -04:00
} , 2 ) ;
2018-08-01 09:19:27 -04:00
function App() { element ( 0 , 'test-cmpt' ) ; }
2018-08-18 14:14:50 -04:00
const fixture = new TemplateFixture ( App , ( ) = > { } , 1 , 0 , [ TestCmpt ] ) ;
2018-08-01 09:19:27 -04:00
expect ( fixture . html ) . toEqual ( '<test-cmpt>component template</test-cmpt>' ) ;
} ) ;
2018-08-06 11:46:49 -04:00
it ( 'should render inside another ng-container' , ( ) = > {
/ * *
* < ng - container >
* < ng - container >
* < ng - container >
* content
* < / n g - c o n t a i n e r >
* < / n g - c o n t a i n e r >
* < / n g - c o n t a i n e r >
* /
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 21:53:21 -04:00
} , 4 ) ;
2018-08-06 11:46:49 -04:00
function App() { element ( 0 , 'test-cmpt' ) ; }
2018-08-18 14:14:50 -04:00
const fixture = new TemplateFixture ( App , ( ) = > { } , 1 , 0 , [ TestCmpt ] ) ;
2018-08-06 11:46:49 -04: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 19:04:46 -04:00
let testDirective : TestDirective ;
2018-08-06 11:46:49 -04: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 21:38:13 -04:00
factory :
2018-10-08 19:04:46 -04:00
( ) = > testDirective = new TestDirective (
2018-09-21 21:38:13 -04:00
directiveInject ( TemplateRef as any ) , directiveInject ( ViewContainerRef as any ) ) ,
2018-08-06 11:46:49 -04: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 21:53:21 -04:00
/ * *
* < ng - template testDirective >
* < ng - container >
* < ng - container >
* < ng - container >
* content
* < / n g - c o n t a i n e r >
* < / n g - c o n t a i n e r >
* < / n g - c o n t a i n e r >
* < / n g - t e m p l a t e >
* /
2018-08-06 11:46:49 -04:00
const TestCmpt = createComponent ( 'test-cmpt' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
2018-12-12 18:23:12 -05:00
template (
0 , embeddedTemplate , 4 , 0 , 'ng-template' ,
2019-03-07 03:31:31 -05:00
[ AttributeMarker . Bindings , 'testDirective' ] ) ;
2018-08-06 11:46:49 -04:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , [ TestDirective ] ) ;
2018-08-06 11:46:49 -04: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 11:22:41 -04:00
it ( 'should support directives and inject ElementRef' , ( ) = > {
class Directive {
constructor ( public elRef : ElementRef ) { }
static ngDirectiveDef = defineDirective ( {
type : Directive ,
selectors : [ [ '' , 'dir' , '' ] ] ,
2018-10-08 19:04:46 -04:00
factory : ( ) = > directive = new Directive ( directiveInject ( ElementRef ) ) ,
2018-07-26 11:22:41 -04:00
} ) ;
}
let directive : Directive ;
/ * *
* < div > < ng - container dir > < / n g - c o n t a i n e r > < / d i v >
* /
function Template() {
elementStart ( 0 , 'div' ) ;
{
2019-03-07 03:31:31 -05:00
elementContainerStart ( 1 , [ AttributeMarker . Bindings , 'dir' ] ) ;
2018-07-26 11:22:41 -04:00
elementContainerEnd ( ) ;
}
elementEnd ( ) ;
}
2018-08-18 14:14:50 -04:00
const fixture = new TemplateFixture ( Template , ( ) = > { } , 2 , 0 , [ Directive ] ) ;
2018-07-26 11:22:41 -04:00
expect ( fixture . html ) . toEqual ( '<div></div>' ) ;
expect ( directive ! . elRef . nativeElement . nodeType ) . toBe ( Node . COMMENT_NODE ) ;
} ) ;
2018-10-22 08:10:42 -04: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 < / n g - t e m p l a t e >
* < / n g - c o n t a i n e r >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
2019-03-07 03:31:31 -05:00
elementContainerStart ( 0 , [ AttributeMarker . Bindings , 'dir' ] ) ;
2018-12-12 18:23:12 -05:00
template (
1 , ContentTemplate , 1 , 0 , 'ng-template' , null , [ 'content' , '' ] , templateRefExtractor ) ;
2018-10-22 08:10:42 -04: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 < / n g - t e m p l a t e >
* < / n g - c o n t a i n e r >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags ) {
if ( rf & RenderFlags . Create ) {
elementContainerStart ( 0 ) ;
template (
2019-03-07 03:31:31 -05:00
1 , ContentTemplate , 1 , 0 , 'ng-template' , [ AttributeMarker . Bindings , 'dir' ] , [ ] ,
2018-10-22 08:10:42 -04: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 11:22:41 -04:00
it ( 'should not set any attributes' , ( ) = > {
/ * *
* < div > < ng - container id = "foo" > < / n g - c o n t a i n e r > < / d i v >
* /
function Template() {
elementStart ( 0 , 'div' ) ;
{
elementContainerStart ( 1 , [ 'id' , 'foo' ] ) ;
elementContainerEnd ( ) ;
}
elementEnd ( ) ;
}
2018-08-16 21:53:21 -04:00
const fixture = new TemplateFixture ( Template , ( ) = > { } , 2 ) ;
2018-07-26 11:22:41 -04:00
expect ( fixture . html ) . toEqual ( '<div></div>' ) ;
} ) ;
} ) ;
2018-01-25 09:32:21 -05:00
describe ( 'tree' , ( ) = > {
interface Tree {
beforeLabel? : string ;
subTrees? : Tree [ ] ;
afterLabel? : string ;
}
interface ParentCtx {
beforeTree : Tree ;
projectedTree : Tree ;
afterTree : Tree ;
}
2018-04-10 23:57:09 -04:00
function showLabel ( rf : RenderFlags , ctx : { label : string | undefined } ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
container ( 0 ) ;
2018-01-25 09:32:21 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 ) ;
{
if ( ctx . label != null ) {
2018-08-18 14:14:50 -04:00
let rf1 = embeddedViewStart ( 0 , 1 , 1 ) ;
2018-04-10 23:57:09 -04:00
if ( rf1 & RenderFlags . Create ) {
text ( 0 ) ;
}
if ( rf1 & RenderFlags . Update ) {
textBinding ( 0 , bind ( ctx . label ) ) ;
}
embeddedViewEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
}
2018-04-10 23:57:09 -04:00
containerRefreshEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
}
2018-04-10 23:57:09 -04:00
function showTree ( rf : RenderFlags , ctx : { tree : Tree } ) {
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
container ( 0 ) ;
container ( 1 ) ;
container ( 2 ) ;
2018-01-25 09:32:21 -05:00
}
2018-08-18 14:14:50 -04:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 ) ;
{
const rf0 = embeddedViewStart ( 0 , 1 , 0 ) ;
{ showLabel ( rf0 , { label : ctx.tree.beforeLabel } ) ; }
2018-02-06 20:27:16 -05:00
embeddedViewEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
2018-08-18 14:14:50 -04:00
containerRefreshEnd ( ) ;
containerRefreshStart ( 1 ) ;
{
for ( let subTree of ctx . tree . subTrees || [ ] ) {
2018-08-21 03:03:21 -04:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 ) ;
2018-08-18 14:14:50 -04: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 09:32:21 -05:00
}
}
class ChildComponent {
2018-06-18 19:38:33 -04:00
// TODO(issue/24571): remove '!'.
beforeTree ! : Tree ;
// TODO(issue/24571): remove '!'.
afterTree ! : Tree ;
2018-01-25 09:32:21 -05:00
static ngComponentDef = defineComponent ( {
2018-03-29 19:41:45 -04:00
selectors : [ [ 'child' ] ] ,
2018-01-25 09:32:21 -05:00
type : ChildComponent ,
2018-08-16 21:53:21 -04:00
consts : 3 ,
2018-08-18 14:14:50 -04:00
vars : 0 ,
2018-01-25 09:32:21 -05:00
template : function ChildComponentTemplate (
2018-04-10 23:57:09 -04:00
rf : RenderFlags , ctx : { beforeTree : Tree , afterTree : Tree } ) {
if ( rf & RenderFlags . Create ) {
2018-07-03 23:04:36 -04:00
projectionDef ( ) ;
container ( 0 ) ;
projection ( 1 ) ;
container ( 2 ) ;
2018-01-25 09:32:21 -05:00
}
2018-08-18 14:14:50 -04:00
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 ) ;
{
2018-08-21 03:03:21 -04:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 ) ;
2018-08-18 14:14:50 -04:00
{ showTree ( rf0 , { tree : ctx.beforeTree } ) ; }
embeddedViewEnd ( ) ;
}
containerRefreshEnd ( ) ;
containerRefreshStart ( 2 ) ;
{
2018-08-21 03:03:21 -04:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 ) ;
2018-08-18 14:14:50 -04:00
{ showTree ( rf0 , { tree : ctx.afterTree } ) ; }
embeddedViewEnd ( ) ;
}
containerRefreshEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
} ,
factory : ( ) = > new ChildComponent ,
inputs : { beforeTree : 'beforeTree' , afterTree : 'afterTree' }
} ) ;
}
2018-04-10 23:57:09 -04:00
function parentTemplate ( rf : RenderFlags , ctx : ParentCtx ) {
if ( rf & RenderFlags . Create ) {
2018-03-26 00:32:39 -04:00
elementStart ( 0 , 'child' ) ;
2018-03-21 18:10:34 -04:00
{ container ( 1 ) ; }
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'beforeTree' , bind ( ctx . beforeTree ) ) ;
elementProperty ( 0 , 'afterTree' , bind ( ctx . afterTree ) ) ;
containerRefreshStart ( 1 ) ;
{
2018-08-21 03:03:21 -04:00
const rf0 = embeddedViewStart ( 0 , 3 , 0 ) ;
2018-04-10 23:57:09 -04:00
{ showTree ( rf0 , { tree : ctx.projectedTree } ) ; }
embeddedViewEnd ( ) ;
}
containerRefreshEnd ( ) ;
2018-01-25 09:32:21 -05:00
}
}
it ( 'should work with a tree' , ( ) = > {
const ctx : ParentCtx = {
beforeTree : { subTrees : [ { beforeLabel : 'a' } ] } ,
projectedTree : { beforeLabel : 'p' } ,
afterTree : { afterLabel : 'z' }
} ;
2018-03-29 15:58:41 -04:00
const defs = [ ChildComponent ] ;
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs ) ) . toEqual ( '<child>apz</child>' ) ;
2018-01-25 09:32:21 -05:00
ctx . projectedTree = { subTrees : [ { } , { } , { subTrees : [ { } , { } ] } , { } ] } ;
ctx . beforeTree . subTrees ! . push ( { afterLabel : 'b' } ) ;
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs ) ) . toEqual ( '<child>abz</child>' ) ;
2018-01-25 09:32:21 -05:00
ctx . projectedTree . subTrees ! [ 1 ] . afterLabel = 'h' ;
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs ) ) . toEqual ( '<child>abhz</child>' ) ;
2018-01-25 09:32:21 -05:00
ctx . beforeTree . subTrees ! . push ( { beforeLabel : 'c' } ) ;
2018-08-18 14:14:50 -04:00
expect ( renderToHtml ( parentTemplate , ctx , 2 , 2 , defs ) ) . toEqual ( '<child>abchz</child>' ) ;
2018-01-25 09:32:21 -05:00
// To check the context easily:
// console.log(JSON.stringify(ctx));
} ) ;
} ) ;
2017-12-01 17:23:03 -05:00
describe ( 'element bindings' , ( ) = > {
describe ( 'elementAttribute' , ( ) = > {
it ( 'should support attribute bindings' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'span' ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ) ) ;
}
2018-08-18 14:14:50 -04:00
} , 1 , 1 ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
fixture . component . title = 'Hello' ;
fixture . update ( ) ;
2017-12-01 17:23:03 -05:00
// initial binding
2018-08-16 21:53:21 -04:00
expect ( fixture . html ) . toEqual ( '<span title="Hello"></span>' ) ;
2017-12-01 17:23:03 -05:00
// update binding
2018-08-16 21:53:21 -04:00
fixture . component . title = 'Hi!' ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span title="Hi!"></span>' ) ;
2017-12-01 17:23:03 -05:00
// remove attribute
2018-08-16 21:53:21 -04:00
fixture . component . title = null ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span></span>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should stringify values used attribute bindings' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'span' ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ) ) ;
}
2018-08-18 14:14:50 -04:00
} , 1 , 1 ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
fixture . component . title = NaN ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span title="NaN"></span>' ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
fixture . component . title = { toString : ( ) = > 'Custom toString' } ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span title="Custom toString"></span>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
it ( 'should update bindings' , ( ) = > {
2018-04-10 23:57:09 -04:00
function Template ( rf : RenderFlags , c : any ) {
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'b' ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04: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 17:23:03 -05:00
}
let args = [ '(' , 0 , 'a' , 1 , 'b' , 2 , 'c' , 3 , 'd' , 4 , 'e' , 5 , 'f' , 6 , 'g' , 7 , ')' ] ;
2018-08-21 03:03:21 -04:00
expect ( renderToHtml ( Template , args , 1 , 54 ) )
2017-12-01 17:23:03 -05: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 03:03:21 -04:00
expect ( renderToHtml ( Template , args , 1 , 54 ) )
2017-12-01 17:23:03 -05: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 03:03:21 -04:00
expect ( renderToHtml ( Template , args , 1 , 54 ) )
2017-12-01 17:23:03 -05: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 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'span' ) ;
container ( 1 ) ;
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ) ) ;
containerRefreshStart ( 1 ) ;
{
if ( true ) {
2018-08-21 03:03:21 -04:00
let rf1 = embeddedViewStart ( 1 , 1 , 1 ) ;
2018-04-10 23:57:09 -04:00
{
if ( rf1 & RenderFlags . Create ) {
elementStart ( 0 , 'b' ) ;
{ }
elementEnd ( ) ;
}
2018-08-21 03:03:21 -04:00
if ( rf1 & RenderFlags . Update ) {
elementAttribute ( 0 , 'title' , bind ( ctx . title ) ) ;
}
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
embeddedViewEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
}
2018-04-10 23:57:09 -04:00
containerRefreshEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-08-18 14:14:50 -04:00
} , 2 , 1 ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
fixture . component . title = 'Hello' ;
fixture . update ( ) ;
2017-12-01 17:23:03 -05:00
// initial binding
2018-08-16 21:53:21 -04:00
expect ( fixture . html ) . toEqual ( '<span title="Hello"><b title="Hello"></b></span>' ) ;
2017-12-01 17:23:03 -05:00
// update DOM manually
2018-08-16 21:53:21 -04:00
fixture . hostElement . querySelector ( 'b' ) ! . setAttribute ( 'title' , 'Goodbye' ) ;
2017-12-01 17:23:03 -05:00
// refresh with same binding
2018-08-16 21:53:21 -04:00
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span title="Hello"><b title="Goodbye"></b></span>' ) ;
2017-12-01 17:23:03 -05:00
// refresh again with same binding
2018-08-16 21:53:21 -04:00
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span title="Hello"><b title="Goodbye"></b></span>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
2018-02-16 14:36:50 -05: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 19:41:45 -04:00
selectors : [ [ '' , 'hostBindingDir' , '' ] ] ,
2018-02-16 14:36:50 -05:00
factory : function HostBindingDir_Factory() {
return hostBindingDir = new HostBindingDir ( ) ;
} ,
2018-11-20 18:20:19 -05:00
hostBindings : function HostBindingDir_HostBindings (
rf : RenderFlags , ctx : any , elIndex : number ) {
2018-11-27 15:05:26 -05:00
if ( rf & RenderFlags . Create ) {
allocHostVars ( 1 ) ;
}
2018-11-20 18:20:19 -05:00
if ( rf & RenderFlags . Update ) {
elementAttribute ( elIndex , 'aria-label' , bind ( ctx . label ) ) ;
}
2018-02-16 14:36:50 -05:00
}
} ) ;
}
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-07-27 17:28:22 -04:00
element ( 0 , 'div' , [ 'hostBindingDir' , '' ] ) ;
2018-02-16 14:36:50 -05:00
}
2018-08-18 14:14:50 -04:00
} , 1 , 0 , [ HostBindingDir ] ) ;
2018-02-16 14:36:50 -05:00
2018-08-16 21:53:21 -04:00
const fixture = new ComponentFixture ( App ) ;
expect ( fixture . html ) . toEqual ( ` <div aria-label="some label" hostbindingdir=""></div> ` ) ;
2018-02-16 14:36:50 -05:00
hostBindingDir ! . label = 'other label' ;
2018-08-16 21:53:21 -04:00
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( ` <div aria-label="other label" hostbindingdir=""></div> ` ) ;
2018-02-16 14:36:50 -05:00
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
describe ( 'elementStyle' , ( ) = > {
it ( 'should support binding to styles' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'span' ) ;
2018-07-11 13:58:18 -04:00
elementStyling ( null , [ 'border-color' ] ) ;
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
elementStyleProp ( 0 , 0 , ctx . color ) ;
2018-07-11 12:56:47 -04:00
elementStylingApply ( 0 ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
const fixture = new ComponentFixture ( App ) ;
fixture . component . color = 'red' ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span style="border-color: red;"></span>' ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
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 17:23:03 -05:00
} ) ;
it ( 'should support binding to styles with suffix' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'span' ) ;
2018-07-11 13:58:18 -04:00
elementStyling ( null , [ 'font-size' ] ) ;
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
elementStyleProp ( 0 , 0 , ctx . time , 'px' ) ;
2018-07-11 12:56:47 -04:00
elementStylingApply ( 0 ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
const fixture = new ComponentFixture ( App ) ;
fixture . component . time = '100' ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span style="font-size: 100px;"></span>' ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
fixture . component . time = 200 ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span style="font-size: 200px;"></span>' ) ;
2018-11-26 08:16:18 -05:00
fixture . component . time = 0 ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span style="font-size: 0px;"></span>' ) ;
2018-08-16 21:53:21 -04:00
fixture . component . time = null ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span></span>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
} ) ;
2018-12-13 18:51:47 -05:00
describe ( 'class-based styling' , ( ) = > {
2017-12-01 17:23:03 -05:00
it ( 'should support CSS class toggle' , ( ) = > {
2018-10-11 16:13:57 -04:00
/** <span [class.active]="class"></span> */
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-02-06 19:11:20 -05:00
elementStart ( 0 , 'span' ) ;
2018-07-11 13:58:18 -04:00
elementStyling ( [ 'active' ] ) ;
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
elementClassProp ( 0 , 0 , ctx . class ) ;
2018-07-11 12:56:47 -04:00
elementStylingApply ( 0 ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
// truthy values
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
// falsy values
2018-08-16 21:53:21 -04: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 17:23:03 -05:00
} ) ;
it ( 'should work correctly with existing static classes' , ( ) = > {
2018-08-16 21:53:21 -04:00
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2018-12-13 18:51:47 -05:00
elementStart ( 0 , 'span' , [ AttributeMarker . Classes , 'existing' ] ) ;
elementStyling ( [ 'existing' , 'active' ] ) ;
2018-02-06 19:11:20 -05:00
elementEnd ( ) ;
2017-12-01 17:23:03 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2018-08-16 21:53:21 -04:00
elementClassProp ( 0 , 1 , ctx . class ) ;
2018-07-11 12:56:47 -04:00
elementStylingApply ( 0 ) ;
2018-04-10 23:57:09 -04:00
}
2018-08-16 21:53:21 -04:00
} , 1 ) ;
const fixture = new ComponentFixture ( App ) ;
fixture . component . class = true ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span class="existing active"></span>' ) ;
2017-12-01 17:23:03 -05:00
2018-08-16 21:53:21 -04:00
fixture . component . class = false ;
fixture . update ( ) ;
expect ( fixture . html ) . toEqual ( '<span class="existing"></span>' ) ;
2017-12-01 17:23:03 -05:00
} ) ;
2018-10-11 16:13:57 -04:00
2018-10-12 18:02:54 -04: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 " > < / m y - c o m p >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'my-comp' ) ;
2018-12-13 18:51:47 -05:00
elementStyling ( [ 'active' ] ) ;
2018-10-12 18:02:54 -04: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 16:13:57 -04: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 18:02:54 -04:00
* Temp Content
2018-10-11 16:13:57 -04:00
* < / n g - t e m p l a t e >
* < structural - comp [ class.active ] = " class " [ tmp ] = " foo " > < / s t r u c t u r a l - c o m p >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-12-12 18:23:12 -05:00
template ( 0 , FooTemplate , 1 , 0 , 'ng-template' , null , [ 'foo' , '' ] , templateRefExtractor ) ;
2018-10-11 16:13:57 -04: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 18:02:54 -04:00
fixture . component . class = false ;
fixture . update ( ) ;
expect ( fixture . html )
. toEqual ( '<structural-comp class="">Comp Content</structural-comp>Temp Content' ) ;
} ) ;
2018-10-18 17:47:53 -04: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 ; }
}
2019-02-08 18:03:54 -05:00
let mockStyleDirective : DirWithStyleDirective ;
class DirWithStyleDirective {
static ngDirectiveDef = defineDirective ( {
type : DirWithStyleDirective ,
selectors : [ [ '' , 'DirWithStyle' , '' ] ] ,
factory : ( ) = > mockStyleDirective = new DirWithStyleDirective ( ) ,
inputs : { 'style' : 'style' }
} ) ;
public stylesVal : string = '' ;
set style ( value : string ) { this . stylesVal = value ; }
}
2018-12-13 18:51:47 -05:00
it ( 'should delegate initial classes to a [class] input binding if present on a directive on the same element' ,
2018-10-18 17:47:53 -04:00
( ) = > {
/ * *
2018-12-13 18:51:47 -05:00
* < div class = "apple orange banana" DirWithClass > < / div >
2018-10-18 17:47:53 -04:00
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2018-12-13 18:51:47 -05:00
elementStart (
0 , 'div' ,
2019-03-01 17:15:11 -05:00
[ 'DirWithClass' , '' , AttributeMarker . Classes , 'apple' , 'orange' , 'banana' ] ) ;
2018-12-13 18:51:47 -05:00
elementStyling ( ) ;
2018-10-18 17:47:53 -04:00
elementEnd ( ) ;
}
if ( rf & RenderFlags . Update ) {
elementStylingApply ( 0 ) ;
}
} , 1 , 0 , [ DirWithClassDirective ] ) ;
const fixture = new ComponentFixture ( App ) ;
expect ( mockClassDirective ! . classesVal ) . toEqual ( 'apple orange banana' ) ;
} ) ;
2019-02-08 18:03:54 -05:00
it ( 'should delegate initial styles to a [style] input binding if present on a directive on the same element' ,
( ) = > {
/ * *
* < div width = "width:100px; height:200px;" DirWithStyle > < / div >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
2019-03-01 17:15:11 -05:00
elementStart ( 0 , 'div' , [
'DirWithStyle' , '' , AttributeMarker . Styles , 'width' , '100px' , 'height' , '200px'
] ) ;
2019-02-08 18:03:54 -05:00
elementStyling ( ) ;
elementEnd ( ) ;
}
if ( rf & RenderFlags . Update ) {
elementStylingApply ( 0 ) ;
}
} , 1 , 0 , [ DirWithStyleDirective ] ) ;
const fixture = new ComponentFixture ( App ) ;
expect ( mockStyleDirective ! . stylesVal ) . toEqual ( 'width:100px;height:200px' ) ;
} ) ;
2018-10-18 17:47:53 -04:00
it ( 'should update `[class]` and bindings in the provided directive if the input is matched' ,
( ) = > {
/ * *
2018-12-13 18:51:47 -05:00
* < div DirWithClass > < / div >
2018-10-18 17:47:53 -04: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 18:51:47 -05:00
2019-02-08 18:03:54 -05:00
it ( 'should update `[style]` and bindings in the provided directive if the input is matched' ,
( ) = > {
/ * *
* < div DirWithStyle > < / div >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
elementStart ( 0 , 'div' , [ 'DirWithStyle' ] ) ;
elementStyling ( ) ;
elementEnd ( ) ;
}
if ( rf & RenderFlags . Update ) {
elementStylingMap ( 0 , null , { width : '200px' , height : '500px' } ) ;
elementStylingApply ( 0 ) ;
}
} , 1 , 0 , [ DirWithStyleDirective ] ) ;
const fixture = new ComponentFixture ( App ) ;
expect ( mockStyleDirective ! . stylesVal ) . toEqual ( 'width:200px;height:500px' ) ;
} ) ;
2018-12-13 18:51:47 -05: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 ) {
2019-03-15 16:45:08 -04:00
elementHostAttrs ( [
2019-01-11 17:03:37 -05:00
'title' , 'foo' , AttributeMarker . Classes , 'heavy' , 'golden' ,
AttributeMarker . Styles , 'color' , 'purple' , 'font-weight' , 'bold'
2018-12-13 18:51:47 -05:00
] ) ;
}
}
} ) ;
public classesVal : string = '' ;
}
/ * *
* < div DirWithInitialStyling
* class = "big"
2019-02-08 18:03:54 -05:00
* style = "color:black; font-size:200px" > < / div >
2018-12-13 18:51:47 -05:00
* /
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' ] ) ;
2019-01-11 17:03:37 -05:00
expect ( target . getAttribute ( 'title' ) ) . toEqual ( 'foo' ) ;
2018-12-13 18:51:47 -05:00
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 ] > < / m y - c o m p >
* /
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 (
[ AttributeMarker . Classes , 'def' , AttributeMarker . Styles , 'width' , '555px' ] ) ;
2019-03-15 16:45:08 -04:00
elementHostStyling ( [ 'xyz' ] , [ 'width' , 'height' ] ) ;
2018-12-13 18:51:47 -05:00
}
if ( rf & RenderFlags . Update ) {
2019-03-15 16:45:08 -04:00
elementHostStyleProp ( 0 , ctx . width ) ;
elementHostStyleProp ( 1 , ctx . height ) ;
elementHostClassProp ( 0 , ctx . activateXYZClass ) ;
elementHostStylingApply ( ) ;
2018-12-13 18:51:47 -05:00
}
}
} ) ;
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 ( ) ;
} ) ;
2019-02-08 18:03:54 -05:00
it ( 'should properly prioritize single style binding collisions when they exist on multiple directives' ,
2018-12-13 18:51:47 -05:00
( ) = > {
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 ) {
2019-03-15 16:45:08 -04:00
elementHostStyling ( null , [ 'width' ] ) ;
2018-12-13 18:51:47 -05:00
}
if ( rf & RenderFlags . Update ) {
2019-03-15 16:45:08 -04:00
elementHostStyleProp ( 0 , ctx . width ) ;
elementHostStylingApply ( ) ;
2018-12-13 18:51:47 -05:00
}
}
} ) ;
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 ) {
2019-03-15 16:45:08 -04:00
elementHostAttrs ( [ AttributeMarker . Styles , 'width' , '111px' ] ) ;
elementHostStyling ( null , [ 'width' ] ) ;
2018-12-13 18:51:47 -05:00
}
if ( rf & RenderFlags . Update ) {
2019-03-15 16:45:08 -04:00
elementHostStyleProp ( 0 , ctx . width ) ;
elementHostStylingApply ( ) ;
2018-12-13 18:51:47 -05:00
}
}
} ) ;
width : null | string = null ;
}
/ * *
2019-02-08 18:03:54 -05:00
* Component with the following template :
* < div Dir1WithStyle Dir2WithStyle [ style.width ] > < / div >
2018-12-13 18:51:47 -05:00
* /
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' ) ;
} ) ;
2019-01-16 14:11:33 -05:00
2019-02-08 18:03:54 -05:00
it ( 'should properly prioritize multi style binding collisions when they exist on multiple directives' ,
( ) = > {
let dir1Instance : Dir1WithStyling ;
/ * *
* Directive with host props :
* [ style ]
* [ class ]
* /
class Dir1WithStyling {
static ngDirectiveDef = defineDirective ( {
type : Dir1WithStyling ,
selectors : [ [ '' , 'Dir1WithStyling' , '' ] ] ,
factory : ( ) = > dir1Instance = new Dir1WithStyling ( ) ,
hostBindings : function ( rf : RenderFlags , ctx : Dir1WithStyling , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
2019-03-15 16:45:08 -04:00
elementHostStyling ( ) ;
2019-02-08 18:03:54 -05:00
}
if ( rf & RenderFlags . Update ) {
2019-03-15 16:45:08 -04:00
elementHostStylingMap ( ctx . classesExp , ctx . stylesExp ) ;
elementHostStylingApply ( ) ;
2019-02-08 18:03:54 -05:00
}
}
} ) ;
2019-01-16 14:11:33 -05:00
2019-02-08 18:03:54 -05:00
classesExp : any = { } ;
stylesExp : any = { } ;
}
2019-01-16 14:11:33 -05:00
2019-02-08 18:03:54 -05:00
let dir2Instance : Dir2WithStyling ;
/ * *
* Directive with host props :
* [ style ]
* style = "width:111px"
* /
class Dir2WithStyling {
static ngDirectiveDef = defineDirective ( {
type : Dir2WithStyling ,
selectors : [ [ '' , 'Dir2WithStyling' , '' ] ] ,
factory : ( ) = > dir2Instance = new Dir2WithStyling ( ) ,
hostBindings : function ( rf : RenderFlags , ctx : Dir2WithStyling , elementIndex : number ) {
if ( rf & RenderFlags . Create ) {
2019-03-15 16:45:08 -04:00
elementHostAttrs ( [ AttributeMarker . Styles , 'width' , '111px' ] ) ;
elementHostStyling ( ) ;
2019-02-08 18:03:54 -05:00
}
if ( rf & RenderFlags . Update ) {
2019-03-15 16:45:08 -04:00
elementHostStylingMap ( null , ctx . stylesExp ) ;
elementHostStylingApply ( ) ;
2019-02-08 18:03:54 -05:00
}
}
} ) ;
2019-01-16 14:11:33 -05:00
2019-02-08 18:03:54 -05:00
stylesExp : any = { } ;
}
/ * *
* Component with the following template :
* < div Dir1WithStyling Dir2WithStyling [ style ] [ class ] > < / div >
* /
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'div' , [ 'Dir1WithStyling' , '' , 'Dir2WithStyling' , '' ] ) ;
elementStyling ( ) ;
}
if ( rf & RenderFlags . Update ) {
elementStylingMap ( 0 , ctx . classesExp , ctx . stylesExp ) ;
elementStylingApply ( 0 ) ;
}
} , 1 , 0 , [ Dir1WithStyling , Dir2WithStyling ] ) ;
2017-12-01 17:23:03 -05:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( App ) ;
const target = fixture . hostElement . querySelector ( 'div' ) ! ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '111px' ) ;
2017-12-08 14:48:54 -05:00
2019-02-08 18:03:54 -05:00
const compInstance = fixture . component ;
compInstance . stylesExp = { width : '999px' , height : null } ;
compInstance . classesExp = { one : true , two : false } ;
dir1Instance ! . stylesExp = { width : '222px' } ;
dir1Instance ! . classesExp = { two : true , three : false } ;
dir2Instance ! . stylesExp = { width : '333px' , height : '100px' } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '999px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '100px' ) ;
expect ( target . classList . contains ( 'one' ) ) . toBeTruthy ( ) ;
expect ( target . classList . contains ( 'two' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'three' ) ) . toBeFalsy ( ) ;
compInstance . stylesExp = { } ;
compInstance ! . classesExp = { } ;
dir1Instance ! . stylesExp = { width : '222px' , height : '200px' } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '222px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '200px' ) ;
expect ( target . classList . contains ( 'one' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'two' ) ) . toBeTruthy ( ) ;
expect ( target . classList . contains ( 'three' ) ) . toBeFalsy ( ) ;
dir1Instance ! . stylesExp = { } ;
dir1Instance ! . classesExp = { } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '333px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '100px' ) ;
expect ( target . classList . contains ( 'one' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'two' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'three' ) ) . toBeFalsy ( ) ;
dir2Instance ! . stylesExp = { } ;
compInstance . stylesExp = { height : '900px' } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '111px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '900px' ) ;
dir1Instance ! . stylesExp = { width : '666px' , height : '600px' } ;
dir1Instance ! . classesExp = { four : true , one : true } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '666px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '900px' ) ;
expect ( target . classList . contains ( 'one' ) ) . toBeTruthy ( ) ;
expect ( target . classList . contains ( 'two' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'three' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'four' ) ) . toBeTruthy ( ) ;
compInstance . stylesExp = { width : '777px' } ;
compInstance . classesExp = { four : false } ;
fixture . update ( ) ;
expect ( target . style . getPropertyValue ( 'width' ) ) . toEqual ( '777px' ) ;
expect ( target . style . getPropertyValue ( 'height' ) ) . toEqual ( '600px' ) ;
expect ( target . classList . contains ( 'one' ) ) . toBeTruthy ( ) ;
expect ( target . classList . contains ( 'two' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'three' ) ) . toBeFalsy ( ) ;
expect ( target . classList . contains ( 'four' ) ) . toBeFalsy ( ) ;
} ) ;
} ) ;
it ( 'should properly handle and render interpolation for class attribute bindings' , ( ) = > {
const App = createComponent ( 'app' , function ( rf : RenderFlags , ctx : any ) {
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Create ) {
2019-02-08 18:03:54 -05:00
elementStart ( 0 , 'div' ) ;
elementStyling ( ) ;
elementEnd ( ) ;
2017-12-08 14:48:54 -05:00
}
2018-04-10 23:57:09 -04:00
if ( rf & RenderFlags . Update ) {
2019-02-08 18:03:54 -05:00
elementStylingMap ( 0 , interpolation2 ( '-' , ctx . name , '-' , ctx . age , '-' ) ) ;
elementStylingApply ( 0 ) ;
2017-12-08 14:48:54 -05:00
}
2019-02-08 18:03:54 -05:00
} , 1 , 2 ) ;
2017-12-08 14:48:54 -05:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( App ) ;
const target = fixture . hostElement . querySelector ( 'div' ) ! ;
expect ( target . classList . contains ( '-fred-36-' ) ) . toBeFalsy ( ) ;
2017-12-08 14:48:54 -05:00
2019-02-08 18:03:54 -05:00
fixture . component . name = 'fred' ;
fixture . component . age = '36' ;
fixture . update ( ) ;
2017-12-08 14:48:54 -05:00
2019-02-08 18:03:54 -05:00
expect ( target . classList . contains ( '-fred-36-' ) ) . toBeTruthy ( ) ;
2017-12-08 14:48:54 -05:00
} ) ;
} ) ;
2019-02-08 18:03:54 -05:00
} ) ;
2017-12-08 14:48:54 -05:00
2019-02-08 18:03:54 -05:00
describe ( 'template data' , ( ) = > {
it ( 'should re-use template data and node data' , ( ) = > {
/ * *
* % if ( condition ) {
* < div > < / div >
* % }
* /
function Template ( rf : RenderFlags , ctx : any ) {
if ( rf & RenderFlags . Create ) {
container ( 0 ) ;
}
if ( rf & RenderFlags . Update ) {
containerRefreshStart ( 0 ) ;
{
if ( ctx . condition ) {
let rf1 = embeddedViewStart ( 0 , 1 , 0 ) ;
if ( rf1 & RenderFlags . Create ) {
2018-07-31 14:14:06 -04:00
element ( 0 , 'div' ) ;
}
2019-02-08 18:03:54 -05:00
embeddedViewEnd ( ) ;
2018-07-31 14:14:06 -04:00
}
2019-02-08 18:03:54 -05:00
}
containerRefreshEnd ( ) ;
2018-07-31 14:14:06 -04:00
}
2019-02-08 18:03:54 -05:00
}
2018-07-31 14:14:06 -04:00
2019-02-08 18:03:54 -05:00
expect ( ( Template as any ) . ngPrivateData ) . toBeUndefined ( ) ;
2018-09-05 18:23:59 -04:00
2019-02-08 18:03:54 -05:00
renderToHtml ( Template , { condition : true } , 1 ) ;
2018-09-05 18:23:59 -04:00
2019-02-08 18:03:54 -05:00
const oldTemplateData = ( Template as any ) . ngPrivateData ;
const oldContainerData = ( oldTemplateData as any ) . data [ HEADER_OFFSET ] ;
const oldElementData = oldContainerData . tViews [ 0 ] [ HEADER_OFFSET ] ;
expect ( oldContainerData ) . not . toBeNull ( ) ;
expect ( oldElementData ) . not . toBeNull ( ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
renderToHtml ( Template , { condition : false } , 1 ) ;
renderToHtml ( Template , { condition : true } , 1 ) ;
const newTemplateData = ( Template as any ) . ngPrivateData ;
const newContainerData = ( oldTemplateData as any ) . data [ HEADER_OFFSET ] ;
const newElementData = oldContainerData . tViews [ 0 ] [ HEADER_OFFSET ] ;
expect ( newTemplateData === oldTemplateData ) . toBe ( true ) ;
expect ( newContainerData === oldContainerData ) . toBe ( true ) ;
expect ( newElementData === oldElementData ) . toBe ( true ) ;
} ) ;
} ) ;
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 21:50:57 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
const rendererFactory = new ProxyRenderer3Factory ( ) ;
new ComponentFixture ( StyledComp , { rendererFactory } ) ;
expect ( rendererFactory . lastCapturedType ! . styles ) . toEqual ( [ 'div { color: red; }' ] ) ;
expect ( rendererFactory . lastCapturedType ! . encapsulation ) . toEqual ( 100 ) ;
} ) ;
} ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
describe ( 'component animations' , ( ) = > {
it ( 'should pass in the component styles directly into the underlying renderer' , ( ) = > {
const animA = { name : 'a' } ;
const animB = { name : 'b' } ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
class AnimComp {
static ngComponentDef = defineComponent ( {
type : AnimComp ,
consts : 0 ,
vars : 0 ,
data : {
animation : [
animA ,
animB ,
] ,
} ,
selectors : [ [ 'foo' ] ] ,
factory : ( ) = > new AnimComp ( ) ,
template : ( rf : RenderFlags , ctx : AnimComp ) = > { }
} ) ;
}
const rendererFactory = new ProxyRenderer3Factory ( ) ;
new ComponentFixture ( AnimComp , { rendererFactory } ) ;
const capturedAnimations = rendererFactory . lastCapturedType ! . data ! [ 'animation' ] ;
expect ( Array . isArray ( capturedAnimations ) ) . toBeTruthy ( ) ;
expect ( capturedAnimations . length ) . toEqual ( 2 ) ;
expect ( capturedAnimations ) . toContain ( animA ) ;
expect ( capturedAnimations ) . toContain ( animB ) ;
} ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
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 ,
data : {
animation : [ ] ,
} ,
selectors : [ [ 'foo' ] ] ,
factory : ( ) = > new AnimComp ( ) ,
template : ( rf : RenderFlags , ctx : AnimComp ) = > { }
} ) ;
}
const rendererFactory = new ProxyRenderer3Factory ( ) ;
new ComponentFixture ( AnimComp , { rendererFactory } ) ;
const data = rendererFactory . lastCapturedType ! . data ;
expect ( data . animation ) . toEqual ( [ ] ) ;
} ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05: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 ) {
2019-03-07 03:31:31 -05:00
element ( 0 , 'div' , [ AttributeMarker . Bindings , '@fooAnimation' ] ) ;
2019-02-08 18:03:54 -05:00
}
if ( rf & RenderFlags . Update ) {
elementAttribute ( 0 , '@fooAnimation' , bind ( ctx . animationValue ) ) ;
}
}
} ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
animationValue = '123' ;
}
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
const rendererFactory = new MockRendererFactory ( [ 'setAttribute' ] ) ;
const fixture = new ComponentFixture ( AnimComp , { rendererFactory } ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
const renderer = rendererFactory . lastRenderer ! ;
fixture . component . animationValue = '456' ;
fixture . update ( ) ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
const spy = renderer . spies [ 'setAttribute' ] ;
const [ elm , attr , value ] = spy . calls . mostRecent ( ) . args ;
2018-09-06 21:50:57 -04:00
2019-02-08 18:03:54 -05:00
expect ( attr ) . toEqual ( '@fooAnimation' ) ;
expect ( value ) . toEqual ( '456' ) ;
} ) ;
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
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' , '' ] ) ;
}
}
} ) ;
}
const rendererFactory = new MockRendererFactory ( [ 'setProperty' ] ) ;
const fixture = new ComponentFixture ( AnimComp , { rendererFactory } ) ;
const renderer = rendererFactory . lastRenderer ! ;
fixture . update ( ) ;
const spy = renderer . spies [ 'setProperty' ] ;
const [ elm , attr , value ] = spy . calls . mostRecent ( ) . args ;
expect ( attr ) . toEqual ( '@fooAnimation' ) ;
} ) ;
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 ) ;
}
} ,
} ) ;
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
exp = 'go' ;
}
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
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 ]
} ) ;
}
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
const rendererFactory = new MockRendererFactory ( [ 'setProperty' ] ) ;
const fixture = new ComponentFixture ( ParentComp , { rendererFactory } ) ;
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
const renderer = rendererFactory . lastRenderer ! ;
fixture . update ( ) ;
2019-01-03 21:24:21 -05:00
2019-02-08 18:03:54 -05:00
const spy = renderer . spies [ 'setProperty' ] ;
const [ elm , attr , value ] = spy . calls . mostRecent ( ) . args ;
expect ( attr ) . toEqual ( '@fooAnim' ) ;
2018-09-05 18:23:59 -04:00
} ) ;
2019-02-08 18:03:54 -05:00
} ) ;
2018-09-05 18:23:59 -04:00
2019-02-08 18:03:54 -05: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 ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
}
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const host = fixture . hostElement ;
const parent = host . querySelector ( 'div' ) as any ;
const child = host . querySelector ( 'p' ) as any ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( parent [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
expect ( child [ MONKEY_PATCH_KEY_NAME ] ) . toBeFalsy ( ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
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' ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
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 ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( ParentComp ) ;
fixture . update ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const host = fixture . hostElement ;
const child = host . querySelector ( 'child-comp' ) as any ;
expect ( child [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05: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 ( ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
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' ) ;
}
} , 3 , 0 , 'ng-template' , [ 'ngIf' , '' ] ) ;
elementEnd ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 1 , 'ngIf' , true ) ;
}
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const host = fixture . hostElement ;
const [ section , div1 , p , div2 ] = Array . from ( host . querySelectorAll ( 'section, div, p' ) ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( section . nodeName . toLowerCase ( ) ) . toBe ( 'section' ) ;
expect ( section [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( div1 . nodeName . toLowerCase ( ) ) . toBe ( 'div' ) ;
expect ( div1 [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( p . nodeName . toLowerCase ( ) ) . toBe ( 'p' ) ;
expect ( p [ MONKEY_PATCH_KEY_NAME ] ) . toBeFalsy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( div2 . nodeName . toLowerCase ( ) ) . toBe ( 'div' ) ;
expect ( div2 [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
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' ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const section = fixture . hostElement . querySelector ( 'section' ) ! ;
const sectionContext = getLContext ( section ) ! ;
const sectionLView = sectionContext . lView ! ;
expect ( sectionContext . nodeIndex ) . toEqual ( HEADER_OFFSET ) ;
expect ( sectionLView . length ) . toBeGreaterThan ( HEADER_OFFSET ) ;
expect ( sectionContext . native ) . toBe ( section ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const div = fixture . hostElement . querySelector ( 'div' ) ! ;
const divContext = getLContext ( div ) ! ;
const divLView = divContext . lView ! ;
expect ( divContext . nodeIndex ) . toEqual ( HEADER_OFFSET + 1 ) ;
expect ( divLView . length ) . toBeGreaterThan ( HEADER_OFFSET ) ;
expect ( divContext . native ) . toBe ( div ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( divLView ) . toBe ( sectionLView ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
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' ) ;
}
}
} ) ;
}
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
const result1 = section [ MONKEY_PATCH_KEY_NAME ] ;
expect ( Array . isArray ( result1 ) ) . toBeTruthy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
const context = getLContext ( section ) ! ;
const result2 = section [ MONKEY_PATCH_KEY_NAME ] ;
expect ( Array . isArray ( result2 ) ) . toBeFalsy ( ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05:00
expect ( result2 ) . toBe ( context ) ;
expect ( result2 . lView ) . toBe ( result1 ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05: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 ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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 ( ) ;
const pContext = getLContext ( p ) ! ;
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 ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
elementStylingApply ( 0 ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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 ( ) ;
expect ( elementResult [ StylingIndex . ElementPosition ] ) . toBe ( section ) ;
const context = getLContext ( section ) ! ;
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 >
< / p r o j e c t i o n - c o m p >
< / 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 ( ) ;
2018-08-22 19:57:40 -04:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
2018-08-29 16:52:03 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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 ( ) ;
expect ( projectorComp [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
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 ( ) ;
const parentContext = getLContext ( section ) ! ;
const shadowContext = getLContext ( header ) ! ;
const projectedContext = getLContext ( p ) ! ;
const parentComponentData = parentContext . lView ;
const shadowComponentData = shadowContext . lView ;
const projectedComponentData = projectedContext . lView ;
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' ) ;
const context1 = getLContext ( elm1 ) ;
expect ( context1 ) . toBeFalsy ( ) ;
const elm2 = document . createElement ( 'div' ) ;
document . body . appendChild ( elm2 ) ;
const context2 = getLContext ( elm2 ) ;
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' ) ;
}
}
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const section = fixture . hostElement . querySelector ( 'section' ) ! as any ;
const manuallyCreatedElement = document . createElement ( 'div' ) ;
section . appendChild ( manuallyCreatedElement ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const context = getLContext ( manuallyCreatedElement ) ;
expect ( context ) . toBeFalsy ( ) ;
} ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05: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 ) = > { }
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( StructuredComp ) ;
fixture . update ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const hostElm = fixture . hostElement ;
const component = fixture . component ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const componentLView = ( component as any ) [ MONKEY_PATCH_KEY_NAME ] ;
expect ( Array . isArray ( componentLView ) ) . toBeTruthy ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const hostLView = ( hostElm as any ) [ MONKEY_PATCH_KEY_NAME ] ;
expect ( hostLView ) . toBe ( componentLView ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const context1 = getLContext ( hostElm ) ! ;
expect ( context1 . lView ) . toBe ( hostLView ) ;
expect ( context1 . native ) . toEqual ( hostElm ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const context2 = getLContext ( component ) ! ;
expect ( context2 ) . toBe ( context1 ) ;
expect ( context2 . lView ) . toBe ( hostLView ) ;
expect ( context2 . native ) . toEqual ( hostElm ) ;
} ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
it ( 'should by default monkey-patch the directives with LView so that they can be examined' ,
( ) = > {
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 ( )
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
class MyDir2 {
static ngDirectiveDef = defineDirective ( {
type : MyDir2 ,
selectors : [ [ '' , 'my-dir-2' , '' ] ] ,
factory : ( ) = > myDir2Instance = new MyDir2 ( )
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
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' ] ) ;
2018-08-29 16:52:03 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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 ;
const context = getLContext ( hostElm ) ! ;
const componentView = context . lView [ context . nodeIndex ] ;
expect ( componentView ) . toContain ( myDir1Instance ) ;
expect ( componentView ) . toContain ( myDir2Instance ) ;
expect ( componentView ) . toContain ( myDir3Instance ) ;
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 ( ) ;
const d1Context = getLContext ( myDir1Instance ) ! ;
const d2Context = getLContext ( myDir2Instance ) ! ;
const d3Context = getLContext ( myDir3Instance ) ! ;
expect ( d1Context . lView ) . toEqual ( componentView ) ;
expect ( d2Context . lView ) . toEqual ( componentView ) ;
expect ( d3Context . lView ) . toEqual ( componentView ) ;
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 ) ;
expect ( d1Context . nodeIndex ) . toEqual ( HEADER_OFFSET ) ;
expect ( d1Context . native ) . toBe ( div1 ) ;
expect ( d1Context . directives as any [ ] ) . toEqual ( [ myDir1Instance , myDir2Instance ] ) ;
expect ( d2Context . nodeIndex ) . toEqual ( HEADER_OFFSET ) ;
expect ( d2Context . native ) . toBe ( div1 ) ;
expect ( d2Context . directives as any [ ] ) . toEqual ( [ myDir1Instance , myDir2Instance ] ) ;
expect ( d3Context . nodeIndex ) . toEqual ( HEADER_OFFSET + 1 ) ;
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 ( )
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
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' ) ;
2018-08-29 16:52:03 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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' , '' ] ) ;
2018-08-29 16:52:03 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
const fixture = new ComponentFixture ( ParentComp ) ;
fixture . update ( ) ;
const childCompHostElm = fixture . hostElement . querySelector ( 'child-comp' ) ! as any ;
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 ) ;
const childNodeContext = getLContext ( childCompHostElm ) ! ;
expect ( childNodeContext . component ) . toBeFalsy ( ) ;
expect ( childNodeContext . directives ) . toBeFalsy ( ) ;
assertMonkeyPatchValueIsLView ( myDir1Instance ) ;
assertMonkeyPatchValueIsLView ( myDir2Instance ) ;
assertMonkeyPatchValueIsLView ( childComponentInstance ) ;
expect ( getLContext ( myDir1Instance ) ) . toBe ( childNodeContext ) ;
expect ( childNodeContext . component ) . toBeFalsy ( ) ;
expect ( childNodeContext . directives ! . length ) . toEqual ( 2 ) ;
assertMonkeyPatchValueIsLView ( myDir1Instance , false ) ;
assertMonkeyPatchValueIsLView ( myDir2Instance , false ) ;
assertMonkeyPatchValueIsLView ( childComponentInstance ) ;
expect ( getLContext ( myDir2Instance ) ) . toBe ( childNodeContext ) ;
expect ( childNodeContext . component ) . toBeFalsy ( ) ;
expect ( childNodeContext . directives ! . length ) . toEqual ( 2 ) ;
assertMonkeyPatchValueIsLView ( myDir1Instance , false ) ;
assertMonkeyPatchValueIsLView ( myDir2Instance , false ) ;
assertMonkeyPatchValueIsLView ( childComponentInstance ) ;
expect ( getLContext ( childComponentInstance ) ) . toBe ( childNodeContext ) ;
expect ( childNodeContext . component ) . toBeTruthy ( ) ;
expect ( childNodeContext . directives ! . length ) . toEqual ( 2 ) ;
assertMonkeyPatchValueIsLView ( myDir1Instance , false ) ;
assertMonkeyPatchValueIsLView ( myDir2Instance , false ) ;
assertMonkeyPatchValueIsLView ( childComponentInstance , false ) ;
function assertMonkeyPatchValueIsLView ( value : any , yesOrNo = true ) {
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' ) ;
2018-08-29 16:52:03 -04:00
}
2019-02-08 18:03:54 -05:00
}
} ) ;
}
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 ( ) ;
}
}
} ) ;
}
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( ParentComp ) ;
fixture . update ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const host = fixture . hostElement ;
const child = host . querySelector ( 'child-comp' ) as any ;
expect ( child [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const context = getLContext ( child ) ! ;
expect ( child [ MONKEY_PATCH_KEY_NAME ] ) . toBeTruthy ( ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const componentData = context . lView [ context . nodeIndex ] ;
const component = componentData [ CONTEXT ] ;
expect ( component instanceof ChildComp ) . toBeTruthy ( ) ;
expect ( component [ MONKEY_PATCH_KEY_NAME ] ) . toBe ( context . lView ) ;
2018-08-29 16:52:03 -04:00
2019-02-08 18:03:54 -05:00
const componentContext = getLContext ( component ) ! ;
expect ( component [ MONKEY_PATCH_KEY_NAME ] ) . toBe ( componentContext ) ;
expect ( componentContext . nodeIndex ) . toEqual ( context . nodeIndex ) ;
expect ( componentContext . native ) . toEqual ( context . native ) ;
expect ( componentContext . lView ) . toEqual ( context . lView ) ;
} ) ;
} ) ;
2018-08-22 19:57:40 -04:00
2019-02-08 18:03:54 -05: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 ( ) ,
consts : 1 ,
vars : 1 ,
template : ( rf : RenderFlags , ctx : SanitizationComp ) = > {
if ( rf & RenderFlags . Create ) {
element ( 0 , 'a' ) ;
2018-05-09 18:30:16 -04:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
elementProperty ( 0 , 'href' , bind ( ctx . href ) , sanitizeUrl ) ;
}
}
} ) ;
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
private href = '' ;
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
updateLink ( href : any ) { this . href = href ; }
}
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
const sanitizer = new LocalSanitizer ( ( value ) = > { return 'http://bar' ; } ) ;
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( SanitizationComp , { sanitizer } ) ;
fixture . component . updateLink ( 'http://foo' ) ;
fixture . update ( ) ;
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
const anchor = fixture . hostElement . querySelector ( 'a' ) ! ;
expect ( anchor . getAttribute ( 'href' ) ) . toEqual ( 'http://bar' ) ;
2018-05-09 18:30:16 -04:00
2019-02-08 18:03:54 -05:00
fixture . component . updateLink ( sanitizer . bypassSecurityTrustUrl ( 'http://foo' ) ) ;
fixture . update ( ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
expect ( anchor . getAttribute ( 'href' ) ) . toEqual ( 'http://foo' ) ;
} ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05: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 ) ;
2019-01-03 13:04:06 -05:00
}
2019-02-08 18:03:54 -05:00
if ( rf & RenderFlags . Update ) {
elementProperty ( elementIndex , 'cite' , bind ( ctx . cite ) , sanitizeUrl , true ) ;
}
}
} ) ;
}
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
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 ]
} ) ;
}
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
const sanitizer = new LocalSanitizer ( ( value ) = > 'http://bar' ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
const fixture = new ComponentFixture ( SimpleComp , { sanitizer } ) ;
hostBindingDir ! . cite = 'http://foo' ;
fixture . update ( ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
const anchor = fixture . hostElement . querySelector ( 'blockquote' ) ! ;
expect ( anchor . getAttribute ( 'cite' ) ) . toEqual ( 'http://bar' ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
hostBindingDir ! . cite = sanitizer . bypassSecurityTrustUrl ( 'http://foo' ) ;
fixture . update ( ) ;
2019-01-03 13:04:06 -05:00
2019-02-08 18:03:54 -05:00
expect ( anchor . getAttribute ( 'cite' ) ) . toEqual ( 'http://foo' ) ;
2018-05-09 18:30:16 -04:00
} ) ;
2017-12-01 17:23:03 -05:00
} ) ;
2018-05-09 18:30:16 -04: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 14:14:06 -04:00
2018-09-06 21:50:57 -04:00
class ProxyRenderer3Factory implements RendererFactory3 {
2018-07-31 14:14:06 -04:00
lastCapturedType : RendererType2 | null = null ;
createRenderer ( hostElement : RElement | null , rendererType : RendererType2 | null ) : Renderer3 {
this . lastCapturedType = rendererType ;
return domRendererFactory3 . createRenderer ( hostElement , rendererType ) ;
}
2018-12-13 18:51:47 -05:00
}