When debugging `LView`s it is easy to get lost since all of them have the same name. This change does three things: 1. It makes `TView` have an explicit type: - `Host`: for the top level `TView` for bootstrap - `Component`: for the `TView` which represents components template - `Embedded`: for the `TView` which represents an embedded template 2. It changes the name of `LView` to `LHostView`, `LComponentView`, and `LEmbeddedView` depending on the `TView` type. 3. For `LComponentView` and `LEmbeddedView` we also append the name of of the `context` constructor. The result is that we have `LView`s which are name as: `LComponentView_MyComponent` and `LEmbeddedView_NgIfContext`. The above changes will make it easier to understand the structure of the application when debugging. NOTE: All of these are behind `ngDevMode` and will get removed in production application. PR Close #33449
		
			
				
	
	
		
			246 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			8.9 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 {InjectFlags, Optional, Renderer2, Self} from '@angular/core';
 | |
| import {createLView, createTView, getOrCreateTNode} from '@angular/core/src/render3/instructions/shared';
 | |
| import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
 | |
| 
 | |
| import {ɵɵdefineComponent} from '../../src/render3/definition';
 | |
| import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
 | |
| import {ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext} from '../../src/render3/index';
 | |
| import {TNODE} from '../../src/render3/interfaces/injector';
 | |
| import {TNodeType} from '../../src/render3/interfaces/node';
 | |
| import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
 | |
| import {LViewFlags, TVIEW, TViewType} from '../../src/render3/interfaces/view';
 | |
| import {enterView, leaveViewProcessExit} from '../../src/render3/state';
 | |
| 
 | |
| import {getRendererFactory2} from './imported_renderer2';
 | |
| import {ComponentFixture, createComponent, createDirective} from './render_util';
 | |
| 
 | |
| describe('di', () => {
 | |
|   describe('directive injection', () => {
 | |
| 
 | |
|     class DirB {
 | |
|       value = 'DirB';
 | |
| 
 | |
|       static ɵfac = () => new DirB();
 | |
|       static ɵdir =
 | |
|           ɵɵdefineDirective({selectors: [['', 'dirB', '']], type: DirB, inputs: {value: 'value'}});
 | |
|     }
 | |
| 
 | |
|     describe('flags', () => {
 | |
| 
 | |
|       class DirB {
 | |
|         // TODO(issue/24571): remove '!'.
 | |
|         value !: string;
 | |
| 
 | |
|         static ɵfac = () => new DirB();
 | |
|         static ɵdir =
 | |
|             ɵɵdefineDirective({type: DirB, selectors: [['', 'dirB', '']], inputs: {value: 'dirB'}});
 | |
|       }
 | |
| 
 | |
|       describe('Optional', () => {
 | |
|         let dirA: DirA|null = null;
 | |
| 
 | |
|         class DirA {
 | |
|           constructor(@Optional() public dirB: DirB|null) {}
 | |
| 
 | |
|           static ɵfac =
 | |
|               () => {
 | |
|                 dirA = new DirA(ɵɵdirectiveInject(DirB, InjectFlags.Optional));
 | |
|                 return dirA;
 | |
|               }
 | |
| 
 | |
|           static ɵdir = ɵɵdefineDirective({type: DirA, selectors: [['', 'dirA', '']]});
 | |
|         }
 | |
| 
 | |
|         beforeEach(() => dirA = null);
 | |
| 
 | |
|         it('should not throw if dependency is @Optional (limp mode)', () => {
 | |
| 
 | |
|           /** <div dirA></div> */
 | |
|           const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
 | |
|             if (rf & RenderFlags.Create) {
 | |
|               ɵɵelement(0, 'div', 0);
 | |
|             }
 | |
|           }, 1, 0, [DirA, DirB], [], undefined, [], [], undefined, [['dirA', '']]);
 | |
| 
 | |
|           expect(() => { new ComponentFixture(App); }).not.toThrow();
 | |
|           expect(dirA !.dirB).toEqual(null);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('should check only the current node with @Self even with false positive', () => {
 | |
|         let dirA: DirA;
 | |
| 
 | |
|         class DirA {
 | |
|           constructor(@Self() public dirB: DirB) {}
 | |
| 
 | |
|           static ɵfac = () => dirA = new DirA(ɵɵdirectiveInject(DirB, InjectFlags.Self));
 | |
|           static ɵdir = ɵɵdefineDirective({type: DirA, selectors: [['', 'dirA', '']]});
 | |
|         }
 | |
| 
 | |
|         const DirC = createDirective('dirC');
 | |
| 
 | |
|         /**
 | |
|          * <div dirB>
 | |
|          *   <div dirA dirC></div>
 | |
|          * </div>
 | |
|          */
 | |
|         const App = createComponent(
 | |
|             'app',
 | |
|             function(rf: RenderFlags, ctx: any) {
 | |
|               if (rf & RenderFlags.Create) {
 | |
|                 ɵɵelementStart(0, 'div', 0);
 | |
|                 ɵɵelement(1, 'div', 1);
 | |
|                 ɵɵelementEnd();
 | |
|               }
 | |
|             },
 | |
|             2, 0, [DirA, DirB, DirC], [], undefined, [], [], undefined,
 | |
|             [['dirB', ''], ['dirA', '', 'dirC', '']]);
 | |
| 
 | |
|         expect(() => {
 | |
|           (DirA as any)['__NG_ELEMENT_ID__'] = 1;
 | |
|           (DirC as any)['__NG_ELEMENT_ID__'] = 257;
 | |
|           new ComponentFixture(App);
 | |
|         }).toThrowError(/NodeInjector: NOT_FOUND \[DirB]/);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('Renderer2', () => {
 | |
|     class MyComp {
 | |
|       constructor(public renderer: Renderer2) {}
 | |
| 
 | |
|       static ɵfac = () => new MyComp(ɵɵdirectiveInject(Renderer2 as any));
 | |
|       static ɵcmp = ɵɵdefineComponent({
 | |
|         type: MyComp,
 | |
|         selectors: [['my-comp']],
 | |
|         decls: 1,
 | |
|         vars: 0,
 | |
|         template: function(rf: RenderFlags, ctx: MyComp) {
 | |
|           if (rf & RenderFlags.Create) {
 | |
|             ɵɵtext(0, 'Foo');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     it('should inject the Renderer2 used by the application', () => {
 | |
|       const rendererFactory = getRendererFactory2(document);
 | |
|       const fixture = new ComponentFixture(MyComp, {rendererFactory: rendererFactory});
 | |
|       expect(isProceduralRenderer(fixture.component.renderer)).toBeTruthy();
 | |
|     });
 | |
| 
 | |
|     it('should throw when injecting Renderer2 but the application is using Renderer3',
 | |
|        () => { expect(() => new ComponentFixture(MyComp)).toThrow(); });
 | |
|   });
 | |
| 
 | |
|   describe('ɵɵinject', () => {
 | |
|     describe('bloom filter', () => {
 | |
|       let mockTView: any;
 | |
|       beforeEach(() => {
 | |
|         mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstCreatePass: true};
 | |
|       });
 | |
| 
 | |
|       function bloomState() { return mockTView.data.slice(0, TNODE).reverse(); }
 | |
| 
 | |
|       class Dir0 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 0;
 | |
|       }
 | |
|       class Dir1 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 1;
 | |
|       }
 | |
|       class Dir33 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 33;
 | |
|       }
 | |
|       class Dir66 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 66;
 | |
|       }
 | |
|       class Dir99 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 99;
 | |
|       }
 | |
|       class Dir132 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 132;
 | |
|       }
 | |
|       class Dir165 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 165;
 | |
|       }
 | |
|       class Dir198 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 198;
 | |
|       }
 | |
|       class Dir231 {
 | |
|         /** @internal */ static __NG_ELEMENT_ID__ = 231;
 | |
|       }
 | |
| 
 | |
|       it('should add values', () => {
 | |
|         bloomAdd(0, mockTView, Dir0);
 | |
|         expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]);
 | |
|         bloomAdd(0, mockTView, Dir33);
 | |
|         expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir66);
 | |
|         expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir99);
 | |
|         expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir132);
 | |
|         expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir165);
 | |
|         expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir198);
 | |
|         expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]);
 | |
|         bloomAdd(0, mockTView, Dir231);
 | |
|         expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]);
 | |
|       });
 | |
