From 0e4705aec3c8c6fafe087fc1380fa21fd48db705 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 7 Feb 2019 11:49:44 +0100 Subject: [PATCH] test(ivy): run view_injector_integration tests on node (#28593) There is nothing browser specific in those tests and fakeAsync is supported on node. Testing / debugging on node is often faster than on Karma. PR Close #28593 --- .../linker/view_injector_integration_spec.ts | 1668 ++++++++--------- 1 file changed, 823 insertions(+), 845 deletions(-) diff --git a/packages/core/test/linker/view_injector_integration_spec.ts b/packages/core/test/linker/view_injector_integration_spec.ts index 7c952e931b..f6604d552b 100644 --- a/packages/core/test/linker/view_injector_integration_spec.ts +++ b/packages/core/test/linker/view_injector_integration_spec.ts @@ -8,7 +8,6 @@ import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, DebugElement, Directive, ElementRef, EmbeddedViewRef, Host, Inject, InjectionToken, Injector, Input, NgModule, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing'; -import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing'; @@ -183,898 +182,877 @@ export class DuplicatePipe2 implements PipeTransform { class TestComp { } -(function() { - function createComponentFixture( - template: string, providers?: Provider[] | null, comp?: Type): ComponentFixture { - if (!comp) { - comp = TestComp; - } - TestBed.overrideComponent(comp !, {set: {template}}); - if (providers && providers.length) { - TestBed.overrideComponent(comp !, {add: {providers: providers}}); - } - return TestBed.createComponent(comp !); +function createComponentFixture( + template: string, providers?: Provider[] | null, comp?: Type): ComponentFixture { + if (!comp) { + comp = TestComp; } - - function createComponent( - template: string, providers?: Provider[], comp?: Type): DebugElement { - const fixture = createComponentFixture(template, providers, comp); - fixture.detectChanges(); - return fixture.debugElement; + TestBed.overrideComponent(comp !, {set: {template}}); + if (providers && providers.length) { + TestBed.overrideComponent(comp !, {add: {providers: providers}}); } + return TestBed.createComponent(comp !); +} - describe('View injector', () => { - // On CJS fakeAsync is not supported... - if (!getDOM().supportsDOMEvents()) return; +function createComponent(template: string, providers?: Provider[], comp?: Type): DebugElement { + const fixture = createComponentFixture(template, providers, comp); + fixture.detectChanges(); + return fixture.debugElement; +} - const TOKEN = new InjectionToken('token'); +describe('View injector', () => { + const TOKEN = new InjectionToken('token'); - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TestComp], - providers: [ - {provide: TOKEN, useValue: 'appService'}, - {provide: 'appService', useFactory: (v: string) => v, deps: [TOKEN]}, - ], - }); + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestComp], + providers: [ + {provide: TOKEN, useValue: 'appService'}, + {provide: 'appService', useFactory: (v: string) => v, deps: [TOKEN]}, + ], + }); + }); + + describe('injection', () => { + it('should instantiate directives that have no dependencies', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective]}); + const el = createComponent('
'); + expect(el.children[0].injector.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); }); - describe('injection', () => { - it('should instantiate directives that have no dependencies', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective]}); - const el = createComponent('
'); - expect(el.children[0].injector.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); - }); + it('should instantiate directives that depend on another directive', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]}); + const el = createComponent('
'); - it('should instantiate directives that depend on another directive', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]}); - const el = createComponent('
'); + const d = el.children[0].injector.get(NeedsDirective); - const d = el.children[0].injector.get(NeedsDirective); - - expect(d).toBeAnInstanceOf(NeedsDirective); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it('should support useValue with different values', () => { - const el = createComponent('', [ - {provide: 'numLiteral', useValue: 0}, - {provide: 'boolLiteral', useValue: true}, - {provide: 'strLiteral', useValue: 'a'}, - {provide: 'null', useValue: null}, - {provide: 'array', useValue: [1]}, - {provide: 'map', useValue: {'a': 1}}, - {provide: 'instance', useValue: new TestValue('a')}, - {provide: 'nested', useValue: [{'a': [1]}, new TestValue('b')]}, - ]); - expect(el.injector.get('numLiteral')).toBe(0); - expect(el.injector.get('boolLiteral')).toBe(true); - expect(el.injector.get('strLiteral')).toBe('a'); - expect(el.injector.get('null')).toBe(null); - expect(el.injector.get('array')).toEqual([1]); - expect(el.injector.get('map')).toEqual({'a': 1}); - expect(el.injector.get('instance')).toEqual(new TestValue('a')); - expect(el.injector.get('nested')).toEqual([{'a': [1]}, new TestValue('b')]); - }); - - it('should instantiate providers that have dependencies with SkipSelf', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, SomeOtherDirective]}); - TestBed.overrideDirective( - SimpleDirective, - {add: {providers: [{provide: 'injectable1', useValue: 'injectable1'}]}}); - TestBed.overrideDirective(SomeOtherDirective, { - add: { - providers: [ - {provide: 'injectable1', useValue: 'new-injectable1'}, { - provide: 'injectable2', - useFactory: (val: any) => `${val}-injectable2`, - deps: [[new Inject('injectable1'), new SkipSelf()]] - } - ] - } - }); - const el = createComponent('
'); - expect(el.children[0].children[0].injector.get('injectable2')) - .toEqual('injectable1-injectable2'); - }); - - it('should instantiate providers that have dependencies', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective]}); - const providers = [ - {provide: 'injectable1', useValue: 'injectable1'}, { - provide: 'injectable2', - useFactory: (val: any) => `${val}-injectable2`, - deps: ['injectable1'] - } - ]; - TestBed.overrideDirective(SimpleDirective, {add: {providers}}); - const el = createComponent('
'); - expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); - }); - - it('should instantiate viewProviders that have dependencies', () => { - TestBed.configureTestingModule({declarations: [SimpleComponent]}); - const viewProviders = [ - {provide: 'injectable1', useValue: 'injectable1'}, { - provide: 'injectable2', - useFactory: (val: any) => `${val}-injectable2`, - deps: ['injectable1'] - } - ]; - TestBed.overrideComponent(SimpleComponent, {set: {viewProviders}}); - const el = createComponent('
'); - expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); - }); - - it('should instantiate components that depend on viewProviders providers', () => { - TestBed.configureTestingModule({declarations: [NeedsServiceComponent]}); - TestBed.overrideComponent( - NeedsServiceComponent, {set: {providers: [{provide: 'service', useValue: 'service'}]}}); - const el = createComponent('
'); - expect(el.children[0].injector.get(NeedsServiceComponent).service).toEqual('service'); - }); - - it('should instantiate multi providers', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective]}); - const providers = [ - {provide: 'injectable1', useValue: 'injectable11', multi: true}, - {provide: 'injectable1', useValue: 'injectable12', multi: true} - ]; - TestBed.overrideDirective(SimpleDirective, {set: {providers}}); - const el = createComponent('
'); - expect(el.children[0].injector.get('injectable1')).toEqual([ - 'injectable11', 'injectable12' - ]); - }); - - it('should instantiate providers lazily', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective]}); - - let created = false; - TestBed.overrideDirective( - SimpleDirective, - {set: {providers: [{provide: 'service', useFactory: () => created = true}]}}); - const el = createComponent('
'); - - expect(created).toBe(false); - - el.children[0].injector.get('service'); - - expect(created).toBe(true); - }); - - it('should provide undefined', () => { - let factoryCounter = 0; - - const el = createComponent('', [{ - provide: 'token', - useFactory: () => { - factoryCounter++; - return undefined; - } - }]); - - expect(el.injector.get('token')).toBeUndefined(); - expect(el.injector.get('token')).toBeUndefined(); - expect(factoryCounter).toBe(1); - }); - - describe('injecting lazy providers into an eager provider via Injector.get', () => { - - it('should inject providers that were declared before it', () => { - @Component({ - template: '', - providers: [ - {provide: 'lazy', useFactory: () => 'lazyValue'}, - { - provide: 'eager', - useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`, - deps: [Injector] - }, - ] - }) - class MyComp { - // Component is eager, which makes all of its deps eager - constructor(@Inject('eager') eager: any) {} - } - - const ctx = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); - expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue'); - }); - - it('should inject providers that were declared after it', () => { - @Component({ - template: '', - providers: [ - { - provide: 'eager', - useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`, - deps: [Injector] - }, - {provide: 'lazy', useFactory: () => 'lazyValue'}, - ] - }) - class MyComp { - // Component is eager, which makes all of its deps eager - constructor(@Inject('eager') eager: any) {} - } - - const ctx = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); - expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue'); - }); - }); - - describe('injecting eager providers into an eager provider via Injector.get', () => { - it('should inject providers that were declared before it', () => { - @Component({ - template: '', - providers: [ - {provide: 'eager1', useFactory: () => 'v1'}, - { - provide: 'eager2', - useFactory: (i: Injector) => `v2: ${i.get('eager1')}`, - deps: [Injector] - }, - ] - }) - class MyComp { - // Component is eager, which makes all of its deps eager - constructor(@Inject('eager1') eager1: any, @Inject('eager2') eager2: any) {} - } - - const ctx = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); - expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1'); - }); - - it('should inject providers that were declared after it', () => { - @Component({ - template: '', - providers: [ - { - provide: 'eager1', - useFactory: (i: Injector) => `v1: ${i.get('eager2')}`, - deps: [Injector] - }, - {provide: 'eager2', useFactory: () => 'v2'}, - ] - }) - class MyComp { - // Component is eager, which makes all of its deps eager - constructor(@Inject('eager1') eager1: any, @Inject('eager2') eager2: any) {} - } - - const ctx = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); - expect(ctx.debugElement.injector.get('eager1')).toBe('v1: v2'); - }); - }); - - it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier', - () => { - @Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''}) - class SomeComponent { - public a: string; - constructor(injector: Injector) { this.a = injector.get('a'); } - } - - const comp = TestBed.configureTestingModule({declarations: [SomeComponent]}) - .createComponent(SomeComponent); - expect(comp.componentInstance.a).toBe('aValue'); - }); - - it('should support ngOnDestroy for lazy providers', () => { - let created = false; - let destroyed = false; - - class SomeInjectable { - constructor() { created = true; } - ngOnDestroy() { destroyed = true; } - } - - @Component({providers: [SomeInjectable], template: ''}) - class SomeComp { - } - - TestBed.configureTestingModule({declarations: [SomeComp]}); - - - let compRef = TestBed.createComponent(SomeComp).componentRef; - expect(created).toBe(false); - expect(destroyed).toBe(false); - - // no error if the provider was not yet created - compRef.destroy(); - expect(created).toBe(false); - expect(destroyed).toBe(false); - - compRef = TestBed.createComponent(SomeComp).componentRef; - compRef.injector.get(SomeInjectable); - expect(created).toBe(true); - compRef.destroy(); - expect(destroyed).toBe(true); - }); - - it('should instantiate view providers lazily', () => { - let created = false; - TestBed.configureTestingModule({declarations: [SimpleComponent]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {viewProviders: [{provide: 'service', useFactory: () => created = true}]}}); - const el = createComponent('
'); - - expect(created).toBe(false); - - el.children[0].injector.get('service'); - - expect(created).toBe(true); - }); - - it('should not instantiate other directives that depend on viewProviders providers (same element)', - () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {viewProviders: [{provide: 'service', useValue: 'service'}]}}); - expect(() => createComponent('
')) - .toThrowError(/No provider for service!/); - }); - - it('should not instantiate other directives that depend on viewProviders providers (child element)', - () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {viewProviders: [{provide: 'service', useValue: 'service'}]}}); - expect(() => createComponent('
')) - .toThrowError(/No provider for service!/); - }); - - it('should instantiate directives that depend on providers of other directives', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsService]}); - TestBed.overrideDirective( - SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}}); - - const el = createComponent('
'); - expect(el.children[0].children[0].injector.get(NeedsService).service) - .toEqual('parentService'); - }); - - it('should instantiate directives that depend on providers in a parent view', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsService]}); - TestBed.overrideDirective( - SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}}); - const el = createComponent( - '
'); - expect(el.children[0].children[0].injector.get(NeedsService).service) - .toEqual('parentService'); - }); - - it('should instantiate directives that depend on providers of a component', () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); - TestBed.overrideComponent( - SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent(SimpleComponent, {set: {template: '
'}}); - const el = createComponent('
'); - expect(el.children[0].children[0].injector.get(NeedsService).service) - .toEqual('hostService'); - }); - - it('should instantiate directives that depend on view providers of a component', () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); - TestBed.overrideComponent( - SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent(SimpleComponent, {set: {template: '
'}}); - const el = createComponent('
'); - expect(el.children[0].children[0].injector.get(NeedsService).service) - .toEqual('hostService'); - }); - - it('should instantiate directives in a root embedded view that depend on view providers of a component', - () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - const el = createComponent('
'); - expect(el.children[0].children[0].injector.get(NeedsService).service) - .toEqual('hostService'); - }); - - it('should instantiate directives that depend on instances in the app injector', () => { - TestBed.configureTestingModule({declarations: [NeedsAppService]}); - const el = createComponent('
'); - expect(el.children[0].injector.get(NeedsAppService).service).toEqual('appService'); - }); - - obsoleteInIvy('This error is no longer generated by the compiler') - .it('should not instantiate a directive with cyclic dependencies', () => { - TestBed.configureTestingModule({declarations: [CycleDirective]}); - expect(() => createComponent('
')) - .toThrowError( - /Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective \("\[ERROR ->\]
<\/div>"\): .*TestComp.html@0:0/); - }); - - onlyInIvy('This error is generated by the runtime of Ivy') - .it('should not instantiate a directive with cyclic dependencies', () => { - TestBed.configureTestingModule({declarations: [CycleDirective]}); - expect(() => createComponent('
')) - .toThrowError('Circular dep for CycleDirective'); - }); - - obsoleteInIvy('This error is no longer generated by the compiler') - .it('should not instantiate a directive in a view that has a host dependency on providers' + - ' of the component', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, NeedsServiceFromHost]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - - expect(() => createComponent('
')) - .toThrowError( - /Template parse errors:\nNo provider for service \("\[ERROR ->\]
"\): .*SimpleComponent.html@0:0/); - }); - - onlyInIvy('This error is generated by the runtime of Ivy') - .it('should not instantiate a directive in a view that has a host dependency on providers' + - ' of the component', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, NeedsServiceFromHost]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - - expect(() => createComponent('
')) - .toThrowError('NodeInjector: NOT_FOUND [service]'); - }); - - obsoleteInIvy('This error is no longer generated by the compiler') - .it('should not instantiate a directive in a view that has a host dependency on providers' + - ' of a decorator directive', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - - expect(() => createComponent('
')) - .toThrowError( - /Template parse errors:\nNo provider for service \("\[ERROR ->\]
"\): .*SimpleComponent.html@0:0/); - }); - - onlyInIvy('This error is generated by the runtime of Ivy') - .it('should not instantiate a directive in a view that has a host dependency on providers' + - ' of a decorator directive', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]}); - TestBed.overrideComponent( - SimpleComponent, - {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - - expect(() => createComponent('
')) - .toThrowError('NodeInjector: NOT_FOUND [service]'); - }); - - obsoleteInIvy('This error is no longer generated by the compiler') - .it('should not instantiate a directive in a view that has a self dependency on a parent directive', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleDirective, NeedsDirectiveFromSelf]}); - expect( - () => createComponent( - '
')) - .toThrowError( - /Template parse errors:\nNo provider for SimpleDirective \("
\[ERROR ->\]
<\/div><\/div>"\): .*TestComp.html@0:21/); - }); - - onlyInIvy('This error is generated by the runtime of Ivy') - .it('should not instantiate a directive in a view that has a self dependency on a parent directive', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleDirective, NeedsDirectiveFromSelf]}); - expect( - () => createComponent( - '
')) - .toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]'); - }); - - it('should instantiate directives that depend on other directives', fakeAsync(() => { - TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]}); - const el = createComponent('
'); - const d = el.children[0].children[0].injector.get(NeedsDirective); - - expect(d).toBeAnInstanceOf(NeedsDirective); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - })); - - it('should throw when a dependency cannot be resolved', fakeAsync(() => { - TestBed.configureTestingModule({declarations: [NeedsService]}); - - expect(() => createComponent('
')) - .toThrowError(/No provider for service!/); - })); - - it('should inject null when an optional dependency cannot be resolved', () => { - TestBed.configureTestingModule({declarations: [OptionallyNeedsDirective]}); - const el = createComponent('
'); - const d = el.children[0].injector.get(OptionallyNeedsDirective); - expect(d.dependency).toBeNull(); - }); - - it('should instantiate directives that depends on the host component', () => { - TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsComponentFromHost]}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - const el = createComponent('
'); - const d = el.children[0].children[0].injector.get(NeedsComponentFromHost); - expect(d.dependency).toBeAnInstanceOf(SimpleComponent); - }); - - obsoleteInIvy('@Host() / @Self() no longer looks in module injector') - .it('should instantiate host views for components that have a @Host dependency ', () => { - TestBed.configureTestingModule({declarations: [NeedsHostAppService]}); - const el = createComponent('', [], NeedsHostAppService); - expect(el.componentInstance.service).toEqual('appService'); - }); - - obsoleteInIvy('This error is no longer generated by the compiler') - .it('should not instantiate directives that depend on other directives on the host element', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - expect(() => createComponent('
')) - .toThrowError( - /Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]
<\/div>"\): .*SimpleComponent.html@0:0/); - }); - - onlyInIvy('This error is generated by the runtime of Ivy') - .it('should not instantiate directives that depend on other directives on the host element', - () => { - TestBed.configureTestingModule( - {declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]}); - TestBed.overrideComponent( - SimpleComponent, {set: {template: '
'}}); - expect(() => createComponent('
')) - .toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]'); - }); - - it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector', - () => { - @Component({template: ''}) - class MyComp { - constructor(public vc: ViewContainerRef) {} - } - - const compFixture = TestBed - .configureTestingModule({ - declarations: [MyComp], - providers: [{provide: 'someToken', useValue: 'someValue'}] - }) - .createComponent(MyComp); - - expect(compFixture.componentInstance.vc.parentInjector.get('someToken')) - .toBe('someValue'); - }); + expect(d).toBeAnInstanceOf(NeedsDirective); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); }); - describe('static attributes', () => { - it('should be injectable', () => { - TestBed.configureTestingModule({declarations: [NeedsAttribute]}); - const el = createComponent('
'); - const needsAttribute = el.children[0].injector.get(NeedsAttribute); - - expect(needsAttribute.typeAttribute).toEqual('text'); - expect(needsAttribute.titleAttribute).toEqual(''); - expect(needsAttribute.fooAttribute).toEqual(null); - }); - - it('should be injectable without type annotation', () => { - TestBed.configureTestingModule({declarations: [NeedsAttributeNoType]}); - const el = createComponent('
'); - const needsAttribute = el.children[0].injector.get(NeedsAttributeNoType); - - expect(needsAttribute.fooAttribute).toEqual('bar'); - }); + it('should support useValue with different values', () => { + const el = createComponent('', [ + {provide: 'numLiteral', useValue: 0}, + {provide: 'boolLiteral', useValue: true}, + {provide: 'strLiteral', useValue: 'a'}, + {provide: 'null', useValue: null}, + {provide: 'array', useValue: [1]}, + {provide: 'map', useValue: {'a': 1}}, + {provide: 'instance', useValue: new TestValue('a')}, + {provide: 'nested', useValue: [{'a': [1]}, new TestValue('b')]}, + ]); + expect(el.injector.get('numLiteral')).toBe(0); + expect(el.injector.get('boolLiteral')).toBe(true); + expect(el.injector.get('strLiteral')).toBe('a'); + expect(el.injector.get('null')).toBe(null); + expect(el.injector.get('array')).toEqual([1]); + expect(el.injector.get('map')).toEqual({'a': 1}); + expect(el.injector.get('instance')).toEqual(new TestValue('a')); + expect(el.injector.get('nested')).toEqual([{'a': [1]}, new TestValue('b')]); }); - describe('refs', () => { - it('should inject ElementRef', () => { - TestBed.configureTestingModule({declarations: [NeedsElementRef]}); - const el = createComponent('
'); - expect(el.children[0].injector.get(NeedsElementRef).elementRef.nativeElement) - .toBe(el.children[0].nativeElement); - }); - - it('should inject ChangeDetectorRef of the component\'s view into the component', () => { - TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); - const cf = createComponentFixture('
'); - cf.detectChanges(); - const compEl = cf.debugElement.children[0]; - const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); - comp.counter = 1; - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - comp.changeDetectorRef.markForCheck(); - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('1'); - }); - - it('should inject ChangeDetectorRef of the containing component into directives', () => { - TestBed.configureTestingModule( - {declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); - TestBed.overrideComponent(PushComponentNeedsChangeDetectorRef, { - set: { - template: - '{{counter}}
' - } - }); - const cf = createComponentFixture('
'); - cf.detectChanges(); - const compEl = cf.debugElement.children[0]; - const comp: PushComponentNeedsChangeDetectorRef = - compEl.injector.get(PushComponentNeedsChangeDetectorRef); - comp.counter = 1; - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - - /** - * Compares two `ChangeDetectorRef` instances. The logic depends on the engine, as the - * implementation details of `ViewRef` vary. - */ - function _compareChangeDetectorRefs(a: ChangeDetectorRef, b: ChangeDetectorRef) { - if (!ivyEnabled) { - // View Engine case - expect(a).toEqual(b); - } else { - // Ivy case - expect((a as any)._lView).toEqual((b as any)._lView); - expect((a as any).context).toEqual((b as any).context); - } + it('should instantiate providers that have dependencies with SkipSelf', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, SomeOtherDirective]}); + TestBed.overrideDirective( + SimpleDirective, {add: {providers: [{provide: 'injectable1', useValue: 'injectable1'}]}}); + TestBed.overrideDirective(SomeOtherDirective, { + add: { + providers: [ + {provide: 'injectable1', useValue: 'new-injectable1'}, { + provide: 'injectable2', + useFactory: (val: any) => `${val}-injectable2`, + deps: [[new Inject('injectable1'), new SkipSelf()]] + } + ] } - - _compareChangeDetectorRefs( - compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef, - comp.changeDetectorRef); - _compareChangeDetectorRefs( - compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef, - comp.changeDetectorRef); - - comp.changeDetectorRef.markForCheck(); - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('1'); }); + const el = createComponent('
'); + expect(el.children[0].children[0].injector.get('injectable2')) + .toEqual('injectable1-injectable2'); + }); - it('should inject ChangeDetectorRef of a same element component into a directive', () => { - TestBed.configureTestingModule( - {declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); - const cf = createComponentFixture( - '
'); - cf.detectChanges(); - const compEl = cf.debugElement.children[0]; - const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); - const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef); - comp.counter = 1; - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - dir.changeDetectorRef.markForCheck(); - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('1'); - }); - - it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => { - TestBed - .configureTestingModule({ - declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] - }) - .overrideComponent( - PushComponentNeedsChangeDetectorRef, - {set: {template: '{{counter}}'}}); - const cf = createComponentFixture( - '
'); - cf.detectChanges(); - const compEl = cf.debugElement.children[0]; - const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); - const dirEl = compEl.children[0]; - const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef); - comp.counter = 1; - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - dir.changeDetectorRef.markForCheck(); - cf.detectChanges(); - expect(compEl.nativeElement).toHaveText('0'); - }); - - it('should inject ViewContainerRef', () => { - TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]}); - const el = createComponent('
'); - expect( - el.children[0].injector.get(NeedsViewContainerRef).viewContainer.element.nativeElement) - .toBe(el.children[0].nativeElement); - }); - - it('should inject ViewContainerRef', () => { - @Component({template: ''}) - class TestComp { - constructor(public vcr: ViewContainerRef) {} + it('should instantiate providers that have dependencies', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective]}); + const providers = [ + {provide: 'injectable1', useValue: 'injectable1'}, { + provide: 'injectable2', + useFactory: (val: any) => `${val}-injectable2`, + deps: ['injectable1'] } + ]; + TestBed.overrideDirective(SimpleDirective, {add: {providers}}); + const el = createComponent('
'); + expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); + }); - @NgModule({ - declarations: [TestComp], - entryComponents: [TestComp], + it('should instantiate viewProviders that have dependencies', () => { + TestBed.configureTestingModule({declarations: [SimpleComponent]}); + const viewProviders = [ + {provide: 'injectable1', useValue: 'injectable1'}, { + provide: 'injectable2', + useFactory: (val: any) => `${val}-injectable2`, + deps: ['injectable1'] + } + ]; + TestBed.overrideComponent(SimpleComponent, {set: {viewProviders}}); + const el = createComponent('
'); + expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); + }); + + it('should instantiate components that depend on viewProviders providers', () => { + TestBed.configureTestingModule({declarations: [NeedsServiceComponent]}); + TestBed.overrideComponent( + NeedsServiceComponent, {set: {providers: [{provide: 'service', useValue: 'service'}]}}); + const el = createComponent('
'); + expect(el.children[0].injector.get(NeedsServiceComponent).service).toEqual('service'); + }); + + it('should instantiate multi providers', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective]}); + const providers = [ + {provide: 'injectable1', useValue: 'injectable11', multi: true}, + {provide: 'injectable1', useValue: 'injectable12', multi: true} + ]; + TestBed.overrideDirective(SimpleDirective, {set: {providers}}); + const el = createComponent('
'); + expect(el.children[0].injector.get('injectable1')).toEqual(['injectable11', 'injectable12']); + }); + + it('should instantiate providers lazily', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective]}); + + let created = false; + TestBed.overrideDirective( + SimpleDirective, + {set: {providers: [{provide: 'service', useFactory: () => created = true}]}}); + const el = createComponent('
'); + + expect(created).toBe(false); + + el.children[0].injector.get('service'); + + expect(created).toBe(true); + }); + + it('should provide undefined', () => { + let factoryCounter = 0; + + const el = createComponent('', [{ + provide: 'token', + useFactory: () => { + factoryCounter++; + return undefined; + } + }]); + + expect(el.injector.get('token')).toBeUndefined(); + expect(el.injector.get('token')).toBeUndefined(); + expect(factoryCounter).toBe(1); + }); + + describe('injecting lazy providers into an eager provider via Injector.get', () => { + + it('should inject providers that were declared before it', () => { + @Component({ + template: '', + providers: [ + {provide: 'lazy', useFactory: () => 'lazyValue'}, + { + provide: 'eager', + useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`, + deps: [Injector] + }, + ] }) - class TestModule { + class MyComp { + // Component is eager, which makes all of its deps eager + constructor(@Inject('eager') eager: any) {} } - const testInjector = { - get: (token: any, notFoundValue: any) => - token === 'someToken' ? 'someNewValue' : notFoundValue - }; - - const compFactory = TestBed.configureTestingModule({imports: [TestModule]}) - .get(ComponentFactoryResolver) - .resolveComponentFactory(TestComp); - const component = compFactory.create(testInjector); - expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue'); + const ctx = + TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue'); }); - it('should inject TemplateRef', () => { - TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]}); - const el = - createComponent(''); - expect(el.childNodes[0].injector.get(NeedsTemplateRef).templateRef.elementRef) - .toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element); - }); + it('should inject providers that were declared after it', () => { + @Component({ + template: '', + providers: [ + { + provide: 'eager', + useFactory: (i: Injector) => `eagerValue: ${i.get('lazy')}`, + deps: [Injector] + }, + {provide: 'lazy', useFactory: () => 'lazyValue'}, + ] + }) + class MyComp { + // Component is eager, which makes all of its deps eager + constructor(@Inject('eager') eager: any) {} + } - it('should throw if there is no TemplateRef', () => { - TestBed.configureTestingModule({declarations: [NeedsTemplateRef]}); - expect(() => createComponent('
')) - .toThrowError(/No provider for TemplateRef!/); - }); - - it('should inject null if there is no TemplateRef when the dependency is optional', () => { - TestBed.configureTestingModule({declarations: [OptionallyNeedsTemplateRef]}); - const el = createComponent('
'); - const instance = el.children[0].injector.get(OptionallyNeedsTemplateRef); - expect(instance.templateRef).toBeNull(); + const ctx = + TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + expect(ctx.debugElement.injector.get('eager')).toBe('eagerValue: lazyValue'); }); }); - describe('pipes', () => { - it('should instantiate pipes that have dependencies', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]}); + describe('injecting eager providers into an eager provider via Injector.get', () => { + it('should inject providers that were declared before it', () => { + @Component({ + template: '', + providers: [ + {provide: 'eager1', useFactory: () => 'v1'}, + { + provide: 'eager2', + useFactory: (i: Injector) => `v2: ${i.get('eager1')}`, + deps: [Injector] + }, + ] + }) + class MyComp { + // Component is eager, which makes all of its deps eager + constructor(@Inject('eager1') eager1: any, @Inject('eager2') eager2: any) {} + } - const el = createComponent( - '
', - [{provide: 'service', useValue: 'pipeService'}]); - expect(el.children[0].injector.get(SimpleDirective).value.service).toEqual('pipeService'); + const ctx = + TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1'); }); - it('should overwrite pipes with later entry in the pipes array', () => { - TestBed.configureTestingModule( - {declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]}); - const el = createComponent('
'); - expect(el.children[0].injector.get(SimpleDirective).value).toBeAnInstanceOf(DuplicatePipe2); - }); + it('should inject providers that were declared after it', () => { + @Component({ + template: '', + providers: [ + { + provide: 'eager1', + useFactory: (i: Injector) => `v1: ${i.get('eager2')}`, + deps: [Injector] + }, + {provide: 'eager2', useFactory: () => 'v2'}, + ] + }) + class MyComp { + // Component is eager, which makes all of its deps eager + constructor(@Inject('eager1') eager1: any, @Inject('eager2') eager2: any) {} + } - it('should inject ChangeDetectorRef into pipes', () => { - TestBed.configureTestingModule({ - declarations: - [SimpleDirective, PipeNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] + const ctx = + TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + expect(ctx.debugElement.injector.get('eager1')).toBe('v1: v2'); + }); + }); + + it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier', + () => { + @Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''}) + class SomeComponent { + public a: string; + constructor(injector: Injector) { this.a = injector.get('a'); } + } + + const comp = TestBed.configureTestingModule({declarations: [SomeComponent]}) + .createComponent(SomeComponent); + expect(comp.componentInstance.a).toBe('aValue'); + }); + + it('should support ngOnDestroy for lazy providers', () => { + let created = false; + let destroyed = false; + + class SomeInjectable { + constructor() { created = true; } + ngOnDestroy() { destroyed = true; } + } + + @Component({providers: [SomeInjectable], template: ''}) + class SomeComp { + } + + TestBed.configureTestingModule({declarations: [SomeComp]}); + + + let compRef = TestBed.createComponent(SomeComp).componentRef; + expect(created).toBe(false); + expect(destroyed).toBe(false); + + // no error if the provider was not yet created + compRef.destroy(); + expect(created).toBe(false); + expect(destroyed).toBe(false); + + compRef = TestBed.createComponent(SomeComp).componentRef; + compRef.injector.get(SomeInjectable); + expect(created).toBe(true); + compRef.destroy(); + expect(destroyed).toBe(true); + }); + + it('should instantiate view providers lazily', () => { + let created = false; + TestBed.configureTestingModule({declarations: [SimpleComponent]}); + TestBed.overrideComponent( + SimpleComponent, + {set: {viewProviders: [{provide: 'service', useFactory: () => created = true}]}}); + const el = createComponent('
'); + + expect(created).toBe(false); + + el.children[0].injector.get('service'); + + expect(created).toBe(true); + }); + + it('should not instantiate other directives that depend on viewProviders providers (same element)', + () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); + TestBed.overrideComponent( + SimpleComponent, {set: {viewProviders: [{provide: 'service', useValue: 'service'}]}}); + expect(() => createComponent('
')) + .toThrowError(/No provider for service!/); + }); + + it('should not instantiate other directives that depend on viewProviders providers (child element)', + () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); + TestBed.overrideComponent( + SimpleComponent, {set: {viewProviders: [{provide: 'service', useValue: 'service'}]}}); + expect(() => createComponent('
')) + .toThrowError(/No provider for service!/); + }); + + it('should instantiate directives that depend on providers of other directives', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsService]}); + TestBed.overrideDirective( + SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}}); + + const el = createComponent('
'); + expect(el.children[0].children[0].injector.get(NeedsService).service) + .toEqual('parentService'); + }); + + it('should instantiate directives that depend on providers in a parent view', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsService]}); + TestBed.overrideDirective( + SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}}); + const el = createComponent( + '
'); + expect(el.children[0].children[0].injector.get(NeedsService).service) + .toEqual('parentService'); + }); + + it('should instantiate directives that depend on providers of a component', () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); + TestBed.overrideComponent( + SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent(SimpleComponent, {set: {template: '
'}}); + const el = createComponent('
'); + expect(el.children[0].children[0].injector.get(NeedsService).service).toEqual('hostService'); + }); + + it('should instantiate directives that depend on view providers of a component', () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); + TestBed.overrideComponent( + SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent(SimpleComponent, {set: {template: '
'}}); + const el = createComponent('
'); + expect(el.children[0].children[0].injector.get(NeedsService).service).toEqual('hostService'); + }); + + it('should instantiate directives in a root embedded view that depend on view providers of a component', + () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); + TestBed.overrideComponent( + SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + const el = createComponent('
'); + expect(el.children[0].children[0].injector.get(NeedsService).service) + .toEqual('hostService'); + }); + + it('should instantiate directives that depend on instances in the app injector', () => { + TestBed.configureTestingModule({declarations: [NeedsAppService]}); + const el = createComponent('
'); + expect(el.children[0].injector.get(NeedsAppService).service).toEqual('appService'); + }); + + obsoleteInIvy('This error is no longer generated by the compiler') + .it('should not instantiate a directive with cyclic dependencies', () => { + TestBed.configureTestingModule({declarations: [CycleDirective]}); + expect(() => createComponent('
')) + .toThrowError( + /Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective \("\[ERROR ->\]
<\/div>"\): .*TestComp.html@0:0/); }); - const el = createComponent( - '
'); - const cdRef = - el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef; - expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef); - }); - modifiedInIvy('Pure pipes are instantiated differently in view engine and ivy') - .it('should cache pure pipes', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]}); - const el = createComponent( - '
' + - '
'); - const purePipe1 = el.children[0].injector.get(SimpleDirective).value; - const purePipe2 = el.children[1].injector.get(SimpleDirective).value; - const purePipe3 = el.children[2].injector.get(SimpleDirective).value; - const purePipe4 = el.children[3].injector.get(SimpleDirective).value; - expect(purePipe1).toBeAnInstanceOf(PurePipe); - expect(purePipe2).toBe(purePipe1); - expect(purePipe3).toBe(purePipe1); - expect(purePipe4).toBe(purePipe1); - }); + onlyInIvy('This error is generated by the runtime of Ivy') + .it('should not instantiate a directive with cyclic dependencies', () => { + TestBed.configureTestingModule({declarations: [CycleDirective]}); + expect(() => createComponent('
')) + .toThrowError('Circular dep for CycleDirective'); + }); - it('should not cache impure pipes', () => { - TestBed.configureTestingModule({declarations: [SimpleDirective, ImpurePipe]}); - const el = createComponent( - '
' + - '
'); - const impurePipe1 = el.children[0].injector.get(SimpleDirective).value; - const impurePipe2 = el.children[1].injector.get(SimpleDirective).value; - const impurePipe3 = el.children[2].injector.get(SimpleDirective).value; - const impurePipe4 = el.children[3].injector.get(SimpleDirective).value; - expect(impurePipe1).toBeAnInstanceOf(ImpurePipe); - expect(impurePipe2).toBeAnInstanceOf(ImpurePipe); - expect(impurePipe2).not.toBe(impurePipe1); - expect(impurePipe3).toBeAnInstanceOf(ImpurePipe); - expect(impurePipe3).not.toBe(impurePipe1); - expect(impurePipe4).toBeAnInstanceOf(ImpurePipe); - expect(impurePipe4).not.toBe(impurePipe1); - }); + obsoleteInIvy('This error is no longer generated by the compiler') + .it('should not instantiate a directive in a view that has a host dependency on providers' + + ' of the component', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, NeedsServiceFromHost]}); + TestBed.overrideComponent( + SimpleComponent, + {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + + expect(() => createComponent('
')) + .toThrowError( + /Template parse errors:\nNo provider for service \("\[ERROR ->\]
"\): .*SimpleComponent.html@0:0/); + }); + + onlyInIvy('This error is generated by the runtime of Ivy') + .it('should not instantiate a directive in a view that has a host dependency on providers' + + ' of the component', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, NeedsServiceFromHost]}); + TestBed.overrideComponent( + SimpleComponent, + {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + + expect(() => createComponent('
')) + .toThrowError('NodeInjector: NOT_FOUND [service]'); + }); + + obsoleteInIvy('This error is no longer generated by the compiler') + .it('should not instantiate a directive in a view that has a host dependency on providers' + + ' of a decorator directive', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]}); + TestBed.overrideComponent( + SimpleComponent, + {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + + expect(() => createComponent('
')) + .toThrowError( + /Template parse errors:\nNo provider for service \("\[ERROR ->\]
"\): .*SimpleComponent.html@0:0/); + }); + + onlyInIvy('This error is generated by the runtime of Ivy') + .it('should not instantiate a directive in a view that has a host dependency on providers' + + ' of a decorator directive', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]}); + TestBed.overrideComponent( + SimpleComponent, + {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + + expect(() => createComponent('
')) + .toThrowError('NodeInjector: NOT_FOUND [service]'); + }); + + obsoleteInIvy('This error is no longer generated by the compiler') + .it('should not instantiate a directive in a view that has a self dependency on a parent directive', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleDirective, NeedsDirectiveFromSelf]}); + expect( + () => createComponent( + '
')) + .toThrowError( + /Template parse errors:\nNo provider for SimpleDirective \("
\[ERROR ->\]
<\/div><\/div>"\): .*TestComp.html@0:21/); + }); + + onlyInIvy('This error is generated by the runtime of Ivy') + .it('should not instantiate a directive in a view that has a self dependency on a parent directive', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleDirective, NeedsDirectiveFromSelf]}); + expect( + () => createComponent( + '
')) + .toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]'); + }); + + it('should instantiate directives that depend on other directives', fakeAsync(() => { + TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]}); + const el = createComponent('
'); + const d = el.children[0].children[0].injector.get(NeedsDirective); + + expect(d).toBeAnInstanceOf(NeedsDirective); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + })); + + it('should throw when a dependency cannot be resolved', fakeAsync(() => { + TestBed.configureTestingModule({declarations: [NeedsService]}); + + expect(() => createComponent('
')) + .toThrowError(/No provider for service!/); + })); + + it('should inject null when an optional dependency cannot be resolved', () => { + TestBed.configureTestingModule({declarations: [OptionallyNeedsDirective]}); + const el = createComponent('
'); + const d = el.children[0].injector.get(OptionallyNeedsDirective); + expect(d.dependency).toBeNull(); }); - describe('view destruction', () => { - @Component({selector: 'some-component', template: ''}) - class SomeComponent { + it('should instantiate directives that depends on the host component', () => { + TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsComponentFromHost]}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + const el = createComponent('
'); + const d = el.children[0].children[0].injector.get(NeedsComponentFromHost); + expect(d.dependency).toBeAnInstanceOf(SimpleComponent); + }); + + obsoleteInIvy('@Host() / @Self() no longer looks in module injector') + .it('should instantiate host views for components that have a @Host dependency ', () => { + TestBed.configureTestingModule({declarations: [NeedsHostAppService]}); + const el = createComponent('', [], NeedsHostAppService); + expect(el.componentInstance.service).toEqual('appService'); + }); + + obsoleteInIvy('This error is no longer generated by the compiler') + .it('should not instantiate directives that depend on other directives on the host element', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + expect(() => createComponent('
')) + .toThrowError( + /Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]
<\/div>"\): .*SimpleComponent.html@0:0/); + }); + + onlyInIvy('This error is generated by the runtime of Ivy') + .it('should not instantiate directives that depend on other directives on the host element', + () => { + TestBed.configureTestingModule( + {declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]}); + TestBed.overrideComponent( + SimpleComponent, {set: {template: '
'}}); + expect(() => createComponent('
')) + .toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]'); + }); + + it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector', + () => { + @Component({template: ''}) + class MyComp { + constructor(public vc: ViewContainerRef) {} + } + + const compFixture = TestBed + .configureTestingModule({ + declarations: [MyComp], + providers: [{provide: 'someToken', useValue: 'someValue'}] + }) + .createComponent(MyComp); + + expect(compFixture.componentInstance.vc.parentInjector.get('someToken')).toBe('someValue'); + }); + }); + + describe('static attributes', () => { + it('should be injectable', () => { + TestBed.configureTestingModule({declarations: [NeedsAttribute]}); + const el = createComponent('
'); + const needsAttribute = el.children[0].injector.get(NeedsAttribute); + + expect(needsAttribute.typeAttribute).toEqual('text'); + expect(needsAttribute.titleAttribute).toEqual(''); + expect(needsAttribute.fooAttribute).toEqual(null); + }); + + it('should be injectable without type annotation', () => { + TestBed.configureTestingModule({declarations: [NeedsAttributeNoType]}); + const el = createComponent('
'); + const needsAttribute = el.children[0].injector.get(NeedsAttributeNoType); + + expect(needsAttribute.fooAttribute).toEqual('bar'); + }); + }); + + describe('refs', () => { + it('should inject ElementRef', () => { + TestBed.configureTestingModule({declarations: [NeedsElementRef]}); + const el = createComponent('
'); + expect(el.children[0].injector.get(NeedsElementRef).elementRef.nativeElement) + .toBe(el.children[0].nativeElement); + }); + + it('should inject ChangeDetectorRef of the component\'s view into the component', () => { + TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); + const cf = createComponentFixture('
'); + cf.detectChanges(); + const compEl = cf.debugElement.children[0]; + const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); + comp.counter = 1; + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + comp.changeDetectorRef.markForCheck(); + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('1'); + }); + + it('should inject ChangeDetectorRef of the containing component into directives', () => { + TestBed.configureTestingModule( + {declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); + TestBed.overrideComponent(PushComponentNeedsChangeDetectorRef, { + set: { + template: + '{{counter}}
' + } + }); + const cf = createComponentFixture('
'); + cf.detectChanges(); + const compEl = cf.debugElement.children[0]; + const comp: PushComponentNeedsChangeDetectorRef = + compEl.injector.get(PushComponentNeedsChangeDetectorRef); + comp.counter = 1; + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + + /** + * Compares two `ChangeDetectorRef` instances. The logic depends on the engine, as the + * implementation details of `ViewRef` vary. + */ + function _compareChangeDetectorRefs(a: ChangeDetectorRef, b: ChangeDetectorRef) { + if (!ivyEnabled) { + // View Engine case + expect(a).toEqual(b); + } else { + // Ivy case + expect((a as any)._lView).toEqual((b as any)._lView); + expect((a as any).context).toEqual((b as any).context); + } + } + + _compareChangeDetectorRefs( + compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef, + comp.changeDetectorRef); + _compareChangeDetectorRefs( + compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef, + comp.changeDetectorRef); + + comp.changeDetectorRef.markForCheck(); + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('1'); + }); + + it('should inject ChangeDetectorRef of a same element component into a directive', () => { + TestBed.configureTestingModule( + {declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); + const cf = createComponentFixture( + '
'); + cf.detectChanges(); + const compEl = cf.debugElement.children[0]; + const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); + const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef); + comp.counter = 1; + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + dir.changeDetectorRef.markForCheck(); + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('1'); + }); + + it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => { + TestBed + .configureTestingModule({ + declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] + }) + .overrideComponent( + PushComponentNeedsChangeDetectorRef, + {set: {template: '{{counter}}'}}); + const cf = createComponentFixture( + '
'); + cf.detectChanges(); + const compEl = cf.debugElement.children[0]; + const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); + const dirEl = compEl.children[0]; + const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef); + comp.counter = 1; + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + dir.changeDetectorRef.markForCheck(); + cf.detectChanges(); + expect(compEl.nativeElement).toHaveText('0'); + }); + + it('should inject ViewContainerRef', () => { + TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]}); + const el = createComponent('
'); + expect(el.children[0].injector.get(NeedsViewContainerRef).viewContainer.element.nativeElement) + .toBe(el.children[0].nativeElement); + }); + + it('should inject ViewContainerRef', () => { + @Component({template: ''}) + class TestComp { + constructor(public vcr: ViewContainerRef) {} } @NgModule({ - declarations: [SomeComponent], - exports: [SomeComponent], - entryComponents: [SomeComponent] + declarations: [TestComp], + entryComponents: [TestComp], }) - class SomeModule { + class TestModule { } - @Component({selector: 'listener-and-on-destroy', template: ''}) - class ComponentThatLoadsAnotherComponentThenMovesIt { - constructor( - private viewContainerRef: ViewContainerRef, - private componentFactoryResolver: ComponentFactoryResolver) {} + const testInjector = { + get: (token: any, notFoundValue: any) => + token === 'someToken' ? 'someNewValue' : notFoundValue + }; + const compFactory = TestBed.configureTestingModule({imports: [TestModule]}) + .get(ComponentFactoryResolver) + .resolveComponentFactory(TestComp); + const component = compFactory.create(testInjector); + expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue'); + }); - ngOnInit() { - // Dynamically load some component. - const componentFactory = - this.componentFactoryResolver.resolveComponentFactory(SomeComponent); - const componentRef = - this.viewContainerRef.createComponent(componentFactory, this.viewContainerRef.length); + it('should inject TemplateRef', () => { + TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]}); + const el = + createComponent(''); + expect(el.childNodes[0].injector.get(NeedsTemplateRef).templateRef.elementRef) + .toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element); + }); - // Manually move the loaded component to some arbitrary DOM node. - const componentRootNode = - (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; - document.createElement('div').appendChild(componentRootNode); + it('should throw if there is no TemplateRef', () => { + TestBed.configureTestingModule({declarations: [NeedsTemplateRef]}); + expect(() => createComponent('
')) + .toThrowError(/No provider for TemplateRef!/); + }); - // Destroy the component we just moved to ensure that it does not error during - // destruction. - componentRef.destroy(); - } - } - - it('should not error when destroying a component that has been moved in the DOM', () => { - TestBed.configureTestingModule({ - imports: [SomeModule], - declarations: [ComponentThatLoadsAnotherComponentThenMovesIt], - }); - const fixture = - createComponentFixture(``); - fixture.detectChanges(); - - // This test will fail if the ngOnInit of ComponentThatLoadsAnotherComponentThenMovesIt - // throws an error. - }); + it('should inject null if there is no TemplateRef when the dependency is optional', () => { + TestBed.configureTestingModule({declarations: [OptionallyNeedsTemplateRef]}); + const el = createComponent('
'); + const instance = el.children[0].injector.get(OptionallyNeedsTemplateRef); + expect(instance.templateRef).toBeNull(); }); }); -})(); + + describe('pipes', () => { + it('should instantiate pipes that have dependencies', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]}); + + const el = createComponent( + '
', + [{provide: 'service', useValue: 'pipeService'}]); + expect(el.children[0].injector.get(SimpleDirective).value.service).toEqual('pipeService'); + }); + + it('should overwrite pipes with later entry in the pipes array', () => { + TestBed.configureTestingModule( + {declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]}); + const el = createComponent('
'); + expect(el.children[0].injector.get(SimpleDirective).value).toBeAnInstanceOf(DuplicatePipe2); + }); + + it('should inject ChangeDetectorRef into pipes', () => { + TestBed.configureTestingModule({ + declarations: + [SimpleDirective, PipeNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] + }); + const el = createComponent( + '
'); + const cdRef = el.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef; + expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef); + }); + + modifiedInIvy('Pure pipes are instantiated differently in view engine and ivy') + .it('should cache pure pipes', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]}); + const el = createComponent( + '
' + + '
'); + const purePipe1 = el.children[0].injector.get(SimpleDirective).value; + const purePipe2 = el.children[1].injector.get(SimpleDirective).value; + const purePipe3 = el.children[2].injector.get(SimpleDirective).value; + const purePipe4 = el.children[3].injector.get(SimpleDirective).value; + expect(purePipe1).toBeAnInstanceOf(PurePipe); + expect(purePipe2).toBe(purePipe1); + expect(purePipe3).toBe(purePipe1); + expect(purePipe4).toBe(purePipe1); + }); + + it('should not cache impure pipes', () => { + TestBed.configureTestingModule({declarations: [SimpleDirective, ImpurePipe]}); + const el = createComponent( + '
' + + '
'); + const impurePipe1 = el.children[0].injector.get(SimpleDirective).value; + const impurePipe2 = el.children[1].injector.get(SimpleDirective).value; + const impurePipe3 = el.children[2].injector.get(SimpleDirective).value; + const impurePipe4 = el.children[3].injector.get(SimpleDirective).value; + expect(impurePipe1).toBeAnInstanceOf(ImpurePipe); + expect(impurePipe2).toBeAnInstanceOf(ImpurePipe); + expect(impurePipe2).not.toBe(impurePipe1); + expect(impurePipe3).toBeAnInstanceOf(ImpurePipe); + expect(impurePipe3).not.toBe(impurePipe1); + expect(impurePipe4).toBeAnInstanceOf(ImpurePipe); + expect(impurePipe4).not.toBe(impurePipe1); + }); + }); + + describe('view destruction', () => { + @Component({selector: 'some-component', template: ''}) + class SomeComponent { + } + + @NgModule( + {declarations: [SomeComponent], exports: [SomeComponent], entryComponents: [SomeComponent]}) + class SomeModule { + } + + @Component({selector: 'listener-and-on-destroy', template: ''}) + class ComponentThatLoadsAnotherComponentThenMovesIt { + constructor( + private viewContainerRef: ViewContainerRef, + private componentFactoryResolver: ComponentFactoryResolver) {} + + + ngOnInit() { + // Dynamically load some component. + const componentFactory = + this.componentFactoryResolver.resolveComponentFactory(SomeComponent); + const componentRef = + this.viewContainerRef.createComponent(componentFactory, this.viewContainerRef.length); + + // Manually move the loaded component to some arbitrary DOM node. + const componentRootNode = + (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; + document.createElement('div').appendChild(componentRootNode); + + // Destroy the component we just moved to ensure that it does not error during + // destruction. + componentRef.destroy(); + } + } + + it('should not error when destroying a component that has been moved in the DOM', () => { + TestBed.configureTestingModule({ + imports: [SomeModule], + declarations: [ComponentThatLoadsAnotherComponentThenMovesIt], + }); + const fixture = createComponentFixture(``); + fixture.detectChanges(); + + // This test will fail if the ngOnInit of ComponentThatLoadsAnotherComponentThenMovesIt + // throws an error. + }); + }); +}); class TestValue { constructor(public value: string) {}