From 47244ba2b870f8631bc7ec23b9cf5329d69f438c Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Tue, 19 Mar 2019 19:41:10 +0100 Subject: [PATCH] refactor(ivy): move di tests for DI tokens to acceptance (#29299) Move tests for special tokens like `Injector`, `ElementRef`, `TemplateRef`, `ViewContainerRef`, `ChangeDectetorRef` and custom string tokens. PR Close #29299 --- packages/core/test/acceptance/di_spec.ts | 472 +++++++++++++++++++-- packages/core/test/render3/di_spec.ts | 519 +---------------------- 2 files changed, 448 insertions(+), 543 deletions(-) diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 195f3964c1..b7c2090d93 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -7,59 +7,463 @@ */ import {CommonModule} from '@angular/common'; -import {Attribute, ChangeDetectorRef, Component, Directive, EventEmitter, Inject, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, SkipSelf, ViewChild} from '@angular/core'; +import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, INJECTOR, Inject, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, SkipSelf, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {ViewRef} from '@angular/core/src/render3/view_ref'; import {TestBed} from '@angular/core/testing'; +import {onlyInIvy} from '@angular/private/testing'; describe('di', () => { - describe('ChangeDetectorRef', () => { - it('should inject host component ChangeDetectorRef into directives on templates', () => { - let pipeInstance: MyPipe; + describe('Special tokens', () => { - @Pipe({name: 'pipe'}) - class MyPipe implements PipeTransform { - constructor(public cdr: ChangeDetectorRef) { pipeInstance = this; } + describe('Injector', () => { - transform(value: any): any { return value; } - } + it('should inject the injector', () => { + @Directive({selector: '[injectorDir]'}) + class InjectorDir { + constructor(public injector: Injector) {} + } - @Component({ - selector: 'my-app', - template: `
Visible
`, - }) - class MyApp { - showing = true; + @Directive({selector: '[otherInjectorDir]'}) + class OtherInjectorDir { + constructor(public otherDir: InjectorDir, public injector: Injector) {} + } - constructor(public cdr: ChangeDetectorRef) {} - } + @Component({template: '
'}) + class MyComp { + @ViewChild(InjectorDir) injectorDir !: InjectorDir; + @ViewChild(OtherInjectorDir) otherInjectorDir !: OtherInjectorDir; + } - TestBed.configureTestingModule({declarations: [MyApp, MyPipe], imports: [CommonModule]}); - const fixture = TestBed.createComponent(MyApp); - fixture.detectChanges(); - expect((pipeInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance); + TestBed.configureTestingModule({declarations: [InjectorDir, OtherInjectorDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const divElement = fixture.nativeElement.querySelector('div'); + const injectorDir = fixture.componentInstance.injectorDir; + const otherInjectorDir = fixture.componentInstance.otherInjectorDir; + + expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); + expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); + expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir); + expect(injectorDir.injector).not.toBe(otherInjectorDir.injector); + }); + + it('should inject INJECTOR', () => { + @Directive({selector: '[injectorDir]'}) + class InjectorDir { + constructor(@Inject(INJECTOR) public injector: Injector) {} + } + + @Component({template: '
'}) + class MyComp { + @ViewChild(InjectorDir) injectorDir !: InjectorDir; + } + + TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const divElement = fixture.nativeElement.querySelector('div'); + const injectorDir = fixture.componentInstance.injectorDir; + + expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); + expect(injectorDir.injector.get(Injector).get(ElementRef).nativeElement).toBe(divElement); + expect(injectorDir.injector.get(INJECTOR).get(ElementRef).nativeElement).toBe(divElement); + }); }); - it('should inject host component ChangeDetectorRef into directives on ng-container', () => { - let dirInstance: MyDirective; + describe('ElementRef', () => { - @Directive({selector: '[getCDR]'}) - class MyDirective { - constructor(public cdr: ChangeDetectorRef) { dirInstance = this; } + it('should create directive with ElementRef dependencies', () => { + @Directive({selector: '[dir]'}) + class MyDir { + value: string; + constructor(public elementRef: ElementRef) { + this.value = (elementRef.constructor as any).name; + } + } + + @Directive({selector: '[otherDir]'}) + class MyOtherDir { + isSameInstance: boolean; + constructor(public elementRef: ElementRef, public directive: MyDir) { + this.isSameInstance = elementRef === directive.elementRef; + } + } + + @Component({template: '
'}) + class MyComp { + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + + TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const divElement = fixture.nativeElement.querySelector('div'); + const directive = fixture.componentInstance.directive; + const otherDirective = fixture.componentInstance.otherDirective; + + expect(directive.value).toContain('ElementRef'); + expect(directive.elementRef.nativeElement).toEqual(divElement); + expect(otherDirective.elementRef.nativeElement).toEqual(divElement); + + // Each ElementRef instance should be unique + expect(otherDirective.isSameInstance).toBe(false); + }); + + it('should create ElementRef with comment if requesting directive is on node', + () => { + @Directive({selector: '[dir]'}) + class MyDir { + value: string; + constructor(public elementRef: ElementRef) { + this.value = (elementRef.constructor as any).name; + } + } + + @Component({template: ''}) + class MyComp { + @ViewChild(MyDir) directive !: MyDir; + } + + TestBed.configureTestingModule({declarations: [MyDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const directive = fixture.componentInstance.directive; + + expect(directive.value).toContain('ElementRef'); + // the nativeElement should be a comment + expect(directive.elementRef.nativeElement.nodeType).toEqual(Node.COMMENT_NODE); + }); + }); + + describe('TemplateRef', () => { + + @Directive({selector: '[dir]', exportAs: 'dir'}) + class MyDir { + value: string; + constructor(public templateRef: TemplateRef) { + this.value = (templateRef.constructor as any).name; + } } + onlyInIvy('Ivy creates a unique instance of TemplateRef for each directive') + .it('should create directive with TemplateRef dependencies', () => { + @Directive({selector: '[otherDir]', exportAs: 'otherDir'}) + class MyOtherDir { + isSameInstance: boolean; + constructor(public templateRef: TemplateRef, public directive: MyDir) { + this.isSameInstance = templateRef === directive.templateRef; + } + } - @Component({ - selector: 'my-app', - template: `Visible`, - }) - class MyApp { + @Component({ + template: '' + }) + class MyComp { + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + + TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const directive = fixture.componentInstance.directive; + const otherDirective = fixture.componentInstance.otherDirective; + + expect(directive.value).toContain('TemplateRef'); + expect(directive.templateRef).not.toBeNull(); + expect(otherDirective.templateRef).not.toBeNull(); + + // Each TemplateRef instance should be unique + expect(otherDirective.isSameInstance).toBe(false); + }); + + it('should throw if injected on an element', () => { + @Component({template: '
'}) + class MyComp { + } + + TestBed.configureTestingModule({declarations: [MyDir, MyComp]}); + expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for TemplateRef/); + }); + + it('should throw if injected on an ng-container', () => { + @Component({template: ''}) + class MyComp { + } + + TestBed.configureTestingModule({declarations: [MyDir, MyComp]}); + expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for TemplateRef/); + }); + + it('should NOT throw if optional and injected on an element', () => { + @Directive({selector: '[optionalDir]', exportAs: 'optionalDir'}) + class OptionalDir { + constructor(@Optional() public templateRef: TemplateRef) {} + } + @Component({template: '
'}) + class MyComp { + @ViewChild(OptionalDir) directive !: OptionalDir; + } + + TestBed.configureTestingModule({declarations: [OptionalDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + expect(fixture.componentInstance.directive.templateRef).toBeNull(); + }); + }); + + describe('ViewContainerRef', () => { + onlyInIvy('Ivy creates a unique instance of ViewContainerRef for each directive') + .it('should create directive with ViewContainerRef dependencies', () => { + @Directive({selector: '[dir]', exportAs: 'dir'}) + class MyDir { + value: string; + constructor(public viewContainerRef: ViewContainerRef) { + this.value = (viewContainerRef.constructor as any).name; + } + } + @Directive({selector: '[otherDir]', exportAs: 'otherDir'}) + class MyOtherDir { + isSameInstance: boolean; + constructor(public viewContainerRef: ViewContainerRef, public directive: MyDir) { + this.isSameInstance = viewContainerRef === directive.viewContainerRef; + } + } + @Component({template: '
'}) + class MyComp { + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + + TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const directive = fixture.componentInstance.directive; + const otherDirective = fixture.componentInstance.otherDirective; + + expect(directive.value).toContain('ViewContainerRef'); + expect(directive.viewContainerRef).not.toBeNull(); + expect(otherDirective.viewContainerRef).not.toBeNull(); + + // Each ViewContainerRef instance should be unique + expect(otherDirective.isSameInstance).toBe(false); + }); + }); + + describe('ChangeDetectorRef', () => { + + @Directive({selector: '[dir]', exportAs: 'dir'}) + class MyDir { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + } + @Directive({selector: '[otherDir]', exportAs: 'otherDir'}) + class MyOtherDir { + constructor(public cdr: ChangeDetectorRef) {} + } + @Component({selector: 'my-comp', template: ''}) + class MyComp { constructor(public cdr: ChangeDetectorRef) {} } - TestBed.configureTestingModule({declarations: [MyApp, MyDirective]}); - const fixture = TestBed.createComponent(MyApp); + it('should inject host component ChangeDetectorRef into directives on templates', () => { + let pipeInstance: MyPipe; + + @Pipe({name: 'pipe'}) + class MyPipe implements PipeTransform { + constructor(public cdr: ChangeDetectorRef) { pipeInstance = this; } + + transform(value: any): any { return value; } + } + + @Component({ + selector: 'my-app', + template: `
Visible
`, + }) + class MyApp { + showing = true; + + constructor(public cdr: ChangeDetectorRef) {} + } + + TestBed.configureTestingModule({declarations: [MyApp, MyPipe], imports: [CommonModule]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect((pipeInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance); + }); + + it('should inject current component ChangeDetectorRef into directives on the same node as components', + () => { + @Component({selector: 'my-app', template: ''}) + class MyApp { + @ViewChild(MyComp) component !: MyComp; + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + const app = fixture.componentInstance; + const comp = fixture.componentInstance.component; + expect((comp !.cdr as ViewRef).context).toBe(comp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(app.directive.value).toContain('ViewRef'); + + // Each ChangeDetectorRef instance should be unique + expect(app.directive !.cdr).not.toBe(comp !.cdr); + expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on normal elements', + () => { + @Component({selector: 'my-comp', template: '
'}) + class MyComp { + constructor(public cdr: ChangeDetectorRef) {} + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + const comp = fixture.componentInstance; + expect((comp !.cdr as ViewRef).context).toBe(comp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(comp.directive.value).toContain('ViewRef'); + + // Each ChangeDetectorRef instance should be unique + expect(comp.directive !.cdr).not.toBe(comp.cdr); + expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren', + () => { + @Component({ + selector: 'my-app', + template: ` +
+
+ ` + }) + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + @ViewChild(MyComp) component !: MyComp; + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + const app = fixture.componentInstance; + expect((app !.cdr as ViewRef).context).toBe(app); + const comp = fixture.componentInstance.component; + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(app.directive.value).toContain('ViewRef'); + + // Each ChangeDetectorRef instance should be unique + expect(app.directive !.cdr).not.toBe(comp.cdr); + expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in embedded views', () => { + @Component({ + selector: 'my-comp', + template: ` +
+
` + }) + class MyComp { + showing = true; + constructor(public cdr: ChangeDetectorRef) {} + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + + TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + const comp = fixture.componentInstance; + expect((comp !.cdr as ViewRef).context).toBe(comp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(comp.directive.value).toContain('ViewRef'); + + // Each ChangeDetectorRef instance should be unique + expect(comp.directive !.cdr).not.toBe(comp.cdr); + expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on containers', () => { + @Component( + {selector: 'my-comp', template: '
'}) + class MyComp { + showing = true; + constructor(public cdr: ChangeDetectorRef) {} + @ViewChild(MyDir) directive !: MyDir; + @ViewChild(MyOtherDir) otherDirective !: MyOtherDir; + } + + TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + const comp = fixture.componentInstance; + expect((comp !.cdr as ViewRef).context).toBe(comp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(comp.directive.value).toContain('ViewRef'); + + // Each ChangeDetectorRef instance should be unique + expect(comp.directive !.cdr).not.toBe(comp.cdr); + expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on ng-container', () => { + let dirInstance: MyDirective; + + @Directive({selector: '[getCDR]'}) + class MyDirective { + constructor(public cdr: ChangeDetectorRef) { dirInstance = this; } + } + + @Component({ + selector: 'my-app', + template: `Visible`, + }) + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + } + + TestBed.configureTestingModule({declarations: [MyApp, MyDirective]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect((dirInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance); + }); + }); + }); + + describe('string tokens', () => { + it('should be able to provide a string token', () => { + @Directive({selector: '[injectorDir]', providers: [{provide: 'test', useValue: 'provided'}]}) + class InjectorDir { + constructor(@Inject('test') public value: string) {} + } + + @Component({template: '
'}) + class MyComp { + @ViewChild(InjectorDir) injectorDirInstance !: InjectorDir; + } + + TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]}); + const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); - expect((dirInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance); + + const injectorDir = fixture.componentInstance.injectorDirInstance; + + expect(injectorDir.value).toBe('provided'); }); }); diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 78c91a54ee..0426e06777 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,21 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, ElementRef, Host, INJECTOR, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, ɵɵdefineInjectable, ɵɵdefineInjector} from '@angular/core'; +import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, ɵɵdefineInjectable, ɵɵdefineInjector} 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 {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, ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵload, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtemplate, ɵɵtemplateRefExtractor, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; -import {LContainer, NATIVE} from '../../src/render3/interfaces/container'; +import {ɵɵProvidersFeature, ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation2, ɵɵprojection, ɵɵprojectionDef, ɵɵreference, ɵɵtemplate, ɵɵtemplateRefExtractor, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; import {TNODE} from '../../src/render3/interfaces/injector'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; -import {RElement, isProceduralRenderer} from '../../src/render3/interfaces/renderer'; +import {isProceduralRenderer} from '../../src/render3/interfaces/renderer'; import {LViewFlags} from '../../src/render3/interfaces/view'; -import {enterView, getLView, leaveView} from '../../src/render3/state'; -import {getNativeByIndex} from '../../src/render3/util/view_utils'; +import {enterView, leaveView} from '../../src/render3/state'; import {ViewRef} from '../../src/render3/view_ref'; import {NgIf} from './common_with_def'; @@ -1419,326 +1417,6 @@ describe('di', () => { describe('Special tokens', () => { - describe('Injector', () => { - - it('should inject the injector', () => { - let injectorDir !: InjectorDir; - let otherInjectorDir !: OtherInjectorDir; - let divElement !: HTMLElement; - - class InjectorDir { - constructor(public injector: Injector) {} - - static ngDirectiveDef = ɵɵdefineDirective({ - type: InjectorDir, - selectors: [['', 'injectorDir', '']], - factory: () => injectorDir = new InjectorDir(ɵɵdirectiveInject(Injector as any)) - }); - } - - class OtherInjectorDir { - constructor(public otherDir: InjectorDir, public injector: Injector) {} - - static ngDirectiveDef = ɵɵdefineDirective({ - type: OtherInjectorDir, - selectors: [['', 'otherInjectorDir', '']], - factory: () => otherInjectorDir = new OtherInjectorDir( - ɵɵdirectiveInject(InjectorDir), ɵɵdirectiveInject(Injector as any)) - }); - } - - - /**
*/ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['injectorDir', '', 'otherInjectorDir', '']); - } - // testing only - divElement = ɵɵload(0); - }, 1, 0, [InjectorDir, OtherInjectorDir]); - - const fixture = new ComponentFixture(App); - expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); - expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); - expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir); - expect(injectorDir.injector).not.toBe(otherInjectorDir.injector); - }); - - it('should inject INJECTOR', () => { - let injectorDir !: INJECTORDir; - let divElement !: HTMLElement; - - class INJECTORDir { - constructor(public injector: Injector) {} - - static ngDirectiveDef = ɵɵdefineDirective({ - type: INJECTORDir, - selectors: [['', 'injectorDir', '']], - factory: () => injectorDir = new INJECTORDir(ɵɵdirectiveInject(INJECTOR as any)) - }); - } - - - /**
*/ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['injectorDir', '']); - } - // testing only - divElement = ɵɵload(0); - }, 1, 0, [INJECTORDir]); - - const fixture = new ComponentFixture(App); - expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement); - expect(injectorDir.injector.get(Injector).get(ElementRef).nativeElement).toBe(divElement); - expect(injectorDir.injector.get(INJECTOR).get(ElementRef).nativeElement).toBe(divElement); - }); - - }); - - describe('ElementRef', () => { - - it('should create directive with ElementRef dependencies', () => { - let dir !: Directive; - let dirSameInstance !: DirectiveSameInstance; - let div !: RElement; - - class Directive { - value: string; - constructor(public elementRef: ElementRef) { - this.value = (elementRef.constructor as any).name; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: Directive, - selectors: [['', 'dir', '']], - factory: () => dir = new Directive(ɵɵdirectiveInject(ElementRef)), - exportAs: ['dir'] - }); - } - - class DirectiveSameInstance { - isSameInstance: boolean; - constructor(public elementRef: ElementRef, directive: Directive) { - this.isSameInstance = elementRef === directive.elementRef; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: DirectiveSameInstance, - selectors: [['', 'dirSame', '']], - factory: () => dirSameInstance = new DirectiveSameInstance( - ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(Directive)), - exportAs: ['dirSame'] - }); - } - - /**
*/ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div', ['dir', '', 'dirSame', '']); - ɵɵelementEnd(); - div = getNativeByIndex(0, getLView()) as RElement; - } - }, 1, 0, [Directive, DirectiveSameInstance]); - - const fixture = new ComponentFixture(App); - expect(dir.value).toContain('ElementRef'); - expect(dir.elementRef.nativeElement).toEqual(div); - expect(dirSameInstance.elementRef.nativeElement).toEqual(div); - - // Each ElementRef instance should be unique - expect(dirSameInstance.isSameInstance).toBe(false); - }); - - it('should create ElementRef with comment if requesting directive is on node', - () => { - let dir !: Directive; - let lContainer !: LContainer; - - class Directive { - value: string; - constructor(public elementRef: ElementRef) { - this.value = (elementRef.constructor as any).name; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: Directive, - selectors: [['', 'dir', '']], - factory: () => dir = new Directive(ɵɵdirectiveInject(ElementRef)), - exportAs: ['dir'] - }); - } - - /** */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵtemplate(0, () => {}, 0, 0, 'ng-template', ['dir', '']); - lContainer = ɵɵload(0) as any; - } - }, 1, 0, [Directive]); - - const fixture = new ComponentFixture(App); - expect(dir.value).toContain('ElementRef'); - expect(dir.elementRef.nativeElement).toEqual(lContainer[NATIVE]); - }); - }); - - describe('TemplateRef', () => { - class Directive { - value: string; - constructor(public templateRef: TemplateRef) { - this.value = (templateRef.constructor as any).name; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: Directive, - selectors: [['', 'dir', '']], - factory: () => new Directive(ɵɵdirectiveInject(TemplateRef as any)), - exportAs: ['dir'] - }); - } - - it('should create directive with TemplateRef dependencies', () => { - class DirectiveSameInstance { - isSameInstance: boolean; - constructor(templateRef: TemplateRef, directive: Directive) { - this.isSameInstance = templateRef === directive.templateRef; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: DirectiveSameInstance, - selectors: [['', 'dirSame', '']], - factory: () => new DirectiveSameInstance( - ɵɵdirectiveInject(TemplateRef as any), ɵɵdirectiveInject(Directive)), - exportAs: ['dirSame'] - }); - } - - /** - * - * {{ dir.value }} - {{ dirSame.value }} - * - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵtemplate( - 0, function() {}, 0, 0, 'ng-template', ['dir', '', 'dirSame', ''], - ['dir', 'dir', 'dirSame', 'dirSame']); - ɵɵtext(3); - } - if (rf & RenderFlags.Update) { - const tmp1 = ɵɵreference(1) as any; - const tmp2 = ɵɵreference(2) as any; - ɵɵtextBinding(3, ɵɵinterpolation2('', tmp1.value, '-', tmp2.isSameInstance, '')); - } - }, 4, 2, [Directive, DirectiveSameInstance]); - - const fixture = new ComponentFixture(App); - // Each TemplateRef instance should be unique - expect(fixture.html).toContain('TemplateRef'); - expect(fixture.html).toContain('false'); - }); - - it('should throw if injected on an element', () => { - /**
*/ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dir', '']); - } - }, 1, 0, [Directive]); - - expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/); - }); - - it('should throw if injected on an ng-container', () => { - /** */ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelementContainerStart(0, ['dir', '']); - ɵɵelementContainerEnd(); - } - }, 1, 0, [Directive]); - - expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/); - }); - - it('should NOT throw if optional and injected on an element', () => { - let dir !: OptionalDirective; - class OptionalDirective { - constructor(@Optional() public templateRef: TemplateRef) {} - - static ngDirectiveDef = ɵɵdefineDirective({ - type: OptionalDirective, - selectors: [['', 'dir', '']], - factory: () => dir = new OptionalDirective( - ɵɵdirectiveInject(TemplateRef as any, InjectFlags.Optional)), - exportAs: ['dir'] - }); - } - - /**
*/ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['dir', '']); - } - }, 1, 0, [OptionalDirective]); - - expect(() => new ComponentFixture(App)).not.toThrow(); - expect(dir.templateRef).toBeNull(); - }); - - }); - - describe('ViewContainerRef', () => { - it('should create directive with ViewContainerRef dependencies', () => { - class Directive { - value: string; - constructor(public viewContainerRef: ViewContainerRef) { - this.value = (viewContainerRef.constructor as any).name; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: Directive, - selectors: [['', 'dir', '']], - factory: () => new Directive(ɵɵdirectiveInject(ViewContainerRef as any)), - exportAs: ['dir'] - }); - } - - class DirectiveSameInstance { - isSameInstance: boolean; - constructor(viewContainerRef: ViewContainerRef, directive: Directive) { - this.isSameInstance = viewContainerRef === directive.viewContainerRef; - } - static ngDirectiveDef = ɵɵdefineDirective({ - type: DirectiveSameInstance, - selectors: [['', 'dirSame', '']], - factory: () => new DirectiveSameInstance( - ɵɵdirectiveInject(ViewContainerRef as any), ɵɵdirectiveInject(Directive)), - exportAs: ['dirSame'] - }); - } - - /** - *
- * {{ dir.value }} - {{ dirSame.value }} - *
- */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart( - 0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']); - { ɵɵtext(3); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - const tmp1 = ɵɵreference(1) as any; - const tmp2 = ɵɵreference(2) as any; - ɵɵtextBinding(3, ɵɵinterpolation2('', tmp1.value, '-', tmp2.isSameInstance, '')); - } - }, 4, 2, [Directive, DirectiveSameInstance]); - - const fixture = new ComponentFixture(App); - // Each ViewContainerRef instance should be unique - expect(fixture.html).toContain('ViewContainerRef'); - expect(fixture.html).toContain('false'); - }); - }); - describe('ChangeDetectorRef', () => { let dir: Directive; let dirSameInstance: DirectiveSameInstance; @@ -1788,109 +1466,10 @@ describe('di', () => { const directives = [MyComp, Directive, DirectiveSameInstance, NgIf]; - it('should inject current component ChangeDetectorRef into directives on the same node as components', - () => { - /** {{ dir.value }} */ - const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - ɵɵtext(2); - } - if (rf & RenderFlags.Update) { - const tmp = ɵɵreference(1) as any; - ɵɵtextBinding(2, ɵɵbind(tmp.value)); - } - }, 3, 1, directives); - - const app = renderComponent(MyApp); - // ChangeDetectorRef is the token, ViewRef has historically been the constructor - expect(toHtml(app)).toEqual('ViewRef'); - expect((comp !.cdr as ViewRef).context).toBe(comp); - - // Each ChangeDetectorRef instance should be unique - expect(dir !.cdr).not.toBe(comp !.cdr); - expect(dir !.cdr).not.toBe(dirSameInstance !.cdr); - }); - - it('should inject host component ChangeDetectorRef into directives on normal elements', - () => { - - class MyApp { - constructor(public cdr: ChangeDetectorRef) {} - - static ngComponentDef = ɵɵdefineComponent({ - type: MyApp, - selectors: [['my-app']], - consts: 3, - vars: 1, - factory: () => new MyApp(ɵɵdirectiveInject(ChangeDetectorRef as any)), - /**
{{ dir.value }}
*/ - template: function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - { ɵɵtext(2); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - const tmp = ɵɵreference(1) as any; - ɵɵtextBinding(2, ɵɵbind(tmp.value)); - } - }, - directives: directives - }); - } - - const app = renderComponent(MyApp); - expect(toHtml(app)).toEqual('
ViewRef
'); - expect((app !.cdr as ViewRef).context).toBe(app); - - // Each ChangeDetectorRef instance should be unique - expect(dir !.cdr).not.toBe(app.cdr); - expect(dir !.cdr).not.toBe(dirSameInstance !.cdr); - }); - - it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren', - () => { - class MyApp { - constructor(public cdr: ChangeDetectorRef) {} - - static ngComponentDef = ɵɵdefineComponent({ - type: MyApp, - selectors: [['my-app']], - consts: 4, - vars: 1, - factory: () => new MyApp(ɵɵdirectiveInject(ChangeDetectorRef as any)), - /** - * - *
- *
- * {{ dir.value }} - */ - template: function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'my-comp'); - { ɵɵelement(1, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); } - ɵɵelementEnd(); - ɵɵtext(3); - } - if (rf & RenderFlags.Update) { - const tmp = ɵɵreference(2) as any; - ɵɵtextBinding(3, ɵɵbind(tmp.value)); - } - }, - directives: directives - }); - } - - const app = renderComponent(MyApp); - expect(toHtml(app)).toEqual('
ViewRef'); - expect((app !.cdr as ViewRef).context).toBe(app); - - // Each ChangeDetectorRef instance should be unique - expect(dir !.cdr).not.toBe(app !.cdr); - expect(dir !.cdr).not.toBe(dirSameInstance !.cdr); - }); - + /** + * This test needs to be moved to acceptance/di_spec.ts + * when Ivy compiler supports inline views. + */ it('should inject host component ChangeDetectorRef into directives in embedded views', () => { class MyApp { @@ -1906,8 +1485,8 @@ describe('di', () => { vars: 0, /** * % if (showing) { - *
{{ dir.value }}
- * % } + *
{{ dir.value }}
+ * % } */ template: function(rf: RenderFlags, ctx: MyApp) { if (rf & RenderFlags.Create) { @@ -1945,84 +1524,6 @@ describe('di', () => { expect(dir !.cdr).not.toBe(app.cdr); expect(dir !.cdr).not.toBe(dirSameInstance !.cdr); }); - - it('should inject host component ChangeDetectorRef into directives on containers', () => { - function C1(rf1: RenderFlags, ctx1: any) { - if (rf1 & RenderFlags.Create) { - ɵɵelementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - { ɵɵtext(2); } - ɵɵelementEnd(); - } - if (rf1 & RenderFlags.Update) { - const tmp = ɵɵreference(1) as any; - ɵɵtextBinding(2, ɵɵbind(tmp.value)); - } - } - - class MyApp { - showing = true; - - constructor(public cdr: ChangeDetectorRef) {} - - static ngComponentDef = ɵɵdefineComponent({ - type: MyApp, - selectors: [['my-app']], - factory: () => new MyApp(ɵɵdirectiveInject(ChangeDetectorRef as any)), - consts: 1, - vars: 0, - /**
{{ dir.value }}
*/ - template: function(rf: RenderFlags, ctx: MyApp) { - if (rf & RenderFlags.Create) { - ɵɵtemplate( - 0, C1, 3, 1, 'div', - ['dir', '', 'dirSame', '', AttributeMarker.Template, 'ngIf']); - } - if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'ngIf', ɵɵbind(ctx.showing)); - } - }, - directives: directives - }); - } - - const app = renderComponent(MyApp); - expect(toHtml(app)).toEqual('
ViewRef
'); - expect((app !.cdr as ViewRef).context).toBe(app); - - // Each ChangeDetectorRef instance should be unique - expect(dir !.cdr).not.toBe(app.cdr); - expect(dir !.cdr).not.toBe(dirSameInstance !.cdr); - }); - }); - }); - - describe('string tokens', () => { - it('should be able to provide a string token', () => { - let injectorDir !: InjectorDir; - let divElement !: HTMLElement; - - class InjectorDir { - constructor(public value: string) {} - - static ngDirectiveDef = ɵɵdefineDirective({ - type: InjectorDir, - selectors: [['', 'injectorDir', '']], - factory: () => injectorDir = new InjectorDir(ɵɵdirectiveInject('test' as any)), - features: [ɵɵProvidersFeature([{provide: 'test', useValue: 'provided'}])], - }); - } - - /**
*/ - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'div', ['injectorDir', '']); - } - // testing only - divElement = ɵɵload(0); - }, 1, 0, [InjectorDir]); - - const fixture = new ComponentFixture(App); - expect(injectorDir.value).toBe('provided'); }); });