| 
 | |
|       it('should query values', () => {
 | |
|         bloomAdd(0, mockTView, Dir0);
 | |
|         bloomAdd(0, mockTView, Dir33);
 | |
|         bloomAdd(0, mockTView, Dir66);
 | |
|         bloomAdd(0, mockTView, Dir99);
 | |
|         bloomAdd(0, mockTView, Dir132);
 | |
|         bloomAdd(0, mockTView, Dir165);
 | |
|         bloomAdd(0, mockTView, Dir198);
 | |
|         bloomAdd(0, mockTView, Dir231);
 | |
| 
 | |
|         expect(bloomHasToken(bloomHash(Dir0) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir1) as number, 0, mockTView.data)).toEqual(false);
 | |
|         expect(bloomHasToken(bloomHash(Dir33) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir66) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir99) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir132) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir165) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir198) as number, 0, mockTView.data)).toEqual(true);
 | |
|         expect(bloomHasToken(bloomHash(Dir231) as number, 0, mockTView.data)).toEqual(true);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('getOrCreateNodeInjector', () => {
 | |
|     it('should handle initial undefined state', () => {
 | |
|       const contentView = createLView(
 | |
|           null, createTView(TViewType.Embedded, -1, null, 1, 0, null, null, null, null, null), {},
 | |
|           LViewFlags.CheckAlways, null, null, {} as any, {} as any);
 | |
|       enterView(contentView, null);
 | |
|       try {
 | |
|         const parentTNode =
 | |
|             getOrCreateTNode(contentView[TVIEW], null, 0, TNodeType.Element, null, null);
 | |
|         // Simulate the situation where the previous parent is not initialized.
 | |
|         // This happens on first bootstrap because we don't init existing values
 | |
|         // so that we have smaller HelloWorld.
 | |
|         (parentTNode as{parent: any}).parent = undefined;
 | |
| 
 | |
|         const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView);
 | |
|         expect(injector).not.toEqual(-1);
 | |
|       } finally {
 | |
|         leaveViewProcessExit();
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| 
 | |
| });
 |