test(ivy): add root cause analysis for failing core tests (view injector integration) (#27912)

PR Close #27912
This commit is contained in:
Marc Laval 2018-12-21 17:10:18 +01:00 committed by Ben Lesh
parent c4f7727408
commit 460be795cf
2 changed files with 201 additions and 195 deletions

View File

@ -589,7 +589,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
})); }));
modifiedInIvy('FW-821: Pure pipes are instantiated differently in view engine and ivy') modifiedInIvy('Pure pipes are instantiated differently in view engine and ivy')
.it('should call pure pipes that are used multiple times only when the arguments change and share state between pipe instances', .it('should call pure pipes that are used multiple times only when the arguments change and share state between pipe instances',
fakeAsync(() => { fakeAsync(() => {
const ctx = createCompFixture( const ctx = createCompFixture(

View File

@ -10,7 +10,7 @@ import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, Compon
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing'; import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing'; import {fixmeIvy, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@Directive({selector: '[simpleDirective]'}) @Directive({selector: '[simpleDirective]'})
class SimpleDirective { class SimpleDirective {
@ -292,19 +292,21 @@ class TestComp {
expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2');
}); });
fixmeIvy('unknown').it('should instantiate viewProviders that have dependencies', () => { fixmeIvy(
TestBed.configureTestingModule({declarations: [SimpleComponent]}); 'FW-889: Element injector cannot access the viewProviders of the component to which it belongs')
const viewProviders = [ .it('should instantiate viewProviders that have dependencies', () => {
{provide: 'injectable1', useValue: 'injectable1'}, { TestBed.configureTestingModule({declarations: [SimpleComponent]});
provide: 'injectable2', const viewProviders = [
useFactory: (val: any) => `${val}-injectable2`, {provide: 'injectable1', useValue: 'injectable1'}, {
deps: ['injectable1'] provide: 'injectable2',
} useFactory: (val: any) => `${val}-injectable2`,
]; deps: ['injectable1']
TestBed.overrideComponent(SimpleComponent, {set: {viewProviders}}); }
const el = createComponent('<div simpleComponent></div>'); ];
expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); TestBed.overrideComponent(SimpleComponent, {set: {viewProviders}});
}); const el = createComponent('<div simpleComponent></div>');
expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2');
});
it('should instantiate components that depend on viewProviders providers', () => { it('should instantiate components that depend on viewProviders providers', () => {
TestBed.configureTestingModule({declarations: [NeedsServiceComponent]}); TestBed.configureTestingModule({declarations: [NeedsServiceComponent]});
@ -429,7 +431,7 @@ class TestComp {
expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1'); expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1');
}); });
fixmeIvy('unknown').it('should inject providers that were declared after it', () => { it('should inject providers that were declared after it', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -465,52 +467,55 @@ class TestComp {
expect(comp.componentInstance.a).toBe('aValue'); expect(comp.componentInstance.a).toBe('aValue');
}); });
fixmeIvy('unknown').it('should support ngOnDestroy for lazy providers', () => { fixmeIvy('FW-848: ngOnDestroy hooks are not called on providers')
let created = false; .it('should support ngOnDestroy for lazy providers', () => {
let destroyed = false; let created = false;
let destroyed = false;
class SomeInjectable { class SomeInjectable {
constructor() { created = true; } constructor() { created = true; }
ngOnDestroy() { destroyed = true; } ngOnDestroy() { destroyed = true; }
} }
@Component({providers: [SomeInjectable], template: ''}) @Component({providers: [SomeInjectable], template: ''})
class SomeComp { class SomeComp {
} }
TestBed.configureTestingModule({declarations: [SomeComp]}); TestBed.configureTestingModule({declarations: [SomeComp]});
let compRef = TestBed.createComponent(SomeComp).componentRef; let compRef = TestBed.createComponent(SomeComp).componentRef;
expect(created).toBe(false); expect(created).toBe(false);
expect(destroyed).toBe(false); expect(destroyed).toBe(false);
// no error if the provider was not yet created // no error if the provider was not yet created
compRef.destroy(); compRef.destroy();
expect(created).toBe(false); expect(created).toBe(false);
expect(destroyed).toBe(false); expect(destroyed).toBe(false);
compRef = TestBed.createComponent(SomeComp).componentRef; compRef = TestBed.createComponent(SomeComp).componentRef;
compRef.injector.get(SomeInjectable); compRef.injector.get(SomeInjectable);
expect(created).toBe(true); expect(created).toBe(true);
compRef.destroy(); compRef.destroy();
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
}); });
fixmeIvy('unknown').it('should instantiate view providers lazily', () => { fixmeIvy(
let created = false; 'FW-889: Element injector cannot access the viewProviders of the component to which it belongs')
TestBed.configureTestingModule({declarations: [SimpleComponent]}); .it('should instantiate view providers lazily', () => {
TestBed.overrideComponent( let created = false;
SimpleComponent, TestBed.configureTestingModule({declarations: [SimpleComponent]});
{set: {viewProviders: [{provide: 'service', useFactory: () => created = true}]}}); TestBed.overrideComponent(
const el = createComponent('<div simpleComponent></div>'); SimpleComponent,
{set: {viewProviders: [{provide: 'service', useFactory: () => created = true}]}});
const el = createComponent('<div simpleComponent></div>');
expect(created).toBe(false); expect(created).toBe(false);
el.children[0].injector.get('service'); el.children[0].injector.get('service');
expect(created).toBe(true); expect(created).toBe(true);
}); });
it('should not instantiate other directives that depend on viewProviders providers (same element)', it('should not instantiate other directives that depend on viewProviders providers (same element)',
() => { () => {
@ -552,8 +557,8 @@ class TestComp {
.toEqual('parentService'); .toEqual('parentService');
}); });
fixmeIvy('unknown').it( fixmeIvy('FW-890: Overridden providers are not found during dependency resolution')
'should instantiate directives that depend on providers of a component', () => { .it('should instantiate directives that depend on providers of a component', () => {
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
TestBed.overrideComponent( TestBed.overrideComponent(
SimpleComponent, SimpleComponent,
@ -565,8 +570,8 @@ class TestComp {
.toEqual('hostService'); .toEqual('hostService');
}); });
fixmeIvy('unknown').it( fixmeIvy('FW-890: Overridden providers are not found during dependency resolution')
'should instantiate directives that depend on view providers of a component', () => { .it('should instantiate directives that depend on view providers of a component', () => {
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
TestBed.overrideComponent( TestBed.overrideComponent(
SimpleComponent, SimpleComponent,
@ -578,19 +583,19 @@ class TestComp {
.toEqual('hostService'); .toEqual('hostService');
}); });
fixmeIvy('unknown').it( fixmeIvy('FW-890: Overridden providers are not found during dependency resolution')
'should instantiate directives in a root embedded view that depend on view providers of a component', .it('should instantiate directives in a root embedded view that depend on view providers of a component',
() => { () => {
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
TestBed.overrideComponent( TestBed.overrideComponent(
SimpleComponent, SimpleComponent,
{set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
TestBed.overrideComponent( TestBed.overrideComponent(
SimpleComponent, {set: {template: '<div *ngIf="true" needsService></div>'}}); SimpleComponent, {set: {template: '<div *ngIf="true" needsService></div>'}});
const el = createComponent('<div simpleComponent></div>'); const el = createComponent('<div simpleComponent></div>');
expect(el.children[0].children[0].injector.get(NeedsService).service) expect(el.children[0].children[0].injector.get(NeedsService).service)
.toEqual('hostService'); .toEqual('hostService');
}); });
it('should instantiate directives that depend on instances in the app injector', () => { it('should instantiate directives that depend on instances in the app injector', () => {
TestBed.configureTestingModule({declarations: [NeedsAppService]}); TestBed.configureTestingModule({declarations: [NeedsAppService]});
@ -734,8 +739,8 @@ class TestComp {
expect(d.dependency).toBeAnInstanceOf(SimpleComponent); expect(d.dependency).toBeAnInstanceOf(SimpleComponent);
}); });
fixmeIvy('unknown').it( obsoleteInIvy('@Host() / @Self() no longer looks in module injector')
'should instantiate host views for components that have a @Host dependency ', () => { .it('should instantiate host views for components that have a @Host dependency ', () => {
TestBed.configureTestingModule({declarations: [NeedsHostAppService]}); TestBed.configureTestingModule({declarations: [NeedsHostAppService]});
const el = createComponent('', [], NeedsHostAppService); const el = createComponent('', [], NeedsHostAppService);
expect(el.componentInstance.service).toEqual('appService'); expect(el.componentInstance.service).toEqual('appService');
@ -764,24 +769,25 @@ class TestComp {
.toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]'); .toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]');
}); });
fixmeIvy('unknown').it( fixmeIvy('FW-638: Exception thrown when getting VCRef.parentInjector on the root view')
'should allow to use the NgModule injector from a root ViewContainerRef.parentInjector', .it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
() => { () => {
@Component({template: ''}) @Component({template: ''})
class MyComp { class MyComp {
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
} }
const compFixture = TestBed const compFixture =
.configureTestingModule({ TestBed
declarations: [MyComp], .configureTestingModule({
providers: [{provide: 'someToken', useValue: 'someValue'}] declarations: [MyComp],
}) providers: [{provide: 'someToken', useValue: 'someValue'}]
.createComponent(MyComp); })
.createComponent(MyComp);
expect(compFixture.componentInstance.vc.parentInjector.get('someToken')) expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
.toBe('someValue'); .toBe('someValue');
}); });
}); });
describe('static attributes', () => { describe('static attributes', () => {
@ -812,23 +818,23 @@ class TestComp {
.toBe(el.children[0].nativeElement); .toBe(el.children[0].nativeElement);
}); });
fixmeIvy('unknown').it( it('should inject ChangeDetectorRef of the component\'s view into the component', () => {
'should inject ChangeDetectorRef of the component\'s view into the component', () => { TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>'); cf.detectChanges();
cf.detectChanges(); const compEl = cf.debugElement.children[0];
const compEl = cf.debugElement.children[0]; const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); comp.counter = 1;
comp.counter = 1; cf.detectChanges();
cf.detectChanges(); expect(compEl.nativeElement).toHaveText('0');
expect(compEl.nativeElement).toHaveText('0'); comp.changeDetectorRef.markForCheck();
comp.changeDetectorRef.markForCheck(); cf.detectChanges();
cf.detectChanges(); expect(compEl.nativeElement).toHaveText('1');
expect(compEl.nativeElement).toHaveText('1'); });
});
fixmeIvy('unknown').it( fixmeIvy(
'should inject ChangeDetectorRef of the containing component into directives', () => { 'FW-893: expect(changeDetectorRef).toEqual(otherChangeDetectorRef) creates an infinite loop')
.it('should inject ChangeDetectorRef of the containing component into directives', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: declarations:
[PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
@ -858,51 +864,45 @@ class TestComp {
expect(compEl.nativeElement).toHaveText('1'); expect(compEl.nativeElement).toHaveText('1');
}); });
fixmeIvy('unknown').it( it('should inject ChangeDetectorRef of a same element component into a directive', () => {
'should inject ChangeDetectorRef of a same element component into a directive', () => { TestBed.configureTestingModule(
TestBed.configureTestingModule({ {declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]});
declarations: const cf = createComponentFixture(
[PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] '<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>');
}); cf.detectChanges();
const cf = createComponentFixture( const compEl = cf.debugElement.children[0];
'<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>'); const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
cf.detectChanges(); const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef);
const compEl = cf.debugElement.children[0]; comp.counter = 1;
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); cf.detectChanges();
const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef); expect(compEl.nativeElement).toHaveText('0');
comp.counter = 1; dir.changeDetectorRef.markForCheck();
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0'); expect(compEl.nativeElement).toHaveText('1');
dir.changeDetectorRef.markForCheck(); });
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
fixmeIvy('unknown').it( it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => {
`should not inject ChangeDetectorRef of a parent element's component into a directive`, TestBed
() => { .configureTestingModule({
TestBed declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
.configureTestingModule({ })
declarations: .overrideComponent(
[PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] PushComponentNeedsChangeDetectorRef,
}) {set: {template: '<ng-content></ng-content>{{counter}}'}});
.overrideComponent( const cf = createComponentFixture(
PushComponentNeedsChangeDetectorRef, '<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>');
{set: {template: '<ng-content></ng-content>{{counter}}'}}); cf.detectChanges();
const cf = createComponentFixture( const compEl = cf.debugElement.children[0];
'<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>'); const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
cf.detectChanges(); const dirEl = compEl.children[0];
const compEl = cf.debugElement.children[0]; const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef);
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); comp.counter = 1;
const dirEl = compEl.children[0]; cf.detectChanges();
const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef); expect(compEl.nativeElement).toHaveText('0');
comp.counter = 1; dir.changeDetectorRef.markForCheck();
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0'); expect(compEl.nativeElement).toHaveText('0');
dir.changeDetectorRef.markForCheck(); });
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
});
it('should inject ViewContainerRef', () => { it('should inject ViewContainerRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]}); TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]});
@ -912,32 +912,33 @@ class TestComp {
.toBe(el.children[0].nativeElement); .toBe(el.children[0].nativeElement);
}); });
fixmeIvy('unknown').it('should inject ViewContainerRef', () => { fixmeIvy('FW-638: Exception thrown when getting VCRef.parentInjector on the root view')
@Component({template: ''}) .it('should inject ViewContainerRef', () => {
class TestComp { @Component({template: ''})
constructor(public vcr: ViewContainerRef) {} class TestComp {
} constructor(public vcr: ViewContainerRef) {}
}
@NgModule({ @NgModule({
declarations: [TestComp], declarations: [TestComp],
entryComponents: [TestComp], entryComponents: [TestComp],
}) })
class TestModule { class TestModule {
} }
const testInjector = <Injector>{ const testInjector = <Injector>{
get: (token: any, notFoundValue: any) => get: (token: any, notFoundValue: any) =>
token === 'someToken' ? 'someNewValue' : notFoundValue token === 'someToken' ? 'someNewValue' : notFoundValue
}; };
const compFactory = TestBed.configureTestingModule({imports: [TestModule]}) const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
.get(ComponentFactoryResolver) .get(ComponentFactoryResolver)
.resolveComponentFactory(TestComp); .resolveComponentFactory(TestComp);
const component = compFactory.create(testInjector); const component = compFactory.create(testInjector);
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue'); expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
}); });
fixmeIvy('unknown').it('should inject TemplateRef', () => { it('should inject TemplateRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]}); TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
const el = const el =
createComponent('<ng-template needsViewContainerRef needsTemplateRef></ng-template>'); createComponent('<ng-template needsViewContainerRef needsTemplateRef></ng-template>');
@ -960,21 +961,25 @@ class TestComp {
}); });
describe('pipes', () => { describe('pipes', () => {
fixmeIvy('unknown').it('should instantiate pipes that have dependencies', () => { fixmeIvy('FW-890: Overridden providers are not found during dependency resolution')
TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]}); .it('should instantiate pipes that have dependencies', () => {
TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]});
const el = createComponent( const el = createComponent(
'<div [simpleDirective]="true | pipeNeedsService"></div>', '<div [simpleDirective]="true | pipeNeedsService"></div>',
[{provide: 'service', useValue: 'pipeService'}]); [{provide: 'service', useValue: 'pipeService'}]);
expect(el.children[0].injector.get(SimpleDirective).value.service).toEqual('pipeService'); expect(el.children[0].injector.get(SimpleDirective).value.service)
}); .toEqual('pipeService');
});
fixmeIvy('unknown').it('should overwrite pipes with later entry in the pipes array', () => { fixmeIvy('FW-894: Pipes don\'t overwrite pipes with later entry in the pipes array')
TestBed.configureTestingModule( .it('should overwrite pipes with later entry in the pipes array', () => {
{declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]}); TestBed.configureTestingModule(
const el = createComponent('<div [simpleDirective]="true | duplicatePipe"></div>'); {declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]});
expect(el.children[0].injector.get(SimpleDirective).value).toBeAnInstanceOf(DuplicatePipe2); const el = createComponent('<div [simpleDirective]="true | duplicatePipe"></div>');
}); expect(el.children[0].injector.get(SimpleDirective).value)
.toBeAnInstanceOf(DuplicatePipe2);
});
it('should inject ChangeDetectorRef into pipes', () => { it('should inject ChangeDetectorRef into pipes', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -988,20 +993,21 @@ class TestComp {
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef); expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef);
}); });
fixmeIvy('unknown').it('should cache pure pipes', () => { modifiedInIvy('Pure pipes are instantiated differently in view engine and ivy')
TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]}); .it('should cache pure pipes', () => {
const el = createComponent( TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]});
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>' + const el = createComponent(
'<div *ngFor="let x of [1,2]" [simpleDirective]="true | purePipe"></div>'); '<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>' +
const purePipe1 = el.children[0].injector.get(SimpleDirective).value; '<div *ngFor="let x of [1,2]" [simpleDirective]="true | purePipe"></div>');
const purePipe2 = el.children[1].injector.get(SimpleDirective).value; const purePipe1 = el.children[0].injector.get(SimpleDirective).value;
const purePipe3 = el.children[2].injector.get(SimpleDirective).value; const purePipe2 = el.children[1].injector.get(SimpleDirective).value;
const purePipe4 = el.children[3].injector.get(SimpleDirective).value; const purePipe3 = el.children[2].injector.get(SimpleDirective).value;
expect(purePipe1).toBeAnInstanceOf(PurePipe); const purePipe4 = el.children[3].injector.get(SimpleDirective).value;
expect(purePipe2).toBe(purePipe1); expect(purePipe1).toBeAnInstanceOf(PurePipe);
expect(purePipe3).toBe(purePipe1); expect(purePipe2).toBe(purePipe1);
expect(purePipe4).toBe(purePipe1); expect(purePipe3).toBe(purePipe1);
}); expect(purePipe4).toBe(purePipe1);
});
it('should not cache impure pipes', () => { it('should not cache impure pipes', () => {
TestBed.configureTestingModule({declarations: [SimpleDirective, ImpurePipe]}); TestBed.configureTestingModule({declarations: [SimpleDirective, ImpurePipe]});