/**
 * @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 {Attribute, ChangeDetectorRef, ElementRef, Host, INJECTOR, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, defineInjectable, defineInjector} from '@angular/core';
import {ComponentType, 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 {ProvidersFeature, defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index';

import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLView, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions';
import {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer';
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
import {getNativeByIndex} from '../../src/render3/util';
import {LViewFlags} from '../../src/render3/interfaces/view';
import {enterView, leaveView, getLView} from '../../src/render3/state';
import {ViewRef} from '../../src/render3/view_ref';

import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
import {NgIf} from './common_with_def';
import {TNODE} from '../../src/render3/interfaces/injector';
import {createInjector} from '../../src/di/r3_injector';
import {LContainer, NATIVE} from '../../src/render3/interfaces/container';

describe('di', () => {
  describe('no dependencies', () => {
    it('should create directive with no deps', () => {
      class Directive {
        value: string = 'Created';
        static ngDirectiveDef = defineDirective({
          type: Directive,
          selectors: [['', 'dir', '']],
          factory: () => new Directive,
          exportAs: ['dir']
        });
      }

      /** <div dir #dir="dir"> {{ dir.value }}  </div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['dir', ''], ['dir', 'dir']);
          { text(2); }
          elementEnd();
        }
        if (rf & RenderFlags.Update) {
          const tmp = reference(1) as any;
          textBinding(2, bind(tmp.value));
        }
      }, 3, 1, [Directive]);

      const fixture = new ComponentFixture(App);
      expect(fixture.html).toEqual('<div dir="">Created</div>');
    });
  });

  describe('directive injection', () => {
    let log: string[] = [];

    class DirB {
      value = 'DirB';
      constructor() { log.push(this.value); }

      static ngDirectiveDef = defineDirective({
        selectors: [['', 'dirB', '']],
        type: DirB,
        factory: () => new DirB(),
        inputs: {value: 'value'}
      });
    }

    beforeEach(() => log = []);

    it('should create directive with intra view dependencies', () => {
      class DirA {
        value: string = 'DirA';
        static ngDirectiveDef =
            defineDirective({type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA()});
      }

      class DirC {
        value: string;
        constructor(a: DirA, b: DirB) { this.value = a.value + b.value; }
        static ngDirectiveDef = defineDirective({
          type: DirC,
          selectors: [['', 'dirC', '']],
          factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)),
          exportAs: ['dirC']
        });
      }

      /**
       * <div dirA>
       *  <span dirB dirC #dir="dirC"> {{ dir.value }} </span>
       * </div>
       */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['dirA', '']);
          {
            elementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']);
            { text(3); }
            elementEnd();
          }
          elementEnd();
        }
        if (rf & RenderFlags.Update) {
          const tmp = reference(2) as any;
          textBinding(3, bind(tmp.value));
        }
      }, 4, 1, [DirA, DirB, DirC]);

      const fixture = new ComponentFixture(App);
      expect(fixture.html).toEqual('<div dira=""><span dirb="" dirc="">DirADirB</span></div>');
    });

    it('should instantiate injected directives in dependency order', () => {
      class DirA {
        constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB)),
        });
      }

      /** <div dirA dirB></div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dirA', '', 'dirB', '']);
        }
      }, 1, 0, [DirA, DirB]);

      new ComponentFixture(App);
      expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
    });

    it('should fallback to the module injector', () => {
      class DirA {
        constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB)),
        });
      }

      // `<div dirB></div><div dirA></div>`
      // - dirB is know to the node injectors (it uses the diPublic feature)
      // - then when dirA tries to inject dirB, it will check the node injector first tree
      // - if not found, it will check the module injector tree
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dirB', '']);
          element(1, 'div', ['dirA', '']);
        }
      }, 2, 0, [DirA, DirB]);

      const fakeModuleInjector: any = {
        get: function(token: any) {
          const value = token === DirB ? 'module' : 'fail';
          return {value: value};
        }
      };

      new ComponentFixture(App, {injector: fakeModuleInjector});
      expect(log).toEqual(['DirB', 'DirA (dep: module)']);
    });

    it('should instantiate injected directives before components', () => {
      class Comp {
        constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }

        static ngComponentDef = defineComponent({
          selectors: [['comp']],
          type: Comp,
          consts: 0,
          vars: 0,
          factory: () => new Comp(directiveInject(DirB)),
          template: (ctx: any, fm: boolean) => {}
        });
      }

      /** <comp dirB></comp> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'comp', ['dirB', '']);
        }
      }, 1, 0, [Comp, DirB]);

      new ComponentFixture(App);
      expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
    });

    it('should inject directives in the correct order in a for loop', () => {
      class DirA {
        constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB))
        });
      }

      /**
       * % for(let i = 0; i < 3; i++) {
       *   <div dirA dirB></div>
       * % }
       */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          container(0);
        }
        if (rf & RenderFlags.Update) {
          containerRefreshStart(0);
          {
            for (let i = 0; i < 3; i++) {
              if (embeddedViewStart(0, 1, 0)) {
                element(0, 'div', ['dirA', '', 'dirB', '']);
              }
              embeddedViewEnd();
            }
          }
          containerRefreshEnd();
        }
      }, 1, 0, [DirA, DirB]);

      new ComponentFixture(App);
      expect(log).toEqual(
          ['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
    });

    it('should instantiate directives with multiple out-of-order dependencies', () => {
      class DirA {
        value = 'DirA';
        constructor() { log.push(this.value); }

        static ngDirectiveDef =
            defineDirective({selectors: [['', 'dirA', '']], type: DirA, factory: () => new DirA()});
      }

      class DirB {
        constructor(dirA: DirA, dirC: DirC) {
          log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
        }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirB', '']],
          type: DirB,
          factory: () => new DirB(directiveInject(DirA), directiveInject(DirC))
        });
      }

      class DirC {
        value = 'DirC';
        constructor() { log.push(this.value); }

        static ngDirectiveDef =
            defineDirective({selectors: [['', 'dirC', '']], type: DirC, factory: () => new DirC()});
      }

      /** <div dirA dirB dirC></div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
        }
      }, 1, 0, [DirA, DirB, DirC]);

      new ComponentFixture(App);
      expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
    });

    it('should instantiate in the correct order for complex case', () => {
      class Comp {
        constructor(dir: DirD) { log.push(`Comp (dep: ${dir.value})`); }

        static ngComponentDef = defineComponent({
          selectors: [['comp']],
          type: Comp,
          consts: 0,
          vars: 0,
          factory: () => new Comp(directiveInject(DirD)),
          template: (ctx: any, fm: boolean) => {}
        });
      }

      class DirA {
        value = 'DirA';
        constructor(dir: DirC) { log.push(`DirA (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirC))
        });
      }

      class DirC {
        value = 'DirC';
        constructor(dir: DirB) { log.push(`DirC (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirC', '']],
          type: DirC,
          factory: () => new DirC(directiveInject(DirB))
        });
      }

      class DirD {
        value = 'DirD';
        constructor(dir: DirA) { log.push(`DirD (dep: ${dir.value})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirD', '']],
          type: DirD,
          factory: () => new DirD(directiveInject(DirA))
        });
      }

      /** <comp dirA dirB dirC dirD></comp> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
        }
      }, 1, 0, [Comp, DirA, DirB, DirC, DirD]);

      new ComponentFixture(App);
      expect(log).toEqual(
          ['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
    });

    it('should instantiate in correct order with mixed parent and peer dependencies', () => {
      class DirA {
        constructor(dirB: DirB, app: App) {
          log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
        }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB), directiveInject(App)),
        });
      }

      class App {
        value = 'App';

        static ngComponentDef = defineComponent({
          selectors: [['app']],
          type: App,
          factory: () => new App(),
          consts: 1,
          vars: 0,
          /** <div dirA dirB dirC></div> */
          template: (rf: RenderFlags, ctx: any) => {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
            }
          },
          directives: [DirA, DirB]
        });
      }

      new ComponentFixture(App);
      expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
    });

    it('should not use a parent when peer dep is available', () => {
      let count = 1;

      class DirA {
        constructor(dirB: DirB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB)),
        });
      }

      class DirB {
        count: number;

        constructor() {
          log.push(`DirB`);
          this.count = count++;
        }

        static ngDirectiveDef =
            defineDirective({selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB()});
      }

      /** <div dirA dirB></div> */
      const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dirA', '', 'dirB', '']);
        }
      }, 1, 0, [DirA, DirB]);

      /** <parent dirB></parent> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'parent', ['dirB', '']);
        }
      }, 1, 0, [Parent, DirB]);

      new ComponentFixture(App);
      expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
    });

    describe('dependencies in parent views', () => {

      class DirA {
        injector: Injector;
        constructor(public dirB: DirB, public vcr: ViewContainerRef) {
          this.injector = vcr.injector;
        }

        static ngDirectiveDef = defineDirective({
          type: DirA,
          selectors: [['', 'dirA', '']],
          factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)),
          exportAs: ['dirA']
        });
      }

      /**
       * <div dirA #dir="dirA">
       *    {{ dir.dirB.value }}
       * </div>
       */
      const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
          { text(2); }
          elementEnd();
        }
        if (rf & RenderFlags.Update) {
          const dir = reference(1) as DirA;
          textBinding(2, bind(dir.dirB.value));
        }
      }, 3, 1, [DirA]);

      it('should find dependencies on component hosts', () => {
        /** <comp dirB>/comp> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'comp', ['dirB', '']);
          }
        }, 1, 0, [Comp, DirB]);

        const fixture = new ComponentFixture(App);
        expect(fixture.hostElement.textContent).toEqual(`DirB`);
      });

      it('should find dependencies for directives in embedded views', () => {

        function IfTemplate(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div');
            {
              elementStart(1, 'div', ['dirA', ''], ['dir', 'dirA']);
              { text(3); }
              elementEnd();
            }
            elementEnd();
          }

          if (rf & RenderFlags.Update) {
            const dir = reference(2) as DirA;
            textBinding(3, bind(dir.dirB.value));
          }
        }

        /**
         * <div dirB>
         *    <div *ngIf="showing">
         *       <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
         *    </div>
         * </div>
         */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dirB', '']);
            { template(1, IfTemplate, 4, 1, 'div', [AttributeMarker.SelectOnly, 'ngIf', '']); }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            elementProperty(1, 'ngIf', bind(ctx.showing));
          }
        }, 2, 1, [DirA, DirB, NgIf]);

        const fixture = new ComponentFixture(App);
        fixture.component.showing = true;
        fixture.update();

        expect(fixture.hostElement.textContent).toEqual(`DirB`);
      });

      it('should find dependencies of directives nested deeply in inline views', () => {
        /**
         * <div dirB>
         *     % if (!skipContent) {
         *        % if (!skipContent2) {
         *           <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
         *        % }
         *     % }
         * </div>
         */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dirB', '']);
            { container(1); }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            containerRefreshStart(1);
            {
              if (!ctx.skipContent) {
                let rf1 = embeddedViewStart(0, 1, 0);
                {
                  if (rf1 & RenderFlags.Create) {
                    container(0);
                  }
                  if (rf1 & RenderFlags.Update) {
                    containerRefreshStart(0);
                    {
                      if (!ctx.skipContent2) {
                        let rf2 = embeddedViewStart(0, 3, 1);
                        {
                          if (rf2 & RenderFlags.Create) {
                            elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
                            { text(2); }
                            elementEnd();
                          }
                          if (rf2 & RenderFlags.Update) {
                            const dir = reference(1) as DirA;
                            textBinding(2, bind(dir.dirB.value));
                          }
                        }
                        embeddedViewEnd();
                      }
                    }
                    containerRefreshEnd();
                  }
                }
                embeddedViewEnd();
              }
            }
            containerRefreshEnd();
          }
        }, 2, 0, [DirA, DirB]);

        const fixture = new ComponentFixture(App);
        expect(fixture.hostElement.textContent).toEqual(`DirB`);
      });

      it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
        let structuralDir !: StructuralDir;

        class StructuralDir {
          // @Input()
          tmp !: TemplateRef<any>;

          constructor(public vcr: ViewContainerRef) {}

          create() { this.vcr.createEmbeddedView(this.tmp); }

          static ngDirectiveDef = defineDirective({
            type: StructuralDir,
            selectors: [['', 'structuralDir', '']],
            factory: () => structuralDir =
                         new StructuralDir(directiveInject(ViewContainerRef as any)),
            inputs: {tmp: 'tmp'}
          });
        }

        function FooTemplate(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
            { text(2); }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            const dir = reference(1) as DirA;
            textBinding(2, bind(dir.dirB.value));
          }
        }

        /**
         * <div dirB value="declaration">
         *   <ng-template #foo>
         *       <div dirA dir="dirA"> {{ dir.dirB.value }} </div>
         *   </ng-template>
         * </div>
         *
         * <div dirB value="insertion">
         *   <div structuralDir [tmp]="foo"></div>
         *   // insertion point
         * </div>
         */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dirB', '', 'value', 'declaration']);
            {
              template(
                  1, FooTemplate, 3, 1, 'ng-template', null, ['foo', ''], templateRefExtractor);
            }
            elementEnd();
            elementStart(3, 'div', ['dirB', '', 'value', 'insertion']);
            { element(4, 'div', ['structuralDir', '']); }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            const foo = reference(2) as any;
            elementProperty(4, 'tmp', bind(foo));
          }
        }, 5, 1, [DirA, DirB, StructuralDir]);

        const fixture = new ComponentFixture(App);
        structuralDir.create();
        fixture.update();
        expect(fixture.hostElement.textContent).toEqual(`declaration`);
      });

      it('should create injectors on second template pass', () => {
        /**
         * <comp dirB></comp>
         * <comp dirB></comp>
         */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'comp', ['dirB', '']);
            element(1, 'comp', ['dirB', '']);
          }
        }, 2, 0, [Comp, DirB]);

        const fixture = new ComponentFixture(App);
        expect(fixture.hostElement.textContent).toEqual(`DirBDirB`);
      });

      it('should create injectors and host bindings in same view', () => {
        let hostBindingDir !: HostBindingDir;

        class HostBindingDir {
          // @HostBinding('id')
          id = 'foo';

          static ngDirectiveDef = defineDirective({
            type: HostBindingDir,
            selectors: [['', 'hostBindingDir', '']],
            factory: () => hostBindingDir = new HostBindingDir(),
            hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
              if (rf & RenderFlags.Create) {
                allocHostVars(1);
              }
              if (rf & RenderFlags.Update) {
                elementProperty(elementIndex, 'id', bind(ctx.id));
              }
            }
          });
        }

        let dir !: DirA;
        /**
         * <div dirB hostBindingDir>
         *     <p dirA #dir="dirA">
         *         {{ dir.dirB.value }}
         *     </p>
         * </div>
         */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', [
              'dirB',
              '',
              'hostBindingDir',
              '',
            ]);
            {
              elementStart(1, 'p', ['dirA', ''], ['dir', 'dirA']);
              { text(3); }
              elementEnd();
            }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            dir = reference(2) as DirA;
            textBinding(3, bind(dir.dirB.value));
          }
        }, 4, 1, [HostBindingDir, DirA, DirB]);

        const fixture = new ComponentFixture(App);
        expect(fixture.hostElement.textContent).toEqual(`DirB`);
        const hostDirEl = fixture.hostElement.querySelector('div') as HTMLElement;
        expect(hostDirEl.id).toEqual('foo');
        // The injector should not be overwritten by host bindings
        expect(dir.vcr.injector).toEqual(dir.injector);

        hostBindingDir.id = 'bar';
        fixture.update();
        expect(hostDirEl.id).toEqual('bar');
      });
    });

    it('should create instance even when no injector present', () => {
      class MyService {
        value = 'MyService';
        static ngInjectableDef =
            defineInjectable({providedIn: 'root', factory: () => new MyService()});
      }

      class MyComponent {
        constructor(public myService: MyService) {}
        static ngComponentDef = defineComponent({
          type: MyComponent,
          selectors: [['my-component']],
          consts: 1,
          vars: 1,
          factory: () => new MyComponent(directiveInject(MyService)),
          template: function(rf: RenderFlags, ctx: MyComponent) {
            if (rf & RenderFlags.Create) {
              text(0);
            }
            if (rf & RenderFlags.Update) {
              textBinding(0, bind(ctx.myService.value));
            }
          }
        });
      }

      const fixture = new ComponentFixture(MyComponent);
      fixture.update();
      expect(fixture.html).toEqual('MyService');
    });

    it('should throw if directive is not found anywhere', () => {
      class Dir {
        constructor(siblingDir: OtherDir) {}

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dir', '']],
          type: Dir,
          factory: () => new Dir(directiveInject(OtherDir))
        });
      }

      class OtherDir {
        static ngDirectiveDef = defineDirective(
            {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
      }

      /** <div dir></div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dir', '']);
        }
      }, 1, 0, [Dir, OtherDir]);

      expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
    });

    it('should throw if directive is not found in ancestor tree', () => {
      class Dir {
        constructor(siblingDir: OtherDir) {}

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dir', '']],
          type: Dir,
          factory: () => new Dir(directiveInject(OtherDir))
        });
      }

      class OtherDir {
        static ngDirectiveDef = defineDirective(
            {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
      }

      /**
       * <div other></div>
       * <div dir></div>
       */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['other', '']);
          element(1, 'div', ['dir', '']);
        }
      }, 2, 0, [Dir, OtherDir]);

      expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
    });


    it('should throw if directives try to inject each other', () => {
      class DirA {
        constructor(dir: DirB) {}

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirA', '']],
          type: DirA,
          factory: () => new DirA(directiveInject(DirB))
        });
      }

      class DirB {
        constructor(dir: DirA) {}

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dirB', '']],
          type: DirB,
          factory: () => new DirB(directiveInject(DirA))
        });
      }

      /** <div dirA dirB></div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dirA', '', 'dirB', '']);
        }
      }, 1, 0, [DirA, DirB]);

      expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
    });

    it('should throw if directive tries to inject itself', () => {
      class Dir {
        constructor(dir: Dir) {}

        static ngDirectiveDef = defineDirective({
          selectors: [['', 'dir', '']],
          type: Dir,
          factory: () => new Dir(directiveInject(Dir))
        });
      }

      /** <div dir></div> */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['dir', '']);
        }
      }, 1, 0, [Dir]);

      expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
    });

    describe('flags', () => {

      class DirB {
        // TODO(issue/24571): remove '!'.
        value !: string;

        static ngDirectiveDef = defineDirective({
          type: DirB,
          selectors: [['', 'dirB', '']],
          factory: () => new DirB(),
          inputs: {value: 'dirB'}
        });
      }

      describe('Optional', () => {
        let dirA: DirA|null = null;

        class DirA {
          constructor(@Optional() public dirB: DirB|null) {}

          static ngDirectiveDef = defineDirective({
            type: DirA,
            selectors: [['', 'dirA', '']],
            factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
          });
        }

        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', ['dirA', '']);
            }
          }, 1, 0, [DirA, DirB]);

          expect(() => { new ComponentFixture(App); }).not.toThrow();
          expect(dirA !.dirB).toEqual(null);
        });

        it('should not throw if dependency is @Optional (module injector)', () => {
          class SomeModule {
            static ngInjectorDef = defineInjector({factory: () => new SomeModule()});
          }

          /** <div dirA></div> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirA', '']);
            }
          }, 1, 0, [DirA, DirB]);

          expect(() => {
            const injector = createInjector(SomeModule);
            new ComponentFixture(App, {injector});
          }).not.toThrow();
          expect(dirA !.dirB).toEqual(null);
        });

        it('should return null if @Optional dependency has @Self flag', () => {
          let dirC !: DirC;

          class DirC {
            constructor(@Optional() @Self() public dirB: DirB|null) {}

            static ngDirectiveDef = defineDirective({
              type: DirC,
              selectors: [['', 'dirC', '']],
              factory: () => dirC =
                           new DirC(directiveInject(DirB, InjectFlags.Optional|InjectFlags.Self))
            });
          }

          /** <div dirC></div> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirC', '']);
            }
          }, 1, 0, [DirC, DirB]);

          expect(() => { new ComponentFixture(App); }).not.toThrow();
          expect(dirC !.dirB).toEqual(null);
        });

        it('should not throw if dependency is @Optional but defined elsewhere', () => {
          let dirA: DirA;

          class DirA {
            constructor(@Optional() public dirB: DirB|null) {}

            static ngDirectiveDef = defineDirective({
              type: DirA,
              selectors: [['', 'dirA', '']],
              factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
            });
          }

          /**
           * <div dirB></div>
           * <div dirA></div>
           */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirB', '']);
              element(1, 'div', ['dirA', '']);
            }
          }, 2, 0, [DirA, DirB]);

          expect(() => {
            new ComponentFixture(App);
            expect(dirA !.dirB).toEqual(null);
          }).not.toThrow();
        });
      });

      it('should skip the current node with @SkipSelf', () => {
        let dirA: DirA;

        class DirA {
          constructor(@SkipSelf() public dirB: DirB) {}

          static ngDirectiveDef = defineDirective({
            type: DirA,
            selectors: [['', 'dirA', '']],
            factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.SkipSelf))
          });
        }

        /** <div dirA dirB="self"></div> */
        const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            element(0, 'div', ['dirA', '', 'dirB', 'self']);
          }
        }, 1, 0, [DirA, DirB]);

        /* <comp dirB="parent"></comp> */
        const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            element(0, 'comp', ['dirB', 'parent']);
          }
        }, 1, 0, [Comp, DirB]);

        new ComponentFixture(App);
        expect(dirA !.dirB.value).toEqual('parent');
      });

      it('should check only the current node with @Self', () => {
        let dirA: DirA;

        class DirA {
          constructor(@Self() public dirB: DirB) {}

          static ngDirectiveDef = defineDirective({
            type: DirA,
            selectors: [['', 'dirA', '']],
            factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
          });
        }

        /**
         * <div dirB>
         *   <div dirA></div>
         * </div>
         */
        const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dirB', '']);
            element(1, 'div', ['dirA', '']);
            elementEnd();
          }
        }, 2, 0, [DirA, DirB]);

        expect(() => {
          new ComponentFixture(App);
        }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
      });

      it('should check only the current node with @Self even with false positive', () => {
        let dirA: DirA;

        class DirA {
          constructor(@Self() public dirB: DirB) {}

          static ngDirectiveDef = defineDirective({
            type: DirA,
            selectors: [['', 'dirA', '']],
            factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
          });
        }

        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', ['dirB', '']);
            element(1, 'div', ['dirA', '', 'dirC', '']);
            elementEnd();
          }
        }, 2, 0, [DirA, DirB, 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('@Host', () => {
        let dirA: DirA|null = null;
        let dirString: DirString|null = null;

        beforeEach(() => {
          dirA = null;
          dirString = null;
        });

        class DirA {
          constructor(@Host() public dirB: DirB) {}

          static ngDirectiveDef = defineDirective({
            type: DirA,
            selectors: [['', 'dirA', '']],
            factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Host))
          });
        }

        class DirString {
          constructor(@Host() public s: String) {}

          static ngDirectiveDef = defineDirective({
            type: DirString,
            selectors: [['', 'dirString', '']],
            factory: () => dirString = new DirString(directiveInject(String, InjectFlags.Host))
          });
        }

        it('should find viewProviders on the host itself', () => {
          /** <div dirString></div> */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirString', '']);
            }
          }, 1, 0, [DirString], [], null, [], [{provide: String, useValue: 'Foo'}]);

          /* <comp></comp> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'comp');
            }
          }, 1, 0, [Comp]);

          new ComponentFixture(App);
          expect(dirString !.s).toEqual('Foo');
        });

        it('should find host component on the host itself', () => {
          let dirComp: DirComp|null = null;

          class DirComp {
            constructor(@Host() public comp: any) {}

            static ngDirectiveDef = defineDirective({
              type: DirComp,
              selectors: [['', 'dirCmp', '']],
              factory: () => dirComp = new DirComp(directiveInject(Comp, InjectFlags.Host))
            });
          }

          /** <div dirCmp></div> */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirCmp', '']);
            }
          }, 1, 0, [DirComp]);

          /* <comp></comp> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'comp');
            }
          }, 1, 0, [Comp]);

          new ComponentFixture(App);
          expect(dirComp !.comp instanceof Comp).toBeTruthy();
        });

        it('should not find providers on the host itself', () => {
          /** <div dirString></div> */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirString', '']);
            }
          }, 1, 0, [DirString], [], null, [{provide: String, useValue: 'Foo'}]);

          /* <comp></comp> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'comp');
            }
          }, 1, 0, [Comp]);

          expect(() => {
            new ComponentFixture(App);
          }).toThrowError(/NodeInjector: NOT_FOUND \[String\]/);
        });

        it('should not find other directives on the host itself', () => {
          /** <div dirA></div> */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirA', '']);
            }
          }, 1, 0, [DirA]);

          /* <comp dirB></comp> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'comp', ['dirB', '']);
            }
          }, 1, 0, [Comp, DirB]);

          expect(() => {
            new ComponentFixture(App);
          }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
        });

        it('should not find providers on the host itself if in inline view', () => {
          let comp !: any;

          /**
           * % if (showing) {
           *   <div dirA></div>
           * % }
           */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              container(0);
            }
            if (rf & RenderFlags.Update) {
              containerRefreshStart(0);
              {
                if (ctx.showing) {
                  let rf1 = embeddedViewStart(0, 1, 0);
                  if (rf1 & RenderFlags.Create) {
                    element(0, 'div', ['dirA', '']);
                  }
                  embeddedViewEnd();
                }
              }
              containerRefreshEnd();
            }
          }, 1, 0, [DirA, DirB]);

          /* <comp dirB></comp> */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'comp', ['dirB', '']);
            }
            if (rf & RenderFlags.Update) {
              comp = getDirectiveOnNode(0);
            }
          }, 1, 0, [Comp, DirB]);

          const fixture = new ComponentFixture(App);
          expect(() => {
            comp.showing = true;
            fixture.update();
          }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
        });

        it('should find providers across embedded views if not passing component boundary', () => {
          let dirB !: DirB;

          function IfTemplate(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirA', '']);
            }
          }

          /**
           * <div dirB>
           *   <div *ngIf="showing" dirA></div>
           * </div>
           */
          const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              elementStart(0, 'div', ['dirB', '']);
              { template(1, IfTemplate, 1, 0, 'div', ['ngIf', '']); }
              elementEnd();
            }
            if (rf & RenderFlags.Update) {
              elementProperty(1, 'ngIf', bind(ctx.showing));

              // testing only
              dirB = getDirectiveOnNode(0);
            }
          }, 2, 1, [NgIf, DirA, DirB]);

          const fixture = new ComponentFixture(App);
          fixture.component.showing = true;
          fixture.update();

          expect(dirA !.dirB).toEqual(dirB);
        });

        it('should not find component above the host', () => {
          let dirComp: DirComp|null = null;

          class DirComp {
            constructor(@Host() public comp: any) {}

            static ngDirectiveDef = defineDirective({
              type: DirComp,
              selectors: [['', 'dirCmp', '']],
              factory: () => dirComp = new DirComp(directiveInject(App, InjectFlags.Host))
            });
          }

          /** <div dirCmp></div> */
          const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
            if (rf & RenderFlags.Create) {
              element(0, 'div', ['dirCmp', '']);
            }
          }, 1, 0, [DirComp]);

          /* <comp></comp> */
          class App {
            static ngComponentDef = defineComponent({
              type: App,
              selectors: [['app']],
              consts: 1,
              vars: 0,
              factory: () => new App,
              template: function(rf: RenderFlags, ctx: any) {
                if (rf & RenderFlags.Create) {
                  element(0, 'comp');
                }
              },
              directives: [Comp],
            });
          }

          expect(() => {
            new ComponentFixture(App);
          }).toThrowError(/NodeInjector: NOT_FOUND \[App\]/);
        });

        describe('regression', () => {
          // based on https://stackblitz.com/edit/angular-riss8k?file=src/app/app.component.ts
          it('should allow directives with Host flag to inject view providers from containing component',
             () => {
               let controlContainers: ControlContainer[] = [];
               let injectedControlContainer: ControlContainer|null = null;

               class ControlContainer {}

               /*
               @Directive({
                 selector: '[group]',
                 providers: [{provide: ControlContainer, useExisting: GroupDirective}]
               })
               */
               class GroupDirective {
                 constructor() { controlContainers.push(this); }

                 static ngDirectiveDef = defineDirective({
                   type: GroupDirective,
                   selectors: [['', 'group', '']],
                   factory: () => new GroupDirective(),
                   features: [ProvidersFeature(
                       [{provide: ControlContainer, useExisting: GroupDirective}])],
                 });
               }

               // @Directive({selector: '[controlName]'})
               class ControlNameDirective {
                 constructor(@Host() @SkipSelf() @Inject(ControlContainer) parent:
                                 ControlContainer) {
                   injectedControlContainer = parent;
                 }

                 static ngDirectiveDef = defineDirective({
                   type: ControlNameDirective,
                   selectors: [['', 'controlName', '']],
                   factory: () => new ControlNameDirective(directiveInject(
                                ControlContainer, InjectFlags.Host|InjectFlags.SkipSelf))
                 });
               }

               /*
               @Component({
                 selector: 'child',
                 template: `
                   <input controlName type="text">
                 `,
                 viewProviders: [{provide: ControlContainer, useExisting: GroupDirective}]
               })
               */
               class ChildComponent {
                 static ngComponentDef = defineComponent({
                   type: ChildComponent,
                   selectors: [['child']],
                   consts: 1,
                   vars: 0,
                   factory: () => new ChildComponent(),
                   template: function(rf: RenderFlags, ctx: ChildComponent) {
                     if (rf & RenderFlags.Create) {
                       element(0, 'input', ['controlName', '', 'type', 'text']);
                     }
                   },
                   directives: [ControlNameDirective],
                   features: [ProvidersFeature(
                       [], [{provide: ControlContainer, useExisting: GroupDirective}])],
                 });
               }
               /*
               @Component({
                 selector: 'my-app',
                 template: `
                   <div group>
                     <child></child>
                   </div>
                 `
               })
               */
               class AppComponent {
                 static ngComponentDef = defineComponent({
                   type: AppComponent,
                   selectors: [['my-app']],
                   consts: 2,
                   vars: 0,
                   factory: () => new AppComponent(),
                   template: function(rf: RenderFlags, ctx: AppComponent) {
                     if (rf & RenderFlags.Create) {
                       elementStart(0, 'div', ['group', '']);
                       element(1, 'child');
                       elementEnd();
                     }
                   },
                   directives: [ChildComponent, GroupDirective]
                 });
               }

               const fixture = new ComponentFixture(AppComponent as ComponentType<AppComponent>);
               expect(fixture.html)
                   .toEqual(
                       '<div group=""><child><input controlname="" type="text"></child></div>');

               expect(controlContainers).toEqual([injectedControlContainer !]);

             });
        });
      });
    });
  });

  describe('Special tokens', () => {

    describe('Injector', () => {

      it('should inject the injector', () => {
        let injectorDir !: InjectorDir;
        let otherInjectorDir !: OtherInjectorDir;
        let divElement !: HTMLElement;

        class InjectorDir {
          constructor(public injector: Injector) {}

          static ngDirectiveDef = defineDirective({
            type: InjectorDir,
            selectors: [['', 'injectorDir', '']],
            factory: () => injectorDir = new InjectorDir(directiveInject(Injector as any))
          });
        }

        class OtherInjectorDir {
          constructor(public otherDir: InjectorDir, public injector: Injector) {}

          static ngDirectiveDef = defineDirective({
            type: OtherInjectorDir,
            selectors: [['', 'otherInjectorDir', '']],
            factory: () => otherInjectorDir = new OtherInjectorDir(
                         directiveInject(InjectorDir), directiveInject(Injector as any))
          });
        }


        /** <div injectorDir otherInjectorDir></div> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'div', ['injectorDir', '', 'otherInjectorDir', '']);
          }
          // testing only
          divElement = load(0);
        }, 1, 0, [InjectorDir, OtherInjectorDir]);

        const fixture = new ComponentFixture(App);
        expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
        expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
        expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir);
        expect(injectorDir.injector).not.toBe(otherInjectorDir.injector);
      });

      it('should inject INJECTOR', () => {
        let injectorDir !: INJECTORDir;
        let divElement !: HTMLElement;

        class INJECTORDir {
          constructor(public injector: Injector) {}

          static ngDirectiveDef = defineDirective({
            type: INJECTORDir,
            selectors: [['', 'injectorDir', '']],
            factory: () => injectorDir = new INJECTORDir(directiveInject(INJECTOR as any))
          });
        }


        /** <div injectorDir otherInjectorDir></div> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'div', ['injectorDir', '']);
          }
          // testing only
          divElement = load(0);
        }, 1, 0, [INJECTORDir]);

        const fixture = new ComponentFixture(App);
        expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
        expect(injectorDir.injector.get(Injector).get(ElementRef).nativeElement).toBe(divElement);
        expect(injectorDir.injector.get(INJECTOR).get(ElementRef).nativeElement).toBe(divElement);
      });

    });

    describe('ElementRef', () => {

      it('should create directive with ElementRef dependencies', () => {
        let dir !: Directive;
        let dirSameInstance !: DirectiveSameInstance;
        let div !: RElement;

        class Directive {
          value: string;
          constructor(public elementRef: ElementRef) {
            this.value = (elementRef.constructor as any).name;
          }
          static ngDirectiveDef = defineDirective({
            type: Directive,
            selectors: [['', 'dir', '']],
            factory: () => dir = new Directive(directiveInject(ElementRef)),
            exportAs: ['dir']
          });
        }

        class DirectiveSameInstance {
          isSameInstance: boolean;
          constructor(public elementRef: ElementRef, directive: Directive) {
            this.isSameInstance = elementRef === directive.elementRef;
          }
          static ngDirectiveDef = defineDirective({
            type: DirectiveSameInstance,
            selectors: [['', 'dirSame', '']],
            factory: () => dirSameInstance = new DirectiveSameInstance(
                         directiveInject(ElementRef), directiveInject(Directive)),
            exportAs: ['dirSame']
          });
        }

        /** <div dir dirSame></div> */
        const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            elementStart(0, 'div', ['dir', '', 'dirSame', '']);
            elementEnd();
            div = getNativeByIndex(0, getLView());
          }
        }, 1, 0, [Directive, DirectiveSameInstance]);

        const fixture = new ComponentFixture(App);
        expect(dir.value).toContain('ElementRef');
        expect(dir.elementRef.nativeElement).toEqual(div);
        expect(dirSameInstance.elementRef.nativeElement).toEqual(div);

        // Each ElementRef instance should be unique
        expect(dirSameInstance.isSameInstance).toBe(false);
      });

      it('should create ElementRef with comment if requesting directive is on <ng-template> node',
         () => {
           let dir !: Directive;
           let lContainer !: LContainer;

           class Directive {
             value: string;
             constructor(public elementRef: ElementRef) {
               this.value = (elementRef.constructor as any).name;
             }
             static ngDirectiveDef = defineDirective({
               type: Directive,
               selectors: [['', 'dir', '']],
               factory: () => dir = new Directive(directiveInject(ElementRef)),
               exportAs: ['dir']
             });
           }

           /** <ng-template dir></ng-template> */
           const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
             if (rf & RenderFlags.Create) {
               template(0, () => {}, 0, 0, 'ng-template', ['dir', '']);
               lContainer = load(0) as any;
             }
           }, 1, 0, [Directive]);

           const fixture = new ComponentFixture(App);
           expect(dir.value).toContain('ElementRef');
           expect(dir.elementRef.nativeElement).toEqual(lContainer[NATIVE]);
         });
    });

    describe('TemplateRef', () => {
      class Directive {
        value: string;
        constructor(public templateRef: TemplateRef<any>) {
          this.value = (templateRef.constructor as any).name;
        }
        static ngDirectiveDef = defineDirective({
          type: Directive,
          selectors: [['', 'dir', '']],
          factory: () => new Directive(directiveInject(TemplateRef as any)),
          exportAs: ['dir']
        });
      }

      it('should create directive with TemplateRef dependencies', () => {
        class DirectiveSameInstance {
          isSameInstance: boolean;
          constructor(templateRef: TemplateRef<any>, directive: Directive) {
            this.isSameInstance = templateRef === directive.templateRef;
          }
          static ngDirectiveDef = defineDirective({
            type: DirectiveSameInstance,
            selectors: [['', 'dirSame', '']],
            factory: () => new DirectiveSameInstance(
                         directiveInject(TemplateRef as any), directiveInject(Directive)),
            exportAs: ['dirSame']
          });
        }

        /**
         * <ng-template dir dirSame #dir="dir" #dirSame="dirSame">
         *   {{ dir.value }} - {{ dirSame.value }}
         * </ng-template>
         */
        const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            template(
                0, function() {}, 0, 0, 'ng-template', ['dir', '', 'dirSame', ''],
                ['dir', 'dir', 'dirSame', 'dirSame']);
            text(3);
          }
          if (rf & RenderFlags.Update) {
            const tmp1 = reference(1) as any;
            const tmp2 = reference(2) as any;
            textBinding(3, interpolation2('', tmp1.value, '-', tmp2.isSameInstance, ''));
          }
        }, 4, 2, [Directive, DirectiveSameInstance]);

        const fixture = new ComponentFixture(App);
        // Each TemplateRef instance should be unique
        expect(fixture.html).toContain('TemplateRef');
        expect(fixture.html).toContain('false');
      });

      it('should throw if injected on an element', () => {
        /** <div dir></div> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'div', ['dir', '']);
          }
        }, 1, 0, [Directive]);

        expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
      });

      it('should throw if injected on an ng-container', () => {
        /** <ng-container dir></ng-container> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            elementContainerStart(0, ['dir', '']);
            elementContainerEnd();
          }
        }, 1, 0, [Directive]);

        expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
      });

      it('should NOT throw if optional and injected on an element', () => {
        let dir !: OptionalDirective;
        class OptionalDirective {
          constructor(@Optional() public templateRef: TemplateRef<any>) {}

          static ngDirectiveDef = defineDirective({
            type: OptionalDirective,
            selectors: [['', 'dir', '']],
            factory: () => dir = new OptionalDirective(
                         directiveInject(TemplateRef as any, InjectFlags.Optional)),
            exportAs: ['dir']
          });
        }

        /** <div dir></div> */
        const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
          if (rf & RenderFlags.Create) {
            element(0, 'div', ['dir', '']);
          }
        }, 1, 0, [OptionalDirective]);

        expect(() => new ComponentFixture(App)).not.toThrow();
        expect(dir.templateRef).toBeNull();
      });

    });

    describe('ViewContainerRef', () => {
      it('should create directive with ViewContainerRef dependencies', () => {
        class Directive {
          value: string;
          constructor(public viewContainerRef: ViewContainerRef) {
            this.value = (viewContainerRef.constructor as any).name;
          }
          static ngDirectiveDef = defineDirective({
            type: Directive,
            selectors: [['', 'dir', '']],
            factory: () => new Directive(directiveInject(ViewContainerRef as any)),
            exportAs: ['dir']
          });
        }

        class DirectiveSameInstance {
          isSameInstance: boolean;
          constructor(viewContainerRef: ViewContainerRef, directive: Directive) {
            this.isSameInstance = viewContainerRef === directive.viewContainerRef;
          }
          static ngDirectiveDef = defineDirective({
            type: DirectiveSameInstance,
            selectors: [['', 'dirSame', '']],
            factory: () => new DirectiveSameInstance(
                         directiveInject(ViewContainerRef as any), directiveInject(Directive)),
            exportAs: ['dirSame']
          });
        }

        /**
         * <div dir dirSame #dir="dir" #dirSame="dirSame">
         *   {{ dir.value }} - {{ dirSame.value }}
         * </div>
         */
        const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
          if (rf & RenderFlags.Create) {
            elementStart(
                0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']);
            { text(3); }
            elementEnd();
          }
          if (rf & RenderFlags.Update) {
            const tmp1 = reference(1) as any;
            const tmp2 = reference(2) as any;
            textBinding(3, interpolation2('', tmp1.value, '-', tmp2.isSameInstance, ''));
          }
        }, 4, 2, [Directive, DirectiveSameInstance]);

        const fixture = new ComponentFixture(App);
        // Each ViewContainerRef instance should be unique
        expect(fixture.html).toContain('ViewContainerRef');
        expect(fixture.html).toContain('false');
      });
    });

    describe('ChangeDetectorRef', () => {
      let dir: Directive;
      let dirSameInstance: DirectiveSameInstance;
      let comp: MyComp;

      class MyComp {
        constructor(public cdr: ChangeDetectorRef) {}

        static ngComponentDef = defineComponent({
          type: MyComp,
          selectors: [['my-comp']],
          factory: () => comp = new MyComp(directiveInject(ChangeDetectorRef as any)),
          consts: 1,
          vars: 0,
          template: function(rf: RenderFlags, ctx: MyComp) {
            if (rf & RenderFlags.Create) {
              projectionDef();
              projection(0);
            }
          }
        });
      }

      class Directive {
        value: string;

        constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }

        static ngDirectiveDef = defineDirective({
          type: Directive,
          selectors: [['', 'dir', '']],
          factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)),
          exportAs: ['dir']
        });
      }

      class DirectiveSameInstance {
        constructor(public cdr: ChangeDetectorRef) {}

        static ngDirectiveDef = defineDirective({
          type: DirectiveSameInstance,
          selectors: [['', 'dirSame', '']],
          factory: () => dirSameInstance =
                       new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any))
        });
      }

      const directives = [MyComp, Directive, DirectiveSameInstance, NgIf];

      it('should inject current component ChangeDetectorRef into directives on the same node as components',
         () => {
           /** <my-comp dir dirSameInstance #dir="dir"></my-comp> {{ dir.value }} */
           const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
             if (rf & RenderFlags.Create) {
               element(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
               text(2);
             }
             if (rf & RenderFlags.Update) {
               const tmp = reference(1) as any;
               textBinding(2, bind(tmp.value));
             }
           }, 3, 1, directives);

           const app = renderComponent(MyApp);
           // ChangeDetectorRef is the token, ViewRef has historically been the constructor
           expect(toHtml(app)).toEqual('<my-comp dir="" dirsame=""></my-comp>ViewRef');
           expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);

           // Each ChangeDetectorRef instance should be unique
           expect(dir !.cdr).not.toBe(comp !.cdr);
           expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
         });

      it('should inject host component ChangeDetectorRef into directives on normal elements',
         () => {

           class MyApp {
             constructor(public cdr: ChangeDetectorRef) {}

             static ngComponentDef = defineComponent({
               type: MyApp,
               selectors: [['my-app']],
               consts: 3,
               vars: 1,
               factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
               /** <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
               template: function(rf: RenderFlags, ctx: any) {
                 if (rf & RenderFlags.Create) {
                   elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
                   { text(2); }
                   elementEnd();
                 }
                 if (rf & RenderFlags.Update) {
                   const tmp = reference(1) as any;
                   textBinding(2, bind(tmp.value));
                 }
               },
               directives: directives
             });
           }

           const app = renderComponent(MyApp);
           expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
           expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);

           // Each ChangeDetectorRef instance should be unique
           expect(dir !.cdr).not.toBe(app.cdr);
           expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
         });

      it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren',
         () => {
           class MyApp {
             constructor(public cdr: ChangeDetectorRef) {}

             static ngComponentDef = defineComponent({
               type: MyApp,
               selectors: [['my-app']],
               consts: 4,
               vars: 1,
               factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
               /**
                * <my-comp>
                *   <div dir dirSameInstance #dir="dir"></div>
                * </my-comp>
                * {{ dir.value }}
                */
               template: function(rf: RenderFlags, ctx: any) {
                 if (rf & RenderFlags.Create) {
                   elementStart(0, 'my-comp');
                   { element(1, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); }
                   elementEnd();
                   text(3);
                 }
                 if (rf & RenderFlags.Update) {
                   const tmp = reference(2) as any;
                   textBinding(3, bind(tmp.value));
                 }
               },
               directives: directives
             });
           }

           const app = renderComponent(MyApp);
           expect(toHtml(app)).toEqual('<my-comp><div dir="" dirsame=""></div></my-comp>ViewRef');
           expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);

           // Each ChangeDetectorRef instance should be unique
           expect(dir !.cdr).not.toBe(app !.cdr);
           expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
         });

      it('should inject host component ChangeDetectorRef into directives in embedded views', () => {

        class MyApp {
          showing = true;

          constructor(public cdr: ChangeDetectorRef) {}

          static ngComponentDef = defineComponent({
            type: MyApp,
            selectors: [['my-app']],
            factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
            consts: 1,
            vars: 0,
            /**
             * % if (showing) {
           *   <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div>
           * % }
             */
            template: function(rf: RenderFlags, ctx: MyApp) {
              if (rf & RenderFlags.Create) {
                container(0);
              }
              if (rf & RenderFlags.Update) {
                containerRefreshStart(0);
                {
                  if (ctx.showing) {
                    let rf1 = embeddedViewStart(0, 3, 1);
                    if (rf1 & RenderFlags.Create) {
                      elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
                      { text(2); }
                      elementEnd();
                    }
                    if (rf1 & RenderFlags.Update) {
                      const tmp = reference(1) as any;
                      textBinding(2, bind(tmp.value));
                    }
                  }
                  embeddedViewEnd();
                }
                containerRefreshEnd();
              }
            },
            directives: directives
          });
        }

        const app = renderComponent(MyApp);
        expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
        expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);

        // Each ChangeDetectorRef instance should be unique
        expect(dir !.cdr).not.toBe(app.cdr);
        expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
      });

      it('should inject host component ChangeDetectorRef into directives on containers', () => {
        function C1(rf1: RenderFlags, ctx1: any) {
          if (rf1 & RenderFlags.Create) {
            elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
            { text(2); }
            elementEnd();
          }
          if (rf1 & RenderFlags.Update) {
            const tmp = reference(1) as any;
            textBinding(2, bind(tmp.value));
          }
        }

        class MyApp {
          showing = true;

          constructor(public cdr: ChangeDetectorRef) {}

          static ngComponentDef = defineComponent({
            type: MyApp,
            selectors: [['my-app']],
            factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
            consts: 1,
            vars: 0,
            /** <div *ngIf="showing" dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
            template: function(rf: RenderFlags, ctx: MyApp) {
              if (rf & RenderFlags.Create) {
                template(0, C1, 3, 1, 'div', ['ngIf', 'showing']);
              }
            },
            directives: directives
          });
        }

        const app = renderComponent(MyApp);
        expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
        expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);

        // Each ChangeDetectorRef instance should be unique
        expect(dir !.cdr).not.toBe(app.cdr);
        expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
      });
    });
  });

  describe('string tokens', () => {
    it('should be able to provide a string token', () => {
      let injectorDir !: InjectorDir;
      let divElement !: HTMLElement;

      class InjectorDir {
        constructor(public value: string) {}

        static ngDirectiveDef = defineDirective({
          type: InjectorDir,
          selectors: [['', 'injectorDir', '']],
          factory: () => injectorDir = new InjectorDir(directiveInject('test' as any)),
          features: [ProvidersFeature([{provide: 'test', useValue: 'provided'}])],
        });
      }

      /** <div injectorDir otherInjectorDir></div> */
      const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
        if (rf & RenderFlags.Create) {
          element(0, 'div', ['injectorDir', '']);
        }
        // testing only
        divElement = load(0);
      }, 1, 0, [InjectorDir]);

      const fixture = new ComponentFixture(App);
      expect(injectorDir.value).toBe('provided');
    });
  });

  describe('Renderer2', () => {
    class MyComp {
      constructor(public renderer: Renderer2) {}

      static ngComponentDef = defineComponent({
        type: MyComp,
        selectors: [['my-comp']],
        factory: () => new MyComp(directiveInject(Renderer2 as any)),
        consts: 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('@Attribute', () => {
    let myDirectiveInstance !: MyDirective | null;

    class MyDirective {
      exists = 'wrong' as string | null;
      myDirective = 'wrong' as string | null;
      constructor(
          @Attribute('exist') existAttrValue: string|null,
          @Attribute('myDirective') myDirectiveAttrValue: string|null) {
        this.exists = existAttrValue;
        this.myDirective = myDirectiveAttrValue;
      }

      static ngDirectiveDef = defineDirective({
        type: MyDirective,
        selectors: [['', 'myDirective', '']],
        factory: () => myDirectiveInstance =
                     new MyDirective(injectAttribute('exist'), injectAttribute('myDirective'))
      });
    }

    beforeEach(() => myDirectiveInstance = null);

    it('should inject attribute', () => {
      let exist = 'wrong' as string | null;
      let nonExist = 'wrong' as string | null;

      const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
          exist = injectAttribute('exist');
          nonExist = injectAttribute('nonExist');
        }
      }, 1);

      new ComponentFixture(MyApp);
      expect(exist).toEqual('existValue');
      expect(nonExist).toBeNull();
    });

    // https://stackblitz.com/edit/angular-scawyi?file=src%2Fapp%2Fapp.component.ts
    it('should inject attributes on <ng-template>', () => {
      let myDirectiveInstance: MyDirective;

      /* <ng-template myDirective="initial" exist="existValue" other="ignore"></ng-template>*/
      const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          template(
              0, null, 0, 0, 'ng-template',
              ['myDirective', 'initial', 'exist', 'existValue', 'other', 'ignore']);
        }
        if (rf & RenderFlags.Update) {
          myDirectiveInstance = getDirectiveOnNode(0);
        }
      }, 1, 0, [MyDirective]);

      new ComponentFixture(MyApp);
      expect(myDirectiveInstance !.exists).toEqual('existValue');
      expect(myDirectiveInstance !.myDirective).toEqual('initial');
    });

    // https://stackblitz.com/edit/angular-scawyi?file=src%2Fapp%2Fapp.component.ts
    it('should inject attributes on <ng-container>', () => {
      let myDirectiveInstance: MyDirective;

      /* <ng-container myDirective="initial" exist="existValue" other="ignore"></ng-container>*/
      const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementContainerStart(
              0, ['myDirective', 'initial', 'exist', 'existValue', 'other', 'ignore']);
          elementContainerEnd();
        }
        if (rf & RenderFlags.Update) {
          myDirectiveInstance = getDirectiveOnNode(0);
        }
      }, 1, 0, [MyDirective]);

      new ComponentFixture(MyApp);
      expect(myDirectiveInstance !.exists).toEqual('existValue');
      expect(myDirectiveInstance !.myDirective).toEqual('initial');
    });

    // https://stackblitz.com/edit/angular-8ytqkp?file=src%2Fapp%2Fapp.component.ts
    it('should not inject attributes representing bindings and outputs', () => {
      let exist = 'wrong' as string | null;
      let nonExist = 'wrong' as string | null;

      const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['exist', 'existValue', AttributeMarker.SelectOnly, 'nonExist']);
          exist = injectAttribute('exist');
          nonExist = injectAttribute('nonExist');
        }
      }, 1);

      new ComponentFixture(MyApp);
      expect(exist).toEqual('existValue');
      expect(nonExist).toBeNull();
    });

    it('should not accidentally inject attributes representing bindings and outputs', () => {
      let exist = 'wrong' as string | null;
      let nonExist = 'wrong' as string | null;

      const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', [
            'exist', 'existValue', AttributeMarker.SelectOnly, 'binding1', 'nonExist', 'binding2'
          ]);
          exist = injectAttribute('exist');
          nonExist = injectAttribute('nonExist');
        }
      }, 1);

      new ComponentFixture(MyApp);
      expect(exist).toEqual('existValue');
      expect(nonExist).toBeNull();
    });
  });

  describe('inject', () => {
    describe('bloom filter', () => {
      let mockTView: any;
      beforeEach(() => {
        mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstTemplatePass: 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);
      });
    });

    it('should inject from parent view', () => {
      const ParentDirective = createDirective('parentDir');

      class ChildDirective {
        value: string;
        constructor(public parent: any) { this.value = (parent.constructor as any).name; }
        static ngDirectiveDef = defineDirective({
          type: ChildDirective,
          selectors: [['', 'childDir', '']],
          factory: () => new ChildDirective(directiveInject(ParentDirective)),
          exportAs: ['childDir']
        });
      }

      class Child2Directive {
        value: boolean;
        constructor(parent: any, child: ChildDirective) { this.value = parent === child.parent; }
        static ngDirectiveDef = defineDirective({
          selectors: [['', 'child2Dir', '']],
          type: Child2Directive,
          factory: () => new Child2Directive(
                       directiveInject(ParentDirective), directiveInject(ChildDirective)),
          exportAs: ['child2Dir']
        });
      }

      /**
       * <div parentDir>
       *    % if (...) {
       *    <span childDir child2Dir #child1="childDir" #child2="child2Dir">
       *      {{ child1.value }} - {{ child2.value }}
       *    </span>
       *    % }
       * </div>
       */
      const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
        if (rf & RenderFlags.Create) {
          elementStart(0, 'div', ['parentDir', '']);
          { container(1); }
          elementEnd();
        }
        if (rf & RenderFlags.Update) {
          containerRefreshStart(1);
          {
            let rf1 = embeddedViewStart(0, 4, 2);
            if (rf1 & RenderFlags.Create) {
              elementStart(
                  0, 'span', ['childDir', '', 'child2Dir', ''],
                  ['child1', 'childDir', 'child2', 'child2Dir']);
              { text(3); }
              elementEnd();
            }
            if (rf & RenderFlags.Update) {
              const tmp1 = reference(1) as any;
              const tmp2 = reference(2) as any;
              textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
            }
            embeddedViewEnd();
          }
          containerRefreshEnd();
        }
      }, 2, 0, [ChildDirective, Child2Directive, ParentDirective]);

      const fixture = new ComponentFixture(App);
      expect(fixture.html)
          .toEqual('<div parentdir=""><span child2dir="" childdir="">Directive-true</span></div>');
    });

    it('should inject from module Injector', () => {

                                             });
  });

  describe('getOrCreateNodeInjector', () => {
    it('should handle initial undefined state', () => {
      const contentView = createLView(
          null, createTView(-1, null, 1, 0, null, null, null), null, LViewFlags.CheckAlways, null,
          null, {} as any, {} as any);
      const oldView = enterView(contentView, null);
      try {
        const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, 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 {
        leaveView(oldView);
      }
    });
  });

});
