/** * @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)', () => { /**
*/ 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'); /** *
*
*
*/ 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(); } }); }); });