test(core): convert providers test utility to TestBed (#46527)

This commit convers the providers_helper utility function
`expectProvidersScenario` to use the TestBed internally instead of
`ComponentFixture` and handwritten defs. This required several adjustments:

* Making the `ngModule`s passed in to the helper utility real NgModules.
* Using `ViewEncapsulation.None` for the test components (stabilizes the
  generated HTML).
* Convert handwritten test components & directives into JIT-compiled
  standalone equivalents.

PR Close #46527
This commit is contained in:
Alex Rickabaugh
2022-06-27 11:44:39 -07:00
committed by Dylan Hunn
parent 9c81746e99
commit b8d6ffbc9f
2 changed files with 112 additions and 133 deletions
+95 -125
View File
@@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InjectorType, Provider, ɵcreateInjector as createInjector, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵProvidersFeature, ɵɵtext} from '@angular/core';
import {RenderFlags} from '@angular/core/src/render3';
import {ComponentFixture} from '@angular/core/test/render3/render_util';
import {Component, Directive, Provider, Type, ViewEncapsulation} from '@angular/core';
import {TestBed} from '@angular/core/testing';
export interface ComponentTest {
providers?: Provider[];
@@ -24,154 +23,125 @@ export function expectProvidersScenario(defs: {
parent?: ComponentTest,
viewChild?: ComponentTest,
contentChild?: ComponentTest,
ngModule?: InjectorType<any>,
ngModule?: Type<any>,
}): void {
function testComponentInjection<T>(def: ComponentTest|undefined, instance: T): T {
if (def) {
def.componentAssertion && def.componentAssertion();
}
return instance;
}
function testDirectiveInjection<T>(def: ComponentTest|undefined, instance: T): T {
if (def) {
def.directiveAssertion && def.directiveAssertion();
}
return instance;
}
@Component({
standalone: true,
selector: 'view-child',
template: 'view-child',
encapsulation: ViewEncapsulation.None,
providers: defs.viewChild?.providers ?? [],
viewProviders: defs.viewChild?.viewProviders ?? [],
})
class ViewChildComponent {
static ɵfac = () => testComponentInjection(defs.viewChild, new ViewChildComponent());
static ɵcmp = ɵɵdefineComponent({
type: ViewChildComponent,
selectors: [['view-child']],
decls: 1,
vars: 0,
template:
function(fs: RenderFlags, ctx: ViewChildComponent) {
if (fs & RenderFlags.Create) {
ɵɵtext(0, 'view-child');
}
},
features: defs.viewChild &&
[ɵɵProvidersFeature(defs.viewChild.providers || [], defs.viewChild.viewProviders || [])]
});
constructor() {
defs.viewChild?.componentAssertion?.();
}
}
@Directive({
standalone: true,
selector: 'view-child',
providers: defs.viewChild?.directiveProviders ?? [],
})
class ViewChildDirective {
static ɵfac = () => testDirectiveInjection(defs.viewChild, new ViewChildDirective());
static ɵdir = ɵɵdefineDirective({
type: ViewChildDirective,
selectors: [['view-child']],
features: defs.viewChild && [ɵɵProvidersFeature(defs.viewChild.directiveProviders || [])],
});
constructor() {
defs.viewChild?.directiveAssertion?.();
}
}
@Component({
standalone: true,
selector: 'content-child',
template: 'content-child',
encapsulation: ViewEncapsulation.None,
providers: defs.contentChild?.providers ?? [],
viewProviders: defs.contentChild?.viewProviders ?? [],
})
class ContentChildComponent {
static ɵfac =
() => {
return testComponentInjection(defs.contentChild, new ContentChildComponent());
}
static ɵcmp = ɵɵdefineComponent({
type: ContentChildComponent,
selectors: [['content-child']],
decls: 1,
vars: 0,
template:
function(fs: RenderFlags, ctx: ParentComponent) {
if (fs & RenderFlags.Create) {
ɵɵtext(0, 'content-child');
}
},
features: defs.contentChild &&
[ɵɵProvidersFeature(
defs.contentChild.providers || [], defs.contentChild.viewProviders || [])],
});
constructor() {
defs.contentChild?.componentAssertion?.();
}
}
@Directive({
standalone: true,
selector: 'content-child',
providers: defs.contentChild?.directiveProviders ?? [],
})
class ContentChildDirective {
static ɵfac =
() => {
return testDirectiveInjection(defs.contentChild, new ContentChildDirective());
}
static ɵdir = ɵɵdefineDirective({
type: ContentChildDirective,
selectors: [['content-child']],
features: defs.contentChild &&
[ɵɵProvidersFeature(defs.contentChild.directiveProviders || [])],
});
constructor() {
defs.contentChild?.directiveAssertion?.();
}
}
@Component({
standalone: true,
imports: [ViewChildComponent, ViewChildDirective],
selector: 'parent',
template: '<view-child></view-child>',
encapsulation: ViewEncapsulation.None,
providers: defs.parent?.providers ?? [],
viewProviders: defs.parent?.viewProviders ?? [],
})
class ParentComponent {
static ɵfac = () => testComponentInjection(defs.parent, new ParentComponent());
static ɵcmp = ɵɵdefineComponent({
type: ParentComponent,
selectors: [['parent']],
decls: 1,
vars: 0,
template:
function(fs: RenderFlags, ctx: ParentComponent) {
if (fs & RenderFlags.Create) {
ɵɵelement(0, 'view-child');
}
},
features: defs.parent &&
[ɵɵProvidersFeature(defs.parent.providers || [], defs.parent.viewProviders || [])],
dependencies: [ViewChildComponent, ViewChildDirective]
});
constructor() {
defs.parent?.componentAssertion?.();
}
}
@Directive({
standalone: true,
selector: 'parent',
providers: defs.parent?.directiveProviders ?? [],
})
class ParentDirective {
static ɵfac = () => testDirectiveInjection(defs.parent, new ParentDirective());
static ɵdir = ɵɵdefineDirective({
type: ParentDirective,
selectors: [['parent']],
features: defs.parent && [ɵɵProvidersFeature(defs.parent.directiveProviders || [])],
});
constructor() {
defs.parent?.directiveAssertion?.();
}
}
@Directive({
standalone: true,
selector: 'parent',
providers: defs.parent?.directive2Providers ?? [],
})
class ParentDirective2 {
static ɵfac = () => testDirectiveInjection(defs.parent, new ParentDirective2());
static ɵdir = ɵɵdefineDirective({
type: ParentDirective2,
selectors: [['parent']],
features: defs.parent && [ɵɵProvidersFeature(defs.parent.directive2Providers || [])],
});
constructor() {
defs.parent?.directiveAssertion?.();
}
}
@Component({
standalone: true,
imports: [
ParentComponent,
// Note: tests are sensitive to the ordering here - the providers from `ParentDirective`
// should override the providers from `ParentDirective2`.
ParentDirective2,
ParentDirective,
ContentChildComponent,
ContentChildDirective,
],
template: '<parent><content-child></content-child></parent>',
providers: defs.app?.providers ?? [],
viewProviders: defs.app?.viewProviders ?? [],
})
class App {
static ɵfac = () => testComponentInjection(defs.app, new App());
static ɵcmp = ɵɵdefineComponent({
type: App,
selectors: [['app']],
decls: 2,
vars: 0,
template:
function(fs: RenderFlags, ctx: App) {
if (fs & RenderFlags.Create) {
ɵɵelementStart(0, 'parent');
ɵɵelement(1, 'content-child');
ɵɵelementEnd();
}
},
features: defs.app &&
[ɵɵProvidersFeature(defs.app.providers || [], defs.app.viewProviders || [])],
dependencies:
[
ParentComponent, ParentDirective2, ParentDirective, ContentChildComponent,
ContentChildDirective
]
});
constructor() {
defs.app?.componentAssertion?.();
}
}
const fixture = new ComponentFixture(
App, {injector: defs.ngModule ? createInjector(defs.ngModule) : undefined});
expect(fixture.html).toEqual('<parent><view-child>view-child</view-child></parent>');
TestBed.configureTestingModule({
imports: defs.ngModule ? [defs.ngModule] : [],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('<parent><view-child>view-child</view-child></parent>');
fixture.destroy();
}
+17 -8
View File
@@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, InjectorType, Provider, RendererFactory2, Type, ViewContainerRef, ɵcreateInjector as createInjector, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵdefineNgModule, ɵɵinject} from '../../src/core';
import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, NgModule, RendererFactory2, Type, ViewContainerRef, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵdefineNgModule, ɵɵinject} from '../../src/core';
import {forwardRef} from '../../src/di/forward_ref';
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetInheritedFactory, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵelement, ɵɵgetInheritedFactory, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
import {getInjector} from '../../src/render3/util/discovery_utils';
@@ -380,8 +380,10 @@ describe('providers', () => {
});
describe('single', () => {
@NgModule({
providers: [{provide: String, useValue: 'From module'}],
})
class MyModule {
static ɵinj = ɵɵdefineInjector({providers: [{provide: String, useValue: 'From module'}]});
}
describe('without directives', () => {
@@ -646,9 +648,10 @@ describe('providers', () => {
});
describe('multi', () => {
@NgModule({
providers: [{provide: String, useValue: 'From module', multi: true}],
})
class MyModule {
static ɵinj =
ɵɵdefineInjector({providers: [{provide: String, useValue: 'From module', multi: true}]});
}
describe('without directives', () => {
@@ -963,8 +966,10 @@ describe('providers', () => {
});
it('should work with a module', () => {
@NgModule({
providers: [{provide: String, useValue: 'From module'}],
})
class MyModule {
static ɵinj = ɵɵdefineInjector({providers: [{provide: String, useValue: 'From module'}]});
}
@Injectable({providedIn: MyModule})
@@ -1172,8 +1177,10 @@ describe('providers', () => {
});
describe('injection flags', () => {
@NgModule({
providers: [{provide: String, useValue: 'Module'}],
})
class MyModule {
static ɵinj = ɵɵdefineInjector({providers: [{provide: String, useValue: 'Module'}]});
}
it('should not fall through to ModuleInjector if flags limit the scope', () => {
expectProvidersScenario({
@@ -1292,7 +1299,9 @@ describe('providers', () => {
describe('getInheritedFactory on class with custom decorator', () => {
function addFoo() {
return (constructor: Type<any>): any => {
const decoratedClass = class Extender extends constructor { foo = 'bar'; };
const decoratedClass = class Extender extends constructor {
foo = 'bar';
};
return decoratedClass;
};
}