refactor(ivy): move di tests for directive injection to acceptance (#29299)
PR Close #29299
This commit is contained in:
parent
47244ba2b8
commit
1b0be8d656
|
@ -7,13 +7,480 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
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 {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 {ViewRef} from '@angular/core/src/render3/view_ref';
|
import {ViewRef} from '@angular/core/src/render3/view_ref';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {onlyInIvy} from '@angular/private/testing';
|
import {onlyInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
|
|
||||||
describe('di', () => {
|
describe('di', () => {
|
||||||
|
describe('no dependencies', () => {
|
||||||
|
it('should create directive with no deps', () => {
|
||||||
|
@Directive({selector: '[dir]', exportAs: 'dir'})
|
||||||
|
class MyDirective {
|
||||||
|
value = 'Created';
|
||||||
|
}
|
||||||
|
@Component({template: '<div dir #dir="dir">{{ dir.value }}</div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [MyDirective, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toContain('Created');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('directive injection', () => {
|
||||||
|
|
||||||
|
let log: string[] = [];
|
||||||
|
|
||||||
|
@Directive({selector: '[dirB]', exportAs: 'dirB'})
|
||||||
|
class DirectiveB {
|
||||||
|
@Input() value = 'DirB';
|
||||||
|
constructor() { log.push(this.value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => log = []);
|
||||||
|
|
||||||
|
it('should create directive with intra view dependencies', () => {
|
||||||
|
@Directive({selector: '[dirA]', exportAs: 'dirA'})
|
||||||
|
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: `
|
||||||
|
<div dirA>
|
||||||
|
<span dirB dirC #dir="dirC">{{ dir.value }}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('span');
|
||||||
|
expect(divElement.textContent).toContain('DirADirB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate injected directives in dependency order', () => {
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
value = 'dirA';
|
||||||
|
constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); }
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA dirB></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to the module injector', () => {
|
||||||
|
@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: '<div dirB></div><div dirA></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [DirectiveA, DirectiveB, MyComp],
|
||||||
|
providers: [{provide: DirectiveB, useValue: {value: 'module'}}]
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirB', 'DirA (dep: module)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate injected directives before components', () => {
|
||||||
|
@Component({selector: 'my-comp', template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(dirB: DirectiveB) { log.push(`Comp (dep: ${dirB.value})`); }
|
||||||
|
}
|
||||||
|
@Component({template: '<my-comp dirB></my-comp>'})
|
||||||
|
class MyApp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveB, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject directives in the correct order in a for loop', () => {
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor(dir: DirectiveB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA dirB *ngFor="let i of array"></div>'})
|
||||||
|
class MyComp {
|
||||||
|
array = [1, 2, 3];
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(
|
||||||
|
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate directives with multiple out-of-order dependencies', () => {
|
||||||
|
@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: '<div dirA dirB dirC></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, DirectiveC, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate in the correct order for complex case', () => {
|
||||||
|
@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: '<my-comp dirA dirB dirC dirD></my-comp>'})
|
||||||
|
class MyApp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule(
|
||||||
|
{declarations: [DirectiveA, DirectiveB, DirectiveC, DirectiveD, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(
|
||||||
|
['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate in correct order with mixed parent and peer dependencies', () => {
|
||||||
|
@Component({template: '<div dirA dirB dirC></div>'})
|
||||||
|
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();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
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: '<div dirA dirB></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
@Component({template: '<my-comp dirB></my-comp>'})
|
||||||
|
class MyApp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dependencies in parent views', () => {
|
||||||
|
|
||||||
|
@Directive({selector: '[dirA]', exportAs: 'dirA'})
|
||||||
|
class DirectiveA {
|
||||||
|
injector: Injector;
|
||||||
|
constructor(public dirB: DirectiveB, public vcr: ViewContainerRef) {
|
||||||
|
this.injector = vcr.injector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
{selector: 'my-comp', template: '<div dirA #dir="dirA">{{ dir.dirB.value }}</div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should find dependencies on component hosts', () => {
|
||||||
|
@Component({template: '<my-comp dirB></my-comp>'})
|
||||||
|
class MyApp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('DirB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find dependencies for directives in embedded views', () => {
|
||||||
|
@Component({
|
||||||
|
template: `<div dirB>
|
||||||
|
<div *ngIf="showing">
|
||||||
|
<div dirA #dir="dirA">{{ dir.dirB.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
showing = false;
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.componentInstance.showing = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('DirB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find dependencies of directives nested deeply in inline views', () => {
|
||||||
|
@Component({
|
||||||
|
template: `<div dirB>
|
||||||
|
<ng-container *ngIf="!skipContent">
|
||||||
|
<ng-container *ngIf="!skipContent2">
|
||||||
|
<div dirA #dir="dirA">{{ dir.dirB.value }}</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
skipContent = false;
|
||||||
|
skipContent2 = false;
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('DirB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
|
||||||
|
@Directive({selector: '[structuralDir]'})
|
||||||
|
class StructuralDirective {
|
||||||
|
@Input() tmp !: TemplateRef<any>;
|
||||||
|
constructor(public vcr: ViewContainerRef) {}
|
||||||
|
|
||||||
|
create() { this.vcr.createEmbeddedView(this.tmp); }
|
||||||
|
}
|
||||||
|
@Component({
|
||||||
|
template: `<div dirB value="declaration">
|
||||||
|
<ng-template #foo>
|
||||||
|
<div dirA #dir="dirA">{{ dir.dirB.value }}</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div dirB value="insertion">
|
||||||
|
<div structuralDir [tmp]="foo"></div>
|
||||||
|
<!-- insertion point -->
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
class MyComp {
|
||||||
|
@ViewChild(StructuralDirective) structuralDir !: StructuralDirective;
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule(
|
||||||
|
{declarations: [StructuralDirective, DirectiveA, DirectiveB, MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.componentInstance.structuralDir.create();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div[value=insertion]');
|
||||||
|
expect(divElement.textContent).toEqual('declaration');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create injectors on second template pass', () => {
|
||||||
|
@Component({
|
||||||
|
template: `<div>
|
||||||
|
<my-comp dirB></my-comp>
|
||||||
|
<my-comp dirB></my-comp>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('DirBDirB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create injectors and host bindings in same view', () => {
|
||||||
|
@Directive({selector: '[hostBindingDir]'})
|
||||||
|
class HostBindingDirective {
|
||||||
|
@HostBinding('id') id = 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<div dirB hostBindingDir>
|
||||||
|
<p dirA #dir="dirA">{{ dir.dirB.value }}</p>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
@ViewChild(HostBindingDirective) hostBindingDir !: HostBindingDirective;
|
||||||
|
@ViewChild(DirectiveA) dirA !: DirectiveA;
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule(
|
||||||
|
{declarations: [DirectiveA, DirectiveB, HostBindingDirective, MyApp]});
|
||||||
|
const fixture = TestBed.createComponent(MyApp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('DirB');
|
||||||
|
expect(divElement.id).toEqual('foo');
|
||||||
|
|
||||||
|
const dirA = fixture.componentInstance.dirA;
|
||||||
|
expect(dirA.vcr.injector).toEqual(dirA.injector);
|
||||||
|
|
||||||
|
const hostBindingDir = fixture.componentInstance.hostBindingDir;
|
||||||
|
hostBindingDir.id = 'bar';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(divElement.id).toBe('bar');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if directive is not found anywhere', () => {
|
||||||
|
@Directive({selector: '[dirB]'})
|
||||||
|
class DirectiveB {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor(siblingDir: DirectiveB) {}
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if directive is not found in ancestor tree', () => {
|
||||||
|
@Directive({selector: '[dirB]'})
|
||||||
|
class DirectiveB {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor(siblingDir: DirectiveB) {}
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA></div><div dirB></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for DirectiveB/);
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('Ivy has different error message for circular dependency')
|
||||||
|
.it('should throw if directives try to inject each other', () => {
|
||||||
|
@Directive({selector: '[dirB]'})
|
||||||
|
class DirectiveB {
|
||||||
|
constructor(@Inject(forwardRef(() => DirectiveA)) siblingDir: DirectiveA) {}
|
||||||
|
}
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor(siblingDir: DirectiveB) {}
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA dirB></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/);
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('Ivy has different error message for circular dependency')
|
||||||
|
.it('should throw if directive tries to inject itself', () => {
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor(siblingDir: DirectiveA) {}
|
||||||
|
}
|
||||||
|
@Component({template: '<div dirA></div>'})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
|
||||||
|
expect(() => TestBed.createComponent(MyComp)).toThrowError(/Circular dep for/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('service injection', () => {
|
||||||
|
|
||||||
|
it('should create instance even when no injector present', () => {
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class MyService {
|
||||||
|
value = 'MyService';
|
||||||
|
}
|
||||||
|
@Component({template: '<div>{{myService.value}}</div>'})
|
||||||
|
class MyComp {
|
||||||
|
constructor(public myService: MyService) {}
|
||||||
|
}
|
||||||
|
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divElement = fixture.nativeElement.querySelector('div');
|
||||||
|
expect(divElement.textContent).toEqual('MyService');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Special tokens', () => {
|
describe('Special tokens', () => {
|
||||||
|
|
||||||
describe('Injector', () => {
|
describe('Injector', () => {
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, ɵɵdefineInjectable, ɵɵdefineInjector} from '@angular/core';
|
import {ChangeDetectorRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, ViewContainerRef, ɵɵdefineInjector} from '@angular/core';
|
||||||
import {createLView, createNodeAtIndex, createTView} from '@angular/core/src/render3/instructions/shared';
|
import {createLView, createNodeAtIndex, createTView} from '@angular/core/src/render3/instructions/shared';
|
||||||
import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||||
|
|
||||||
import {createInjector} from '../../src/di/r3_injector';
|
import {createInjector} from '../../src/di/r3_injector';
|
||||||
import {ɵɵdefineComponent} from '../../src/render3/definition';
|
import {ɵɵdefineComponent} from '../../src/render3/definition';
|
||||||
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
||||||
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 {ɵɵ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 {TNODE} from '../../src/render3/interfaces/injector';
|
import {TNODE} from '../../src/render3/interfaces/injector';
|
||||||
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
|
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
|
||||||
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
|
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
|
||||||
|
@ -26,36 +26,6 @@ import {getRendererFactory2} from './imported_renderer2';
|
||||||
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
|
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
|
||||||
|
|
||||||
describe('di', () => {
|
describe('di', () => {
|
||||||
describe('no dependencies', () => {
|
|
||||||
it('should create directive with no deps', () => {
|
|
||||||
class Directive {
|
|
||||||
value: string = 'Created';
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
type: Directive,
|
|
||||||
selectors: [['', 'dir', '']],
|
|
||||||
factory: () => new Directive,
|
|
||||||
exportAs: ['dir']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dir #dir="dir"> {{ dir.value }} </div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dir', ''], ['dir', 'dir']);
|
|
||||||
{ ɵɵtext(2); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const tmp = ɵɵreference(1) as any;
|
|
||||||
ɵɵtextBinding(2, ɵɵbind(tmp.value));
|
|
||||||
}
|
|
||||||
}, 3, 1, [Directive]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
expect(fixture.html).toEqual('<div dir="">Created</div>');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('directive injection', () => {
|
describe('directive injection', () => {
|
||||||
let log: string[] = [];
|
let log: string[] = [];
|
||||||
|
|
||||||
|
@ -73,129 +43,10 @@ describe('di', () => {
|
||||||
|
|
||||||
beforeEach(() => log = []);
|
beforeEach(() => log = []);
|
||||||
|
|
||||||
it('should create directive with intra view dependencies', () => {
|
|
||||||
class DirA {
|
|
||||||
value: string = 'DirA';
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA()});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirC {
|
|
||||||
value: string;
|
|
||||||
constructor(a: DirA, b: DirB) { this.value = a.value + b.value; }
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
type: DirC,
|
|
||||||
selectors: [['', 'dirC', '']],
|
|
||||||
factory: () => new DirC(ɵɵdirectiveInject(DirA), ɵɵdirectiveInject(DirB)),
|
|
||||||
exportAs: ['dirC']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <div dirA>
|
* This test needs to be moved to acceptance/di_spec.ts
|
||||||
* <span dirB dirC #dir="dirC"> {{ dir.value }} </span>
|
* when Ivy compiler supports inline views.
|
||||||
* </div>
|
|
||||||
*/
|
*/
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dirA', '']);
|
|
||||||
{
|
|
||||||
ɵɵelementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']);
|
|
||||||
{ ɵɵtext(3); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const tmp = ɵɵreference(2) as any;
|
|
||||||
ɵɵtextBinding(3, ɵɵbind(tmp.value));
|
|
||||||
}
|
|
||||||
}, 4, 1, [DirA, DirB, DirC]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
expect(fixture.html).toEqual('<div dira=""><span dirb="" dirc="">DirADirB</span></div>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should instantiate injected directives in dependency order', () => {
|
|
||||||
class DirA {
|
|
||||||
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirB)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dirA dirB></div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [DirA, DirB]);
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fallback to the module injector', () => {
|
|
||||||
class DirA {
|
|
||||||
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirB)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `<div dirB></div><div dirA></div>`
|
|
||||||
// - dirB is know to the node injectors (it uses the diPublic feature)
|
|
||||||
// - then when dirA tries to inject dirB, it will check the node injector first tree
|
|
||||||
// - if not found, it will check the module injector tree
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirB', '']);
|
|
||||||
ɵɵelement(1, 'div', ['dirA', '']);
|
|
||||||
}
|
|
||||||
}, 2, 0, [DirA, DirB]);
|
|
||||||
|
|
||||||
const fakeModuleInjector: any = {
|
|
||||||
get: function(token: any) {
|
|
||||||
const value = token === DirB ? 'module' : 'fail';
|
|
||||||
return {value: value};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new ComponentFixture(App, {injector: fakeModuleInjector});
|
|
||||||
expect(log).toEqual(['DirB', 'DirA (dep: module)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should instantiate injected directives before components', () => {
|
|
||||||
class Comp {
|
|
||||||
constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngComponentDef = ɵɵdefineComponent({
|
|
||||||
selectors: [['comp']],
|
|
||||||
type: Comp,
|
|
||||||
consts: 0,
|
|
||||||
vars: 0,
|
|
||||||
factory: () => new Comp(ɵɵdirectiveInject(DirB)),
|
|
||||||
template: (rf: RenderFlags, ctx: Comp) => {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <comp dirB></comp> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'comp', ['dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Comp, DirB]);
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inject directives in the correct order in a for loop', () => {
|
it('should inject directives in the correct order in a for loop', () => {
|
||||||
class DirA {
|
class DirA {
|
||||||
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||||
|
@ -235,184 +86,6 @@ describe('di', () => {
|
||||||
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should instantiate directives with multiple out-of-order dependencies', () => {
|
|
||||||
class DirA {
|
|
||||||
value = 'DirA';
|
|
||||||
constructor() { log.push(this.value); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{selectors: [['', 'dirA', '']], type: DirA, factory: () => new DirA()});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirB {
|
|
||||||
constructor(dirA: DirA, dirC: DirC) {
|
|
||||||
log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirB', '']],
|
|
||||||
type: DirB,
|
|
||||||
factory: () => new DirB(ɵɵdirectiveInject(DirA), ɵɵdirectiveInject(DirC))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirC {
|
|
||||||
value = 'DirC';
|
|
||||||
constructor() { log.push(this.value); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{selectors: [['', 'dirC', '']], type: DirC, factory: () => new DirC()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dirA dirB dirC></div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [DirA, DirB, DirC]);
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should instantiate in the correct order for complex case', () => {
|
|
||||||
class Comp {
|
|
||||||
constructor(dir: DirD) { log.push(`Comp (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngComponentDef = ɵɵdefineComponent({
|
|
||||||
selectors: [['comp']],
|
|
||||||
type: Comp,
|
|
||||||
consts: 0,
|
|
||||||
vars: 0,
|
|
||||||
factory: () => new Comp(ɵɵdirectiveInject(DirD)),
|
|
||||||
template: (ctx: any, fm: boolean) => {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirA {
|
|
||||||
value = 'DirA';
|
|
||||||
constructor(dir: DirC) { log.push(`DirA (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirC))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirC {
|
|
||||||
value = 'DirC';
|
|
||||||
constructor(dir: DirB) { log.push(`DirC (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirC', '']],
|
|
||||||
type: DirC,
|
|
||||||
factory: () => new DirC(ɵɵdirectiveInject(DirB))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirD {
|
|
||||||
value = 'DirD';
|
|
||||||
constructor(dir: DirA) { log.push(`DirD (dep: ${dir.value})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirD', '']],
|
|
||||||
type: DirD,
|
|
||||||
factory: () => new DirD(ɵɵdirectiveInject(DirA))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <comp dirA dirB dirC dirD></comp> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Comp, DirA, DirB, DirC, DirD]);
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(
|
|
||||||
['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should instantiate in correct order with mixed parent and peer dependencies', () => {
|
|
||||||
class DirA {
|
|
||||||
constructor(dirB: DirB, app: App) {
|
|
||||||
log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirB), ɵɵdirectiveInject(App)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class App {
|
|
||||||
value = 'App';
|
|
||||||
|
|
||||||
static ngComponentDef = ɵɵdefineComponent({
|
|
||||||
selectors: [['app']],
|
|
||||||
type: App,
|
|
||||||
factory: () => new App(),
|
|
||||||
consts: 1,
|
|
||||||
vars: 0,
|
|
||||||
/** <div dirA dirB dirC></div> */
|
|
||||||
template: (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directives: [DirA, DirB]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not use a parent when peer dep is available', () => {
|
|
||||||
let count = 1;
|
|
||||||
|
|
||||||
class DirA {
|
|
||||||
constructor(dirB: DirB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirB)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirB {
|
|
||||||
count: number;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
log.push(`DirB`);
|
|
||||||
this.count = count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dirA dirB></div> */
|
|
||||||
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [DirA, DirB]);
|
|
||||||
|
|
||||||
/** <parent dirB></parent> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'parent', ['dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Parent, DirB]);
|
|
||||||
|
|
||||||
new ComponentFixture(App);
|
|
||||||
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('dependencies in parent views', () => {
|
describe('dependencies in parent views', () => {
|
||||||
|
|
||||||
class DirA {
|
class DirA {
|
||||||
|
@ -431,78 +104,9 @@ describe('di', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <div dirA #dir="dirA">
|
* This test needs to be moved to acceptance/di_spec.ts
|
||||||
* {{ dir.dirB.value }}
|
* when Ivy compiler supports inline views.
|
||||||
* </div>
|
|
||||||
*/
|
*/
|
||||||
const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
|
|
||||||
{ ɵɵtext(2); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const dir = ɵɵreference(1) as DirA;
|
|
||||||
ɵɵtextBinding(2, ɵɵbind(dir.dirB.value));
|
|
||||||
}
|
|
||||||
}, 3, 1, [DirA]);
|
|
||||||
|
|
||||||
it('should find dependencies on component hosts', () => {
|
|
||||||
/** <comp dirB>/comp> */
|
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'comp', ['dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Comp, DirB]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find dependencies for directives in embedded views', () => {
|
|
||||||
|
|
||||||
function IfTemplate(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div');
|
|
||||||
{
|
|
||||||
ɵɵelementStart(1, 'div', ['dirA', ''], ['dir', 'dirA']);
|
|
||||||
{ ɵɵtext(3); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const dir = ɵɵreference(2) as DirA;
|
|
||||||
ɵɵtextBinding(3, ɵɵbind(dir.dirB.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <div dirB>
|
|
||||||
* <div *ngIf="showing">
|
|
||||||
* <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
|
|
||||||
* </div>
|
|
||||||
* </div>
|
|
||||||
*/
|
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dirB', '']);
|
|
||||||
{ ɵɵtemplate(1, IfTemplate, 4, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
ɵɵelementProperty(1, 'ngIf', ɵɵbind(ctx.showing));
|
|
||||||
}
|
|
||||||
}, 2, 1, [DirA, DirB, NgIf]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
fixture.component.showing = true;
|
|
||||||
fixture.update();
|
|
||||||
|
|
||||||
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find dependencies of directives nested deeply in inline views', () => {
|
it('should find dependencies of directives nested deeply in inline views', () => {
|
||||||
/**
|
/**
|
||||||
* <div dirB>
|
* <div dirB>
|
||||||
|
@ -560,293 +164,6 @@ describe('di', () => {
|
||||||
const fixture = new ComponentFixture(App);
|
const fixture = new ComponentFixture(App);
|
||||||
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
|
|
||||||
let structuralDir !: StructuralDir;
|
|
||||||
|
|
||||||
class StructuralDir {
|
|
||||||
// @Input()
|
|
||||||
tmp !: TemplateRef<any>;
|
|
||||||
|
|
||||||
constructor(public vcr: ViewContainerRef) {}
|
|
||||||
|
|
||||||
create() { this.vcr.createEmbeddedView(this.tmp); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
type: StructuralDir,
|
|
||||||
selectors: [['', 'structuralDir', '']],
|
|
||||||
factory: () => structuralDir =
|
|
||||||
new StructuralDir(ɵɵdirectiveInject(ViewContainerRef as any)),
|
|
||||||
inputs: {tmp: 'tmp'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function FooTemplate(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
|
|
||||||
{ ɵɵtext(2); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const dir = ɵɵreference(1) as DirA;
|
|
||||||
ɵɵtextBinding(2, ɵɵbind(dir.dirB.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <div dirB value="declaration">
|
|
||||||
* <ng-template #foo>
|
|
||||||
* <div dirA dir="dirA"> {{ dir.dirB.value }} </div>
|
|
||||||
* </ng-template>
|
|
||||||
* </div>
|
|
||||||
*
|
|
||||||
* <div dirB value="insertion">
|
|
||||||
* <div structuralDir [tmp]="foo"></div>
|
|
||||||
* // insertion point
|
|
||||||
* </div>
|
|
||||||
*/
|
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', ['dirB', '', 'value', 'declaration']);
|
|
||||||
{
|
|
||||||
ɵɵtemplate(
|
|
||||||
1, FooTemplate, 3, 1, 'ng-template', null, ['foo', ''], ɵɵtemplateRefExtractor);
|
|
||||||
}
|
|
||||||
ɵɵelementEnd();
|
|
||||||
ɵɵelementStart(3, 'div', ['dirB', '', 'value', 'insertion']);
|
|
||||||
{ ɵɵelement(4, 'div', ['structuralDir', '']); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
const foo = ɵɵreference(2) as any;
|
|
||||||
ɵɵelementProperty(4, 'tmp', ɵɵbind(foo));
|
|
||||||
}
|
|
||||||
}, 5, 1, [DirA, DirB, StructuralDir]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
structuralDir.create();
|
|
||||||
fixture.update();
|
|
||||||
expect(fixture.hostElement.textContent).toEqual(`declaration`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create injectors on second template pass', () => {
|
|
||||||
/**
|
|
||||||
* <comp dirB></comp>
|
|
||||||
* <comp dirB></comp>
|
|
||||||
*/
|
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'comp', ['dirB', '']);
|
|
||||||
ɵɵelement(1, 'comp', ['dirB', '']);
|
|
||||||
}
|
|
||||||
}, 2, 0, [Comp, DirB]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
expect(fixture.hostElement.textContent).toEqual(`DirBDirB`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create injectors and host bindings in same view', () => {
|
|
||||||
let hostBindingDir !: HostBindingDir;
|
|
||||||
|
|
||||||
class HostBindingDir {
|
|
||||||
// @HostBinding('id')
|
|
||||||
id = 'foo';
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
type: HostBindingDir,
|
|
||||||
selectors: [['', 'hostBindingDir', '']],
|
|
||||||
factory: () => hostBindingDir = new HostBindingDir(),
|
|
||||||
hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵallocHostVars(1);
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
ɵɵelementProperty(elementIndex, 'id', ɵɵbind(ctx.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let dir !: DirA;
|
|
||||||
/**
|
|
||||||
* <div dirB hostBindingDir>
|
|
||||||
* <p dirA #dir="dirA">
|
|
||||||
* {{ dir.dirB.value }}
|
|
||||||
* </p>
|
|
||||||
* </div>
|
|
||||||
*/
|
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelementStart(0, 'div', [
|
|
||||||
'dirB',
|
|
||||||
'',
|
|
||||||
'hostBindingDir',
|
|
||||||
'',
|
|
||||||
]);
|
|
||||||
{
|
|
||||||
ɵɵelementStart(1, 'p', ['dirA', ''], ['dir', 'dirA']);
|
|
||||||
{ ɵɵtext(3); }
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
ɵɵelementEnd();
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
dir = ɵɵreference(2) as DirA;
|
|
||||||
ɵɵtextBinding(3, ɵɵbind(dir.dirB.value));
|
|
||||||
}
|
|
||||||
}, 4, 1, [HostBindingDir, DirA, DirB]);
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
|
||||||
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
|
||||||
const hostDirEl = fixture.hostElement.querySelector('div') as HTMLElement;
|
|
||||||
expect(hostDirEl.id).toEqual('foo');
|
|
||||||
// The injector should not be overwritten by host bindings
|
|
||||||
expect(dir.vcr.injector).toEqual(dir.injector);
|
|
||||||
|
|
||||||
hostBindingDir.id = 'bar';
|
|
||||||
fixture.update();
|
|
||||||
expect(hostDirEl.id).toEqual('bar');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create instance even when no injector present', () => {
|
|
||||||
class MyService {
|
|
||||||
value = 'MyService';
|
|
||||||
static ngInjectableDef =
|
|
||||||
ɵɵdefineInjectable({providedIn: 'root', factory: () => new MyService()});
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyComponent {
|
|
||||||
constructor(public myService: MyService) {}
|
|
||||||
static ngComponentDef = ɵɵdefineComponent({
|
|
||||||
type: MyComponent,
|
|
||||||
selectors: [['my-component']],
|
|
||||||
consts: 1,
|
|
||||||
vars: 1,
|
|
||||||
factory: () => new MyComponent(ɵɵdirectiveInject(MyService)),
|
|
||||||
template: function(rf: RenderFlags, ctx: MyComponent) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵtext(0);
|
|
||||||
}
|
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
ɵɵtextBinding(0, ɵɵbind(ctx.myService.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(MyComponent);
|
|
||||||
fixture.update();
|
|
||||||
expect(fixture.html).toEqual('MyService');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if directive is not found anywhere', () => {
|
|
||||||
class Dir {
|
|
||||||
constructor(siblingDir: OtherDir) {}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dir', '']],
|
|
||||||
type: Dir,
|
|
||||||
factory: () => new Dir(ɵɵdirectiveInject(OtherDir))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class OtherDir {
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dir></div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dir', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Dir, OtherDir]);
|
|
||||||
|
|
||||||
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if directive is not found in ancestor tree', () => {
|
|
||||||
class Dir {
|
|
||||||
constructor(siblingDir: OtherDir) {}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dir', '']],
|
|
||||||
type: Dir,
|
|
||||||
factory: () => new Dir(ɵɵdirectiveInject(OtherDir))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class OtherDir {
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective(
|
|
||||||
{selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <div other></div>
|
|
||||||
* <div dir></div>
|
|
||||||
*/
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['other', '']);
|
|
||||||
ɵɵelement(1, 'div', ['dir', '']);
|
|
||||||
}
|
|
||||||
}, 2, 0, [Dir, OtherDir]);
|
|
||||||
|
|
||||||
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should throw if directives try to inject each other', () => {
|
|
||||||
class DirA {
|
|
||||||
constructor(dir: DirB) {}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirA', '']],
|
|
||||||
type: DirA,
|
|
||||||
factory: () => new DirA(ɵɵdirectiveInject(DirB))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DirB {
|
|
||||||
constructor(dir: DirA) {}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dirB', '']],
|
|
||||||
type: DirB,
|
|
||||||
factory: () => new DirB(ɵɵdirectiveInject(DirA))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dirA dirB></div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dirA', '', 'dirB', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [DirA, DirB]);
|
|
||||||
|
|
||||||
expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if directive tries to inject itself', () => {
|
|
||||||
class Dir {
|
|
||||||
constructor(dir: Dir) {}
|
|
||||||
|
|
||||||
static ngDirectiveDef = ɵɵdefineDirective({
|
|
||||||
selectors: [['', 'dir', '']],
|
|
||||||
type: Dir,
|
|
||||||
factory: () => new Dir(ɵɵdirectiveInject(Dir))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div dir></div> */
|
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
||||||
if (rf & RenderFlags.Create) {
|
|
||||||
ɵɵelement(0, 'div', ['dir', '']);
|
|
||||||
}
|
|
||||||
}, 1, 0, [Dir]);
|
|
||||||
|
|
||||||
expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('flags', () => {
|
describe('flags', () => {
|
||||||
|
|
Loading…
Reference in New Issue