There is an encoding issue with using delta `Δ`, where the browser will attempt to detect the file encoding if the character set is not explicitly declared on a `<script/>` tag, and Chrome will find the `Δ` character and decide it is window-1252 encoding, which misinterprets the `Δ` character to be some other character that is not a valid JS identifier character
So back to the frog eyes we go.
```
    __
   /ɵɵ\
  ( -- ) - I am ineffable. I am forever.
 _/    \_
/  \  /  \
==  ==  ==
```
PR Close #30546
		
	
			
		
			
				
	
	
		
			1199 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1199 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {RendererType2} from '../../src/render/api';
 | |
| import {getLContext} from '../../src/render3/context_discovery';
 | |
| import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index';
 | |
| import {ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all';
 | |
| import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
 | |
| import {RenderFlags} from '../../src/render3/interfaces/definition';
 | |
| import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
 | |
| import {StylingIndex} from '../../src/render3/interfaces/styling';
 | |
| import {CONTEXT, HEADER_OFFSET} from '../../src/render3/interfaces/view';
 | |
| import {ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
 | |
| import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
 | |
| 
 | |
| import {NgIf} from './common_with_def';
 | |
| import {ComponentFixture, MockRendererFactory, renderToHtml} from './render_util';
 | |
| 
 | |
| describe('render3 integration test', () => {
 | |
| 
 | |
|   describe('render', () => {
 | |
|     describe('text bindings', () => {
 | |
|       it('should support creation-time values in text nodes', () => {
 | |
|         function Template(rf: RenderFlags, value: string) {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵtext(0);
 | |
|             ɵɵtextBinding(0, value);
 | |
|           }
 | |
|         }
 | |
|         expect(renderToHtml(Template, 'once', 1, 1)).toEqual('once');
 | |
|         expect(renderToHtml(Template, 'twice', 1, 1)).toEqual('once');
 | |
|         expect(ngDevMode).toHaveProperties({
 | |
|           firstTemplatePass: 0,
 | |
|           tNode: 2,
 | |
|           tView: 2,  // 1 for root view, 1 for template
 | |
|           rendererSetText: 1,
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('tree', () => {
 | |
|     interface Tree {
 | |
|       beforeLabel?: string;
 | |
|       subTrees?: Tree[];
 | |
|       afterLabel?: string;
 | |
|     }
 | |
| 
 | |
|     interface ParentCtx {
 | |
|       beforeTree: Tree;
 | |
|       projectedTree: Tree;
 | |
|       afterTree: Tree;
 | |
|     }
 | |
| 
 | |
|     function showLabel(rf: RenderFlags, ctx: {label: string | undefined}) {
 | |
|       if (rf & RenderFlags.Create) {
 | |
|         ɵɵcontainer(0);
 | |
|       }
 | |
|       if (rf & RenderFlags.Update) {
 | |
|         ɵɵcontainerRefreshStart(0);
 | |
|         {
 | |
|           if (ctx.label != null) {
 | |
|             let rf1 = ɵɵembeddedViewStart(0, 1, 1);
 | |
|             if (rf1 & RenderFlags.Create) {
 | |
|               ɵɵtext(0);
 | |
|             }
 | |
|             if (rf1 & RenderFlags.Update) {
 | |
|               ɵɵtextBinding(0, ɵɵbind(ctx.label));
 | |
|             }
 | |
|             ɵɵembeddedViewEnd();
 | |
|           }
 | |
|         }
 | |
|         ɵɵcontainerRefreshEnd();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function showTree(rf: RenderFlags, ctx: {tree: Tree}) {
 | |
|       if (rf & RenderFlags.Create) {
 | |
|         ɵɵcontainer(0);
 | |
|         ɵɵcontainer(1);
 | |
|         ɵɵcontainer(2);
 | |
|       }
 | |
|       if (rf & RenderFlags.Update) {
 | |
|         ɵɵcontainerRefreshStart(0);
 | |
|         {
 | |
|           const rf0 = ɵɵembeddedViewStart(0, 1, 0);
 | |
|           { showLabel(rf0, {label: ctx.tree.beforeLabel}); }
 | |
|           ɵɵembeddedViewEnd();
 | |
|         }
 | |
|         ɵɵcontainerRefreshEnd();
 | |
|         ɵɵcontainerRefreshStart(1);
 | |
|         {
 | |
|           for (let subTree of ctx.tree.subTrees || []) {
 | |
|             const rf0 = ɵɵembeddedViewStart(0, 3, 0);
 | |
|             { showTree(rf0, {tree: subTree}); }
 | |
|             ɵɵembeddedViewEnd();
 | |
|           }
 | |
|         }
 | |
|         ɵɵcontainerRefreshEnd();
 | |
|         ɵɵcontainerRefreshStart(2);
 | |
|         {
 | |
|           const rf0 = ɵɵembeddedViewStart(0, 1, 0);
 | |
|           { showLabel(rf0, {label: ctx.tree.afterLabel}); }
 | |
|           ɵɵembeddedViewEnd();
 | |
|         }
 | |
|         ɵɵcontainerRefreshEnd();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     class ChildComponent {
 | |
|       // TODO(issue/24571): remove '!'.
 | |
|       beforeTree !: Tree;
 | |
|       // TODO(issue/24571): remove '!'.
 | |
|       afterTree !: Tree;
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         selectors: [['child']],
 | |
|         type: ChildComponent,
 | |
|         consts: 3,
 | |
|         vars: 0,
 | |
|         template: function ChildComponentTemplate(
 | |
|             rf: RenderFlags, ctx: {beforeTree: Tree, afterTree: Tree}) {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵprojectionDef();
 | |
|             ɵɵcontainer(0);
 | |
|             ɵɵprojection(1);
 | |
|             ɵɵcontainer(2);
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|             ɵɵcontainerRefreshStart(0);
 | |
|             {
 | |
|               const rf0 = ɵɵembeddedViewStart(0, 3, 0);
 | |
|               { showTree(rf0, {tree: ctx.beforeTree}); }
 | |
|               ɵɵembeddedViewEnd();
 | |
|             }
 | |
|             ɵɵcontainerRefreshEnd();
 | |
|             ɵɵcontainerRefreshStart(2);
 | |
|             {
 | |
|               const rf0 = ɵɵembeddedViewStart(0, 3, 0);
 | |
|               { showTree(rf0, {tree: ctx.afterTree}); }
 | |
|               ɵɵembeddedViewEnd();
 | |
|             }
 | |
|             ɵɵcontainerRefreshEnd();
 | |
|           }
 | |
|         },
 | |
|         factory: () => new ChildComponent,
 | |
|         inputs: {beforeTree: 'beforeTree', afterTree: 'afterTree'}
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     function parentTemplate(rf: RenderFlags, ctx: ParentCtx) {
 | |
|       if (rf & RenderFlags.Create) {
 | |
|         ɵɵelementStart(0, 'child');
 | |
|         { ɵɵcontainer(1); }
 | |
|         ɵɵelementEnd();
 | |
|       }
 | |
|       if (rf & RenderFlags.Update) {
 | |
|         ɵɵelementProperty(0, 'beforeTree', ɵɵbind(ctx.beforeTree));
 | |
|         ɵɵelementProperty(0, 'afterTree', ɵɵbind(ctx.afterTree));
 | |
|         ɵɵcontainerRefreshStart(1);
 | |
|         {
 | |
|           const rf0 = ɵɵembeddedViewStart(0, 3, 0);
 | |
|           { showTree(rf0, {tree: ctx.projectedTree}); }
 | |
|           ɵɵembeddedViewEnd();
 | |
|         }
 | |
|         ɵɵcontainerRefreshEnd();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     it('should work with a tree', () => {
 | |
| 
 | |
|       const ctx: ParentCtx = {
 | |
|         beforeTree: {subTrees: [{beforeLabel: 'a'}]},
 | |
|         projectedTree: {beforeLabel: 'p'},
 | |
|         afterTree: {afterLabel: 'z'}
 | |
|       };
 | |
|       const defs = [ChildComponent];
 | |
|       expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>apz</child>');
 | |
|       ctx.projectedTree = {subTrees: [{}, {}, {subTrees: [{}, {}]}, {}]};
 | |
|       ctx.beforeTree.subTrees !.push({afterLabel: 'b'});
 | |
|       expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abz</child>');
 | |
|       ctx.projectedTree.subTrees ![1].afterLabel = 'h';
 | |
|       expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abhz</child>');
 | |
|       ctx.beforeTree.subTrees !.push({beforeLabel: 'c'});
 | |
|       expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abchz</child>');
 | |
| 
 | |
|       // To check the context easily:
 | |
|       // console.log(JSON.stringify(ctx));
 | |
|     });
 | |
| 
 | |
|   });
 | |
| 
 | |
| });
 | |
| 
 | |
| 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');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|     const rendererFactory = new ProxyRenderer3Factory();
 | |
|     new ComponentFixture(StyledComp, {rendererFactory});
 | |
|     expect(rendererFactory.lastCapturedType !.styles).toEqual(['div { color: red; }']);
 | |
|     expect(rendererFactory.lastCapturedType !.encapsulation).toEqual(100);
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('component animations', () => {
 | |
|   it('should pass in the component styles directly into the underlying renderer', () => {
 | |
|     const animA = {name: 'a'};
 | |
|     const animB = {name: 'b'};
 | |
| 
 | |
|     class AnimComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: AnimComp,
 | |
|         consts: 0,
 | |
|         vars: 0,
 | |
|         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);
 | |
|   });
 | |
| 
 | |
|   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([]);
 | |
|   });
 | |
| 
 | |
|   it('should allow [@trigger] bindings to be picked up by the underlying renderer', () => {
 | |
|     class AnimComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: AnimComp,
 | |
|         consts: 1,
 | |
|         vars: 1,
 | |
|         selectors: [['foo']],
 | |
|         factory: () => new AnimComp(),
 | |
|         template: (rf: RenderFlags, ctx: AnimComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'div', [AttributeMarker.Bindings, '@fooAnimation']);
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|             ɵɵelementAttribute(0, '@fooAnimation', ɵɵbind(ctx.animationValue));
 | |
|           }
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       animationValue = '123';
 | |
|     }
 | |
| 
 | |
|     const rendererFactory = new MockRendererFactory(['setAttribute']);
 | |
|     const fixture = new ComponentFixture(AnimComp, {rendererFactory});
 | |
| 
 | |
|     const renderer = rendererFactory.lastRenderer !;
 | |
|     fixture.component.animationValue = '456';
 | |
|     fixture.update();
 | |
| 
 | |
|     const spy = renderer.spies['setAttribute'];
 | |
|     const [elm, attr, value] = spy.calls.mostRecent().args;
 | |
| 
 | |
|     expect(attr).toEqual('@fooAnimation');
 | |
|     expect(value).toEqual('456');
 | |
|   });
 | |
| 
 | |
|   it('should allow creation-level [@trigger] properties to be picked up by the underlying renderer',
 | |
|      () => {
 | |
|        class AnimComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: AnimComp,
 | |
|            consts: 1,
 | |
|            vars: 1,
 | |
|            selectors: [['foo']],
 | |
|            factory: () => new AnimComp(),
 | |
|            template: (rf: RenderFlags, ctx: AnimComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelement(0, 'div', ['@fooAnimation', '']);
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        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);
 | |
|           }
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       exp = 'go';
 | |
|     }
 | |
| 
 | |
|     class ParentComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: ParentComp,
 | |
|         consts: 1,
 | |
|         vars: 1,
 | |
|         selectors: [['foo']],
 | |
|         factory: () => new ParentComp(),
 | |
|         template: (rf: RenderFlags, ctx: ParentComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'child-comp-with-anim');
 | |
|           }
 | |
|         },
 | |
|         directives: [ChildCompWithAnim]
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const rendererFactory = new MockRendererFactory(['setProperty']);
 | |
|     const fixture = new ComponentFixture(ParentComp, {rendererFactory});
 | |
| 
 | |
|     const renderer = rendererFactory.lastRenderer !;
 | |
|     fixture.update();
 | |
| 
 | |
|     const spy = renderer.spies['setProperty'];
 | |
|     const [elm, attr, value] = spy.calls.mostRecent().args;
 | |
|     expect(attr).toEqual('@fooAnim');
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('element discovery', () => {
 | |
|   it('should only monkey-patch immediate child nodes in a component', () => {
 | |
|     class StructuredComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: StructuredComp,
 | |
|         selectors: [['structured-comp']],
 | |
|         factory: () => new StructuredComp(),
 | |
|         consts: 2,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelementStart(0, 'div');
 | |
|             ɵɵelementStart(1, 'p');
 | |
|             ɵɵelementEnd();
 | |
|             ɵɵelementEnd();
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(StructuredComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const host = fixture.hostElement;
 | |
|     const parent = host.querySelector('div') as any;
 | |
|     const child = host.querySelector('p') as any;
 | |
| 
 | |
|     expect(parent[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|     expect(child[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
 | |
|   });
 | |
| 
 | |
|   it('should only monkey-patch immediate child nodes in a sub component', () => {
 | |
|     class ChildComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: ChildComp,
 | |
|         selectors: [['child-comp']],
 | |
|         factory: () => new ChildComp(),
 | |
|         consts: 3,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: ChildComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'div');
 | |
|             ɵɵelement(1, 'div');
 | |
|             ɵɵelement(2, 'div');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     class ParentComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: ParentComp,
 | |
|         selectors: [['parent-comp']],
 | |
|         directives: [ChildComp],
 | |
|         factory: () => new ParentComp(),
 | |
|         consts: 2,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: ParentComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelementStart(0, 'section');
 | |
|             ɵɵelementStart(1, 'child-comp');
 | |
|             ɵɵelementEnd();
 | |
|             ɵɵelementEnd();
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(ParentComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const host = fixture.hostElement;
 | |
|     const child = host.querySelector('child-comp') as any;
 | |
|     expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|     const [kid1, kid2, kid3] = Array.from(host.querySelectorAll('child-comp > *'));
 | |
|     expect(kid1[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|     expect(kid2[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|     expect(kid3[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|   });
 | |
| 
 | |
|   it('should only monkey-patch immediate child nodes in an embedded template container', () => {
 | |
|     class StructuredComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: StructuredComp,
 | |
|         selectors: [['structured-comp']],
 | |
|         directives: [NgIf],
 | |
|         factory: () => new StructuredComp(),
 | |
|         consts: 2,
 | |
|         vars: 1,
 | |
|         template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelementStart(0, 'section');
 | |
|             ɵɵtemplate(1, (rf, ctx) => {
 | |
|               if (rf & RenderFlags.Create) {
 | |
|                 ɵɵelementStart(0, 'div');
 | |
|                 ɵɵelement(1, 'p');
 | |
|                 ɵɵelementEnd();
 | |
|                 ɵɵelement(2, 'div');
 | |
|               }
 | |
|             }, 3, 0, 'ng-template', ['ngIf', '']);
 | |
|             ɵɵelementEnd();
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|             ɵɵelementProperty(1, 'ngIf', true);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(StructuredComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const host = fixture.hostElement;
 | |
|     const [section, div1, p, div2] = Array.from(host.querySelectorAll('section, div, p'));
 | |
| 
 | |
|     expect(section.nodeName.toLowerCase()).toBe('section');
 | |
|     expect(section[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|     expect(div1.nodeName.toLowerCase()).toBe('div');
 | |
|     expect(div1[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|     expect(p.nodeName.toLowerCase()).toBe('p');
 | |
|     expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
 | |
| 
 | |
|     expect(div2.nodeName.toLowerCase()).toBe('div');
 | |
|     expect(div2[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|   });
 | |
| 
 | |
|   it('should return a context object from a given dom node', () => {
 | |
|     class StructuredComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: StructuredComp,
 | |
|         selectors: [['structured-comp']],
 | |
|         directives: [NgIf],
 | |
|         factory: () => new StructuredComp(),
 | |
|         consts: 2,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'section');
 | |
|             ɵɵelement(1, 'div');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(StructuredComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const section = fixture.hostElement.querySelector('section') !;
 | |
|     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);
 | |
| 
 | |
|     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);
 | |
| 
 | |
|     expect(divLView).toBe(sectionLView);
 | |
|   });
 | |
| 
 | |
|   it('should cache the element context on a element was pre-emptively monkey-patched', () => {
 | |
|     class StructuredComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: StructuredComp,
 | |
|         selectors: [['structured-comp']],
 | |
|         factory: () => new StructuredComp(),
 | |
|         consts: 1,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'section');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(StructuredComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const section = fixture.hostElement.querySelector('section') !as any;
 | |
|     const result1 = section[MONKEY_PATCH_KEY_NAME];
 | |
|     expect(Array.isArray(result1)).toBeTruthy();
 | |
| 
 | |
|     const context = getLContext(section) !;
 | |
|     const result2 = section[MONKEY_PATCH_KEY_NAME];
 | |
|     expect(Array.isArray(result2)).toBeFalsy();
 | |
| 
 | |
|     expect(result2).toBe(context);
 | |
|     expect(result2.lView).toBe(result1);
 | |
|   });
 | |
| 
 | |
|   it('should cache the element context on an intermediate element that isn\'t pre-emptively monkey-patched',
 | |
|      () => {
 | |
|        class StructuredComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: StructuredComp,
 | |
|            selectors: [['structured-comp']],
 | |
|            factory: () => new StructuredComp(),
 | |
|            consts: 2,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelementStart(0, 'section');
 | |
|                ɵɵelement(1, 'p');
 | |
|                ɵɵelementEnd();
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(StructuredComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const section = fixture.hostElement.querySelector('section') !as any;
 | |
|        expect(section[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|        const p = fixture.hostElement.querySelector('p') !as any;
 | |
|        expect(p[MONKEY_PATCH_KEY_NAME]).toBeFalsy();
 | |
| 
 | |
|        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');
 | |
|                ɵɵstyling(['class-foo']);
 | |
|                ɵɵelementEnd();
 | |
|              }
 | |
|              if (rf & RenderFlags.Update) {
 | |
|                ɵɵselect(0);
 | |
|                ɵɵstylingApply();
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        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>
 | |
|            </projection-comp>
 | |
|          </section>
 | |
|        */
 | |
|        class ProjectorComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: ProjectorComp,
 | |
|            selectors: [['projector-comp']],
 | |
|            factory: () => new ProjectorComp(),
 | |
|            consts: 4,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: ProjectorComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵprojectionDef();
 | |
|                ɵɵtext(0, 'welcome');
 | |
|                ɵɵelementStart(1, 'header');
 | |
|                ɵɵelementStart(2, 'h1');
 | |
|                ɵɵprojection(3);
 | |
|                ɵɵelementEnd();
 | |
|                ɵɵelementEnd();
 | |
|              }
 | |
|              if (rf & RenderFlags.Update) {
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class ParentComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: ParentComp,
 | |
|            selectors: [['parent-comp']],
 | |
|            directives: [ProjectorComp],
 | |
|            factory: () => new ParentComp(),
 | |
|            consts: 5,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: ParentComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelementStart(0, 'section');
 | |
|                ɵɵelementStart(1, 'projector-comp');
 | |
|                ɵɵelementStart(2, 'p');
 | |
|                ɵɵtext(3, 'this content is projected');
 | |
|                ɵɵelementEnd();
 | |
|                ɵɵtext(4, 'this content is projected also');
 | |
|                ɵɵelementEnd();
 | |
|                ɵɵelementEnd();
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(ParentComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const host = fixture.hostElement;
 | |
|        const textNode = host.firstChild as any;
 | |
|        const section = host.querySelector('section') !as any;
 | |
|        const projectorComp = host.querySelector('projector-comp') !as any;
 | |
|        const header = host.querySelector('header') !as any;
 | |
|        const h1 = host.querySelector('h1') !as any;
 | |
|        const p = host.querySelector('p') !as any;
 | |
|        const pText = p.firstChild as any;
 | |
|        const projectedTextNode = p.nextSibling;
 | |
| 
 | |
|        expect(projectorComp.children).toContain(header);
 | |
|        expect(h1.children).toContain(p);
 | |
| 
 | |
|        expect(textNode[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|        expect(section[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
|        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');
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(StructuredComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const section = fixture.hostElement.querySelector('section') !as any;
 | |
|        const manuallyCreatedElement = document.createElement('div');
 | |
|        section.appendChild(manuallyCreatedElement);
 | |
| 
 | |
|        const context = getLContext(manuallyCreatedElement);
 | |
|        expect(context).toBeFalsy();
 | |
|      });
 | |
| 
 | |
|   it('should by default monkey-patch the bootstrap component with context details', () => {
 | |
|     class StructuredComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: StructuredComp,
 | |
|         selectors: [['structured-comp']],
 | |
|         factory: () => new StructuredComp(),
 | |
|         consts: 0,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: StructuredComp) => {}
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const fixture = new ComponentFixture(StructuredComp);
 | |
|     fixture.update();
 | |
| 
 | |
|     const hostElm = fixture.hostElement;
 | |
|     const component = fixture.component;
 | |
| 
 | |
|     const componentLView = (component as any)[MONKEY_PATCH_KEY_NAME];
 | |
|     expect(Array.isArray(componentLView)).toBeTruthy();
 | |
| 
 | |
|     const hostLView = (hostElm as any)[MONKEY_PATCH_KEY_NAME];
 | |
|     expect(hostLView).toBe(componentLView);
 | |
| 
 | |
|     const context1 = getLContext(hostElm) !;
 | |
|     expect(context1.lView).toBe(hostLView);
 | |
|     expect(context1.native).toEqual(hostElm);
 | |
| 
 | |
|     const context2 = getLContext(component) !;
 | |
|     expect(context2).toBe(context1);
 | |
|     expect(context2.lView).toBe(hostLView);
 | |
|     expect(context2.native).toEqual(hostElm);
 | |
|   });
 | |
| 
 | |
|   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()
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class MyDir2 {
 | |
|          static ngDirectiveDef = ɵɵdefineDirective({
 | |
|            type: MyDir2,
 | |
|            selectors: [['', 'my-dir-2', '']],
 | |
|            factory: () => myDir2Instance = new MyDir2()
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class MyDir3 {
 | |
|          static ngDirectiveDef = ɵɵdefineDirective({
 | |
|            type: MyDir3,
 | |
|            selectors: [['', 'my-dir-3', '']],
 | |
|            factory: () => myDir3Instance = new MyDir2()
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class StructuredComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: StructuredComp,
 | |
|            selectors: [['structured-comp']],
 | |
|            directives: [MyDir1, MyDir2, MyDir3],
 | |
|            factory: () => new StructuredComp(),
 | |
|            consts: 2,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: StructuredComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelement(0, 'div', ['my-dir-1', '', 'my-dir-2', '']);
 | |
|                ɵɵelement(1, 'div', ['my-dir-3']);
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(StructuredComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const hostElm = fixture.hostElement;
 | |
|        const div1 = hostElm.querySelector('div:first-child') !as any;
 | |
|        const div2 = hostElm.querySelector('div:last-child') !as any;
 | |
|        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()
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class MyDir2 {
 | |
|          static ngDirectiveDef = ɵɵdefineDirective({
 | |
|            type: MyDir2,
 | |
|            selectors: [['', 'my-dir-2', '']],
 | |
|            factory: () => myDir2Instance = new MyDir2()
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class ChildComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: ChildComp,
 | |
|            selectors: [['child-comp']],
 | |
|            factory: () => childComponentInstance = new ChildComp(),
 | |
|            consts: 1,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: ChildComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelement(0, 'div');
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class ParentComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: ParentComp,
 | |
|            selectors: [['parent-comp']],
 | |
|            directives: [ChildComp, MyDir1, MyDir2],
 | |
|            factory: () => new ParentComp(),
 | |
|            consts: 1,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: ParentComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelement(0, 'child-comp', ['my-dir-1', '', 'my-dir-2', '']);
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(ParentComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const childCompHostElm = fixture.hostElement.querySelector('child-comp') !as any;
 | |
| 
 | |
|        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');
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        class ParentComp {
 | |
|          static ngComponentDef = ɵɵdefineComponent({
 | |
|            type: ParentComp,
 | |
|            selectors: [['parent-comp']],
 | |
|            directives: [ChildComp],
 | |
|            factory: () => new ParentComp(),
 | |
|            consts: 2,
 | |
|            vars: 0,
 | |
|            template: (rf: RenderFlags, ctx: ParentComp) => {
 | |
|              if (rf & RenderFlags.Create) {
 | |
|                ɵɵelementStart(0, 'section');
 | |
|                ɵɵelementStart(1, 'child-comp');
 | |
|                ɵɵelementEnd();
 | |
|                ɵɵelementEnd();
 | |
|              }
 | |
|            }
 | |
|          });
 | |
|        }
 | |
| 
 | |
|        const fixture = new ComponentFixture(ParentComp);
 | |
|        fixture.update();
 | |
| 
 | |
|        const host = fixture.hostElement;
 | |
|        const child = host.querySelector('child-comp') as any;
 | |
|        expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|        const context = getLContext(child) !;
 | |
|        expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy();
 | |
| 
 | |
|        const componentData = context.lView[context.nodeIndex];
 | |
|        const component = componentData[CONTEXT];
 | |
|        expect(component instanceof ChildComp).toBeTruthy();
 | |
|        expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lView);
 | |
| 
 | |
|        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);
 | |
|      });
 | |
| });
 | |
| 
 | |
| 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');
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|             ɵɵelementProperty(0, 'href', ɵɵbind(ctx.href), ɵɵsanitizeUrl);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       private href = '';
 | |
| 
 | |
|       updateLink(href: any) { this.href = href; }
 | |
|     }
 | |
| 
 | |
|     const sanitizer = new LocalSanitizer((value) => { return 'http://bar'; });
 | |
| 
 | |
|     const fixture = new ComponentFixture(SanitizationComp, {sanitizer});
 | |
|     fixture.component.updateLink('http://foo');
 | |
|     fixture.update();
 | |
| 
 | |
|     const anchor = fixture.hostElement.querySelector('a') !;
 | |
|     expect(anchor.getAttribute('href')).toEqual('http://bar');
 | |
| 
 | |
|     fixture.component.updateLink(sanitizer.bypassSecurityTrustUrl('http://foo'));
 | |
|     fixture.update();
 | |
| 
 | |
|     expect(anchor.getAttribute('href')).toEqual('http://foo');
 | |
|   });
 | |
| 
 | |
|   it('should sanitize HostBindings data using provided sanitization interface', () => {
 | |
|     let hostBindingDir: UnsafeUrlHostBindingDir;
 | |
|     class UnsafeUrlHostBindingDir {
 | |
|       // @HostBinding()
 | |
|       cite: any = 'http://cite-dir-value';
 | |
| 
 | |
|       static ngDirectiveDef = ɵɵdefineDirective({
 | |
|         type: UnsafeUrlHostBindingDir,
 | |
|         selectors: [['', 'unsafeUrlHostBindingDir', '']],
 | |
|         factory: () => hostBindingDir = new UnsafeUrlHostBindingDir(),
 | |
|         hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵallocHostVars(1);
 | |
|           }
 | |
|           if (rf & RenderFlags.Update) {
 | |
|             ɵɵelementProperty(elementIndex, 'cite', ɵɵbind(ctx.cite), ɵɵsanitizeUrl, true);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     class SimpleComp {
 | |
|       static ngComponentDef = ɵɵdefineComponent({
 | |
|         type: SimpleComp,
 | |
|         selectors: [['sanitize-this']],
 | |
|         factory: () => new SimpleComp(),
 | |
|         consts: 1,
 | |
|         vars: 0,
 | |
|         template: (rf: RenderFlags, ctx: SimpleComp) => {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵelement(0, 'blockquote', ['unsafeUrlHostBindingDir', '']);
 | |
|           }
 | |
|         },
 | |
|         directives: [UnsafeUrlHostBindingDir]
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     const sanitizer = new LocalSanitizer((value) => 'http://bar');
 | |
| 
 | |
|     const fixture = new ComponentFixture(SimpleComp, {sanitizer});
 | |
|     hostBindingDir !.cite = 'http://foo';
 | |
|     fixture.update();
 | |
| 
 | |
|     const anchor = fixture.hostElement.querySelector('blockquote') !;
 | |
|     expect(anchor.getAttribute('cite')).toEqual('http://bar');
 | |
| 
 | |
|     hostBindingDir !.cite = sanitizer.bypassSecurityTrustUrl('http://foo');
 | |
|     fixture.update();
 | |
| 
 | |
|     expect(anchor.getAttribute('cite')).toEqual('http://foo');
 | |
|   });
 | |
| });
 | |
| 
 | |
| 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); }
 | |
| }
 | |
| 
 | |
| class ProxyRenderer3Factory implements RendererFactory3 {
 | |
|   lastCapturedType: RendererType2|null = null;
 | |
| 
 | |
|   createRenderer(hostElement: RElement|null, rendererType: RendererType2|null): Renderer3 {
 | |
|     this.lastCapturedType = rendererType;
 | |
|     return domRendererFactory3.createRenderer(hostElement, rendererType);
 | |
|   }
 | |
| }
 |