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.Component, -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();
 | 
						|
      }
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
});
 |