diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 6df8bb54a7..2c4f516c10 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, HostBinding, INJECTOR, Inject, Injectable, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef} from '@angular/core'; +import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef} from '@angular/core'; import {ViewRef} from '@angular/core/src/render3/view_ref'; import {TestBed} from '@angular/core/testing'; import {onlyInIvy} from '@angular/private/testing'; @@ -38,6 +38,7 @@ describe('di', () => { @Directive({selector: '[dirB]', exportAs: 'dirB'}) class DirectiveB { @Input() value = 'DirB'; + constructor() { log.push(this.value); } } @@ -48,11 +49,14 @@ describe('di', () => { class DirectiveA { value = 'DirA'; } + @Directive({selector: '[dirC]', exportAs: 'dirC'}) class DirectiveC { value: string; + constructor(dirA: DirectiveA, dirB: DirectiveB) { this.value = dirA.value + dirB.value; } } + @Component({ template: `
@@ -62,6 +66,7 @@ describe('di', () => { }) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); @@ -74,11 +79,14 @@ describe('di', () => { @Directive({selector: '[dirA]'}) class DirectiveA { value = 'dirA'; + constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); } } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); @@ -90,14 +98,17 @@ describe('di', () => { @Directive({selector: '[dirA]'}) class DirectiveA { value = 'dirA'; + constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); } } + // - dirB is know to the node injectors // - 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 @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({ declarations: [DirectiveA, DirectiveB, MyComp], providers: [{provide: DirectiveB, useValue: {value: 'module'}}] @@ -113,9 +124,11 @@ describe('di', () => { class MyComp { constructor(dirB: DirectiveB) { log.push(`Comp (dep: ${dirB.value})`); } } + @Component({template: ''}) class MyApp { } + TestBed.configureTestingModule({declarations: [DirectiveB, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -128,10 +141,12 @@ describe('di', () => { class DirectiveA { constructor(dir: DirectiveB) { log.push(`DirA (dep: ${dir.value})`); } } + @Component({template: '
'}) class MyComp { array = [1, 2, 3]; } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); @@ -144,22 +159,28 @@ describe('di', () => { @Directive({selector: '[dirA]'}) class DirectiveA { value = 'DirA'; + constructor() { log.push(this.value); } } + @Directive({selector: '[dirC]'}) class DirectiveC { value = 'DirC'; + constructor() { log.push(this.value); } } + @Directive({selector: '[dirB]'}) class DirectiveB { constructor(dirA: DirectiveA, dirC: DirectiveC) { log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`); } } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); @@ -171,25 +192,33 @@ describe('di', () => { @Directive({selector: '[dirC]'}) class DirectiveC { value = 'DirC'; + constructor(dirB: DirectiveB) { log.push(`DirC (dep: ${dirB.value})`); } } + @Directive({selector: '[dirA]'}) class DirectiveA { value = 'DirA'; + constructor(dirC: DirectiveC) { log.push(`DirA (dep: ${dirC.value})`); } } + @Directive({selector: '[dirD]'}) class DirectiveD { value = 'DirD'; + constructor(dirA: DirectiveA) { log.push(`DirD (dep: ${dirA.value})`); } } + @Component({selector: 'my-comp', template: ''}) class MyComp { constructor(dirD: DirectiveD) { log.push(`Comp (dep: ${dirD.value})`); } } + @Component({template: ''}) class MyApp { } + TestBed.configureTestingModule( {declarations: [DirectiveA, DirectiveB, DirectiveC, DirectiveD, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); @@ -204,12 +233,14 @@ describe('di', () => { class MyApp { value = 'App'; } + @Directive({selector: '[dirA]'}) class DirectiveA { constructor(dirB: DirectiveB, app: MyApp) { log.push(`DirA (deps: ${dirB.value} and ${app.value})`); } } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -219,24 +250,30 @@ describe('di', () => { it('should not use a parent when peer dep is available', () => { let count = 1; + @Directive({selector: '[dirB]'}) class DirectiveB { count: number; + constructor() { log.push(`DirB`); this.count = count++; } } + @Directive({selector: '[dirA]'}) class DirectiveA { constructor(dirB: DirectiveB) { log.push(`DirA (dep: DirB - ${dirB.count})`); } } + @Component({selector: 'my-comp', template: '
'}) class MyComp { } + @Component({template: ''}) class MyApp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -249,6 +286,7 @@ describe('di', () => { @Directive({selector: '[dirA]', exportAs: 'dirA'}) class DirectiveA { injector: Injector; + constructor(public dirB: DirectiveB, public vcr: ViewContainerRef) { this.injector = vcr.injector; } @@ -263,6 +301,7 @@ describe('di', () => { @Component({template: ''}) class MyApp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -282,6 +321,7 @@ describe('di', () => { class MyApp { showing = false; } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.componentInstance.showing = true; @@ -305,6 +345,7 @@ describe('di', () => { skipContent = false; skipContent2 = false; } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -317,10 +358,12 @@ describe('di', () => { @Directive({selector: '[structuralDir]'}) class StructuralDirective { @Input() tmp !: TemplateRef; + constructor(public vcr: ViewContainerRef) {} create() { this.vcr.createEmbeddedView(this.tmp); } } + @Component({ template: `
@@ -336,6 +379,7 @@ describe('di', () => { class MyComp { @ViewChild(StructuralDirective) structuralDir !: StructuralDirective; } + TestBed.configureTestingModule( {declarations: [StructuralDirective, DirectiveA, DirectiveB, MyComp]}); const fixture = TestBed.createComponent(MyComp); @@ -356,6 +400,7 @@ describe('di', () => { }) class MyApp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); @@ -379,6 +424,7 @@ describe('di', () => { @ViewChild(HostBindingDirective) hostBindingDir !: HostBindingDirective; @ViewChild(DirectiveA) dirA !: DirectiveA; } + TestBed.configureTestingModule( {declarations: [DirectiveA, DirectiveB, HostBindingDirective, MyApp]}); const fixture = TestBed.createComponent(MyApp); @@ -403,13 +449,16 @@ describe('di', () => { class DirectiveB { constructor() {} } + @Directive({selector: '[dirA]'}) class DirectiveA { constructor(siblingDir: DirectiveB) {} } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/); }); @@ -419,13 +468,16 @@ describe('di', () => { class DirectiveB { constructor() {} } + @Directive({selector: '[dirA]'}) class DirectiveA { constructor(siblingDir: DirectiveB) {} } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/); }); @@ -436,13 +488,16 @@ describe('di', () => { class DirectiveB { constructor(@Inject(forwardRef(() => DirectiveA)) siblingDir: DirectiveA) {} } + @Directive({selector: '[dirA]'}) class DirectiveA { constructor(siblingDir: DirectiveB) {} } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/); }); @@ -453,12 +508,336 @@ describe('di', () => { class DirectiveA { constructor(siblingDir: DirectiveA) {} } + @Component({template: '
'}) class MyComp { } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/); }); + + describe('flags', () => { + + @Directive({selector: '[dirB]'}) + class DirectiveB { + @Input('dirB') value = ''; + } + + describe('Optional', () => { + + @Directive({selector: '[dirA]'}) + class DirectiveA { + constructor(@Optional() public dirB: DirectiveB) {} + } + + it('should not throw if dependency is @Optional (module injector)', () => { + + @Component({template: '
'}) + class MyComp { + @ViewChild(DirectiveA) dirA !: DirectiveA; + } + + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const dirA = fixture.componentInstance.dirA; + expect(dirA.dirB).toBeNull(); + }); + + it('should return null if @Optional dependency has @Self flag', () => { + + @Directive({selector: '[dirC]'}) + class DirectiveC { + constructor(@Optional() @Self() public dirB: DirectiveB) {} + } + + @Component({template: '
'}) + class MyComp { + @ViewChild(DirectiveC) dirC !: DirectiveC; + } + + TestBed.configureTestingModule({declarations: [DirectiveC, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const dirC = fixture.componentInstance.dirC; + expect(dirC.dirB).toBeNull(); + }); + + it('should not throw if dependency is @Optional but defined elsewhere', () => { + + @Directive({selector: '[dirC]'}) + class DirectiveC { + constructor(@Optional() public dirB: DirectiveB) {} + } + + @Component({template: '
'}) + class MyComp { + @ViewChild(DirectiveC) dirC !: DirectiveC; + } + + TestBed.configureTestingModule({declarations: [DirectiveB, DirectiveC, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const dirC = fixture.componentInstance.dirC; + expect(dirC.dirB).toBeNull(); + }); + }); + + it('should skip the current node with @SkipSelf', () => { + + @Directive({selector: '[dirA]'}) + class DirectiveA { + constructor(@SkipSelf() public dirB: DirectiveB) {} + } + + @Component({selector: 'my-comp', template: '
'}) + class MyComp { + @ViewChild(DirectiveA) dirA !: DirectiveA; + } + + @Component({template: ''}) + class MyApp { + @ViewChild(MyComp) myComp !: MyComp; + } + + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + + const dirA = fixture.componentInstance.myComp.dirA; + expect(dirA.dirB.value).toEqual('parent'); + }); + + onlyInIvy('Ivy has different error message when dependency is not found') + .it('should check only the current node with @Self', () => { + + @Directive({selector: '[dirA]'}) + class DirectiveA { + constructor(@Self() public dirB: DirectiveB) {} + } + + @Component({template: '
'}) + class MyComp { + } + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]}); + expect(() => TestBed.createComponent(MyComp)) + .toThrowError(/NodeInjector: NOT_FOUND \[DirectiveB]/); + }); + + describe('@Host', () => { + @Directive({selector: '[dirA]'}) + class DirectiveA { + constructor(@Host() public dirB: DirectiveB) {} + } + + @Directive({selector: '[dirString]'}) + class DirectiveString { + constructor(@Host() public s: String) {} + } + + it('should find viewProviders on the host itself', () => { + @Component({ + selector: 'my-comp', + template: '
', + viewProviders: [{provide: String, useValue: 'Foo'}] + }) + class MyComp { + @ViewChild(DirectiveString) dirString !: DirectiveString; + } + + @Component({template: ''}) + class MyApp { + @ViewChild(MyComp) myComp !: MyComp; + } + + TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + + const dirString = fixture.componentInstance.myComp.dirString; + expect(dirString.s).toBe('Foo'); + }); + + it('should find host component on the host itself', () => { + @Directive({selector: '[dirComp]'}) + class DirectiveComp { + constructor(@Inject(forwardRef(() => MyComp)) @Host() public comp: MyComp) {} + } + + @Component({selector: 'my-comp', template: '
'}) + class MyComp { + @ViewChild(DirectiveComp) dirComp !: DirectiveComp; + } + + @Component({template: ''}) + class MyApp { + @ViewChild(MyComp) myComp !: MyComp; + } + + TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + + const myComp = fixture.componentInstance.myComp; + const dirComp = myComp.dirComp; + expect(dirComp.comp).toBe(myComp); + }); + + onlyInIvy('Ivy has different error message when dependency is not found') + .it('should not find providers on the host itself', () => { + @Component({ + selector: 'my-comp', + template: '
', + providers: [{provide: String, useValue: 'Foo'}] + }) + class MyComp { + } + + @Component({template: ''}) + class MyApp { + } + + TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]}); + expect(() => TestBed.createComponent(MyApp)) + .toThrowError(/NodeInjector: NOT_FOUND \[String]/); + }); + + onlyInIvy('Ivy has different error message when dependency is not found') + .it('should not find other directives on the host itself', () => { + @Component({selector: 'my-comp', template: '
'}) + class MyComp { + } + + @Component({template: ''}) + class MyApp { + } + + TestBed.configureTestingModule( + {declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); + expect(() => TestBed.createComponent(MyApp)) + .toThrowError(/NodeInjector: NOT_FOUND \[DirectiveB]/); + }); + + onlyInIvy('Ivy has different error message when dependency is not found') + .it('should not find providers on the host itself if in inline view', () => { + @Component({ + selector: 'my-comp', + template: '
' + }) + class MyComp { + showing = false; + } + + @Component({template: ''}) + class MyApp { + @ViewChild(MyComp) myComp !: MyComp; + } + + TestBed.configureTestingModule( + {declarations: [DirectiveA, DirectiveB, MyComp, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect(() => { + fixture.componentInstance.myComp.showing = true; + fixture.detectChanges(); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirectiveB]/); + }); + + it('should find providers across embedded views if not passing component boundary', () => { + @Component({template: '
'}) + class MyApp { + showing = false; + @ViewChild(DirectiveA) dirA !: DirectiveA; + @ViewChild(DirectiveB) dirB !: DirectiveB; + } + + TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + fixture.componentInstance.showing = true; + fixture.detectChanges(); + + const dirA = fixture.componentInstance.dirA; + const dirB = fixture.componentInstance.dirB; + expect(dirA.dirB).toBe(dirB); + }); + + onlyInIvy('Ivy has different error message when dependency is not found') + .it('should not find component above the host', () => { + @Directive({selector: '[dirComp]'}) + class DirectiveComp { + constructor(@Inject(forwardRef(() => MyApp)) @Host() public comp: MyApp) {} + } + + @Component({selector: 'my-comp', template: '
'}) + class MyComp { + } + + @Component({template: ''}) + class MyApp { + } + + TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]}); + expect(() => TestBed.createComponent(MyApp)) + .toThrowError(/NodeInjector: NOT_FOUND \[MyApp]/); + }); + + 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', + () => { + class ControlContainer {} + let controlContainers: ControlContainer[] = []; + let injectedControlContainer: ControlContainer|null = null; + + @Directive({ + selector: '[group]', + providers: [{provide: ControlContainer, useExisting: GroupDirective}] + }) + class GroupDirective { + constructor() { controlContainers.push(this); } + } + + @Directive({selector: '[control]'}) + class ControlDirective { + constructor(@Host() @SkipSelf() @Inject(ControlContainer) parent: + ControlContainer) { + injectedControlContainer = parent; + } + } + + @Component({ + selector: 'my-comp', + template: '', + viewProviders: [{provide: ControlContainer, useExisting: GroupDirective}] + }) + class MyComp { + } + + @Component({ + template: ` +
+ +
+ ` + }) + class MyApp { + } + + TestBed.configureTestingModule( + {declarations: [GroupDirective, ControlDirective, MyComp, MyApp]}); + const fixture = TestBed.createComponent(MyApp); + expect(fixture.nativeElement.innerHTML) + .toBe('
'); + expect(controlContainers).toEqual([injectedControlContainer !]); + }); + }); + }); + }); }); describe('service injection', () => { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 3aa1d037df..b57087169f 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,22 +6,20 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, ViewContainerRef, ɵɵdefineInjector} from '@angular/core'; +import {ChangeDetectorRef, Host, InjectFlags, Injector, Optional, Renderer2, Self, ViewContainerRef} from '@angular/core'; import {createLView, createNodeAtIndex, createTView} from '@angular/core/src/render3/instructions/shared'; -import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition'; +import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; -import {createInjector} from '../../src/di/r3_injector'; import {ɵɵdefineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di'; -import {ɵɵProvidersFeature, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; +import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; import {TNODE} from '../../src/render3/interfaces/injector'; -import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; +import {TNodeType} from '../../src/render3/interfaces/node'; import {isProceduralRenderer} from '../../src/render3/interfaces/renderer'; import {LViewFlags} from '../../src/render3/interfaces/view'; import {enterView, leaveView} from '../../src/render3/state'; import {ViewRef} from '../../src/render3/view_ref'; -import {NgIf} from './common_with_def'; import {getRendererFactory2} from './imported_renderer2'; import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util'; @@ -207,142 +205,6 @@ describe('di', () => { 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()}); - } - - /**
*/ - 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)) - }); - } - - /**
*/ - 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)) - }); - } - - /** - *
- *
- */ - 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)) - }); - } - - /**
*/ - const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dirA', '', 'dirB', 'self']); - } - }, 1, 0, [DirA, DirB]); - - /* */ - 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)) - }); - } - - /** - *
- *
- *
- */ - 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', () => { @@ -382,12 +244,8 @@ describe('di', () => { describe('@Host', () => { let dirA: DirA|null = null; - let dirString: DirString|null = null; - beforeEach(() => { - dirA = null; - dirString = null; - }); + beforeEach(() => { dirA = null; }); class DirA { constructor(@Host() public dirB: DirB) {} @@ -399,106 +257,10 @@ describe('di', () => { }); } - 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', () => { - /**
*/ - 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'}]); - - /* */ - 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)) - }); - } - - /**
*/ - const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dirCmp', '']); - } - }, 1, 0, [DirComp]); - - /* */ - 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', () => { - /**
*/ - 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'}]); - - /* */ - 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', () => { - /**
*/ - const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dirA', '']); - } - }, 1, 0, [DirA]); - - /* */ - 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\]/); - }); - + /** + * This test needs to be moved to acceptance/di_spec.ts + * when Ivy compiler supports inline views. + */ it('should not find providers on the host itself if in inline view', () => { let comp !: any; @@ -542,192 +304,6 @@ describe('di', () => { 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', '']); - } - } - - /** - *
- *
- *
- */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div', ['dirB', '']); - { - ɵɵtemplate( - 1, IfTemplate, 1, 0, 'div', ['dirA', '', AttributeMarker.Template, '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)) - }); - } - - /**
*/ - const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dirCmp', '']); - } - }, 1, 0, [DirComp]); - - /* */ - 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: ` - - `, - 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: ` -
- -
- ` - }) - */ - 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); - expect(fixture.html) - .toEqual( - '
'); - - expect(controlContainers).toEqual([injectedControlContainer !]); - - }); - }); }); }); }); @@ -781,7 +357,7 @@ describe('di', () => { }); } - const directives = [MyComp, Directive, DirectiveSameInstance, NgIf]; + const directives = [MyComp, Directive, DirectiveSameInstance]; /** * This test needs to be moved to acceptance/di_spec.ts