Skip to content

Commit c29ca11

Browse files
authored
Setup methods (#30)
* Refactor types * Add `compileEach()` and `setup(..)` * Add types and make module configuration possible outside of beforeEach setup * Use set objects into factory
1 parent 105e752 commit c29ca11

File tree

11 files changed

+223
-114
lines changed

11 files changed

+223
-114
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,120 @@
1-
import { Component, EnvironmentProviders, ModuleWithProviders, Provider, ProviderToken, Type } from '@angular/core';
2-
import { ComponentFixture, TestBed, TestBedStatic, TestModuleMetadata } from '@angular/core/testing';
1+
import { Component, ProviderToken, Type } from '@angular/core';
2+
import { ComponentFixture, TestBed, TestBedStatic } from '@angular/core/testing';
33
import { MaybeArray, Merge, NonEmptyString, Nullable } from '../../models/shared.model';
44
import { assertComponent } from './assert-component';
55
import { assertComponentFixture } from './assert-fixture';
66
import { getComponentAnnotation } from './component-annotation';
7+
import { buildComponentTools } from './component-tools';
78
import { ComponentTestBed } from './models';
9+
import { AnyProvider, Declaration, Importation } from './models/metadata-type.model';
10+
import { ComponentSetup } from './models/setup-fn.model';
811
import { InjectionStore } from './store';
912

10-
export class ComponentTestBedFactory<ComponentType, Injected extends InjectionStore = InjectionStore> {
13+
export class ComponentTestBedFactory<ComponentType, Store extends InjectionStore = InjectionStore> {
1114

1215
public constructor(
1316
private rootComponent: Type<ComponentType>,
1417
) {
15-
this.annotation = getComponentAnnotation(rootComponent);
16-
assertComponent(rootComponent, this.annotation);
17-
}
18+
const annotation: Nullable<Component> = getComponentAnnotation(rootComponent);
19+
assertComponent(rootComponent, annotation);
1820

19-
private readonly annotation: Nullable<Component>;
21+
(annotation?.standalone)
22+
? this.import(this.rootComponent)
23+
: this.declare(this.rootComponent);
24+
}
2025

2126
private testBed: TestBedStatic = TestBed;
2227
private fixture: ComponentFixture<ComponentType> = null!;
23-
private injected: Map<ProviderToken<any>, string> = new Map();
28+
29+
private imports: Set<Importation> = new Set();
30+
private declarations: Set<Declaration> = new Set();
31+
private providers: Set<AnyProvider> = new Set();
32+
33+
private injectedMap: Map<ProviderToken<any>, string> = new Map();
2434

2535
/**
2636
* Import one module or one standalone component / directive / pipe into the `ComponentTestBed`.
2737
*/
28-
public import(importation: Type<any> | ModuleWithProviders<any>): this
38+
public import(importation: Importation): this
2939
/**
3040
* Import many modules or many standalone components / directives / pipes into the `ComponentTestBed`.
3141
*/
32-
public import(imports: (Type<any> | ModuleWithProviders<any>)[]): this
33-
public import(oneOrManyImports: MaybeArray<Type<any> | ModuleWithProviders<any>>): this
34-
public import(oneOrManyImports: MaybeArray<Type<any> | ModuleWithProviders<any>>): this {
35-
return this.configure('imports', oneOrManyImports);
42+
public import(imports: Importation[]): this
43+
public import(oneOrManyImports: MaybeArray<Importation>): this {
44+
makeArray(oneOrManyImports).forEach(v => this.imports.add(v));
45+
return this;
3646
}
3747

3848
/**
3949
* Add one provider into the `ComponentTestBed`.
4050
*/
41-
public provide(provider: Provider | EnvironmentProviders): this
51+
public provide(provider: AnyProvider): this
4252
/**
4353
* Add many providers into the `ComponentTestBed`.
4454
*/
45-
public provide(providers: (Provider | EnvironmentProviders)[]): this
46-
public provide(oneOrManyProviders: MaybeArray<Provider | EnvironmentProviders>): this
47-
public provide(oneOrManyProviders: MaybeArray<Provider | EnvironmentProviders>): this {
48-
return this.configure('providers', oneOrManyProviders);
55+
public provide(providers: AnyProvider[]): this
56+
public provide(oneOrManyProviders: MaybeArray<AnyProvider>): this {
57+
makeArray(oneOrManyProviders).forEach(v => this.providers.add(v));
58+
return this;
4959
}
5060

5161
/**
5262
* Declare one non standalone component, directive or pipe into the `ComponentTestBed`.
5363
*/
54-
public declare(declaration: Type<any>): this
64+
public declare(declaration: Declaration): this
5565
/**
5666
* Declare many non standalone components, directives and pipes into `ComponentTestBed`.
5767
*/
58-
public declare(declarations: Type<any>[]): this
59-
public declare(oneOrManyDeclarations: MaybeArray<Type<any>>): this
60-
public declare(oneOrManyDeclarations: MaybeArray<Type<any>>): this {
61-
return this.configure('declarations', oneOrManyDeclarations);
68+
public declare(declarations: Declaration[]): this
69+
public declare(oneOrManyDeclarations: MaybeArray<Declaration>): this {
70+
makeArray(oneOrManyDeclarations).forEach(v => this.declarations.add(v));
71+
return this;
72+
}
73+
74+
private async configureModule(): Promise<void> {
75+
this.testBed.configureTestingModule({
76+
imports: [...this.imports.values()],
77+
declarations: [...this.declarations.values()],
78+
providers: [...this.providers.values()],
79+
});
80+
81+
await this.testBed.compileComponents();
6282
}
6383

64-
public inject<key extends string, T>(name: NonEmptyString<key>, token: ProviderToken<T>): ComponentTestBed<ComponentType, InjectionStore<Merge<Injected['injected'] & { [k in key]: T }>>> {
65-
this.injected.set(token, name);
84+
/**
85+
* Inject an instance by token into the `ComponentTestBed`.
86+
*
87+
* Retrieve it into the `ComponentTools.injected` by autocompletion.
88+
* @param name the key to access the instance.
89+
* @param token the provider token.
90+
*/
91+
public inject<key extends string, T>(name: NonEmptyString<key>, token: ProviderToken<T>): ComponentTestBed<ComponentType, InjectionStore<Merge<Store['injected'] & { [k in key]: T }>>> {
92+
this.injectedMap.set(token, name);
6693
return this as any;
6794
}
6895

69-
private configure(key: keyof TestModuleMetadata, itemS: MaybeArray<unknown>): this {
70-
const defs: unknown[] = Array.isArray(itemS) ? itemS : [itemS];
71-
this.testBed.configureTestingModule({ [key]: defs });
72-
return this;
96+
/**
97+
* Compile the `ComponentTestBed` before each test.
98+
*/
99+
public compileEach(): void {
100+
beforeEach(() => this.compile());
101+
}
102+
103+
/**
104+
* Compile the `ComponentTestBed` to create the `rootComponent` fixture.
105+
*/
106+
public async compile(): Promise<void> {
107+
await this.configureModule();
108+
this.fixture = this.testBed.createComponent(this.rootComponent);
109+
}
110+
111+
/**
112+
* Not compatible with `beforeAll` and `afterAll`.
113+
*/
114+
public setup(action: ComponentSetup<ComponentType, Store['injected']>): jasmine.ImplementationCallback {
115+
return (action.length > 1)
116+
? (done: DoneFn) => action(buildComponentTools(this), done)
117+
: () => action(buildComponentTools(this), null!);
73118
}
74119

75120
/**
@@ -83,17 +128,8 @@ export class ComponentTestBedFactory<ComponentType, Injected extends InjectionSt
83128
expect(this.fixture.componentInstance).toBeTruthy();
84129
});
85130
}
131+
}
86132

87-
/**
88-
* Compile the `ComponentTestBed` to create the `rootComponent` fixture.
89-
*/
90-
public async compile(): Promise<void> {
91-
(this.annotation?.standalone)
92-
? this.import(this.rootComponent)
93-
: this.declare(this.rootComponent);
94-
95-
await this.testBed.compileComponents();
96-
97-
this.fixture = this.testBed.createComponent(this.rootComponent);
98-
}
133+
function makeArray<T>(itemS: MaybeArray<T>): T[] {
134+
return (Array.isArray(itemS)) ? itemS : [itemS];
99135
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
import { Type } from '@angular/core';
2-
import { ComponentFixture } from '@angular/core/testing';
3-
import { assertComponentFixture } from './assert-fixture';
4-
import { buildComponentActionTools } from './component-action-tools';
5-
import { buildComponentQueryTools } from './component-query-tools';
62
import { ComponentTestBedFactory } from './component-test-bed-factory';
7-
import { ComponentExtraOptions } from './models';
8-
import { ComponentActionTools } from './models/component-action-tools.model';
9-
import { ComponentQueryTools } from './models/component-query-tools.model';
3+
import { buildComponentTools } from './component-tools';
4+
import { ComponentExtraOptions, ComponentTools } from './models';
105
import { ComponentAssertion, ComponentTestBed } from './models/component-test-bed.models';
11-
import { InjectionStore } from './store';
12-
import { buildInjected } from './store/injected';
136

147
/**
158
* Creates a new `ComponentTestBed` to configure the test bed and wrap the assertion test.
@@ -22,19 +15,11 @@ export function componentTestBed<T>(rootComponent: Type<T>): ComponentTestBed<T>
2215
const { startDetectChanges = true } = options;
2316

2417
const expectationFn = (done: DoneFn = null!) => {
25-
const fixture: ComponentFixture<T> = factory['fixture'];
26-
assertComponentFixture(fixture);
18+
const tools: ComponentTools<T> = buildComponentTools(factory);
2719

28-
const { componentInstance: component, debugElement: debug } = fixture;
29-
const { injector } = debug;
20+
if (startDetectChanges) tools.fixture.detectChanges();
3021

31-
const query: ComponentQueryTools = buildComponentQueryTools(fixture);
32-
const action: ComponentActionTools = buildComponentActionTools(fixture);
33-
const injected: InjectionStore['injected'] = buildInjected(factory);
34-
35-
if (startDetectChanges) fixture.detectChanges();
36-
37-
return assertionCb({ fixture, component, injector, query, action, injected, debug }, done);
22+
return assertionCb(tools, done);
3823
};
3924

4025
return (assertionCb.length > 1)
@@ -45,16 +30,16 @@ export function componentTestBed<T>(rootComponent: Type<T>): ComponentTestBed<T>
4530
return mergeFactoryToFn(factory, tb);
4631
}
4732

48-
function mergeFactoryToFn<T>(factory: ComponentTestBedFactory<T, any>, tb: ComponentTestBed<T, any>): ComponentTestBed<T> {
49-
tb.import = (imports) => {
33+
function mergeFactoryToFn<T>(factory: ComponentTestBedFactory<T>, tb: ComponentTestBed<T, any>): ComponentTestBed<T> {
34+
tb.import = (imports: any) => {
5035
factory.import(imports);
5136
return tb;
5237
};
53-
tb.provide = (providers) => {
38+
tb.provide = (providers: any) => {
5439
factory.provide(providers);
5540
return tb;
5641
};
57-
tb.declare = (declarations) => {
42+
tb.declare = (declarations: any) => {
5843
factory.declare(declarations);
5944
return tb;
6045
};
@@ -63,8 +48,10 @@ function mergeFactoryToFn<T>(factory: ComponentTestBedFactory<T, any>, tb: Compo
6348
return tb;
6449
};
6550

66-
tb.shouldCreate = factory.shouldCreate.bind(factory);
6751
tb.compile = factory.compile.bind(factory);
52+
tb.compileEach = factory.compileEach.bind(factory);
53+
tb.setup = factory.setup.bind(factory);
54+
tb.shouldCreate = factory.shouldCreate.bind(factory);
6855

6956
return tb;
7057
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ProviderToken } from '@angular/core';
2+
import { ComponentFixture } from '@angular/core/testing';
3+
import { assertComponentFixture } from './assert-fixture';
4+
import { buildComponentActionTools } from './component-action-tools';
5+
import { buildComponentQueryTools } from './component-query-tools';
6+
import { ComponentTestBedFactory } from './component-test-bed-factory';
7+
import { ComponentTools } from './models';
8+
import { ComponentActionTools } from './models/component-action-tools.model';
9+
import { ComponentQueryTools } from './models/component-query-tools.model';
10+
import { InjectionStore } from './store';
11+
import { buildInjected } from './store/injected';
12+
13+
export function buildComponentTools(factory: ComponentTestBedFactory<any>): ComponentTools<any, any> {
14+
const fixture: ComponentFixture<unknown> = factory['fixture'];
15+
assertComponentFixture(fixture);
16+
17+
const injectedMap: Map<ProviderToken<any>, string> = factory['injectedMap'];
18+
19+
const { componentInstance: component, debugElement: debug } = fixture;
20+
const { injector } = debug;
21+
22+
const query: ComponentQueryTools = buildComponentQueryTools(fixture);
23+
const action: ComponentActionTools = buildComponentActionTools(fixture);
24+
const injected: InjectionStore['injected'] = buildInjected(fixture, injectedMap);
25+
26+
return { fixture, component, injector, query, action, injected, debug };
27+
}

projects/ngx-testing-tools/src/lib/components/test-bed/models/component-tools.model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { InjectionStore } from '../store';
44
import { ComponentActionTools } from './component-action-tools.model';
55
import { ComponentQueryTools } from './component-query-tools.model';
66

7-
export interface ComponentTools<T, I extends {}> extends InjectionStore<I> {
7+
export interface ComponentTools<T, I extends {} = {}> extends InjectionStore<I> {
88
fixture: ComponentFixture<T>;
99
component: T;
1010
injector: Injector;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, ModuleWithProviders, Type, TypeProvider, ValueProvider } from '@angular/core';
2+
3+
export type Importation =
4+
| Type<any>
5+
| ModuleWithProviders<any>
6+
7+
export type AnyProvider =
8+
| TypeProvider
9+
| ValueProvider
10+
| ClassProvider
11+
| ConstructorProvider
12+
| ExistingProvider
13+
| FactoryProvider
14+
| EnvironmentProviders
15+
16+
export type Declaration =
17+
| Type<any>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { ComponentTools } from './component-tools.model';
2+
3+
export type ComponentSetup<T, I extends {}> = (tools: ComponentTools<T, I>, done: DoneFn) => ReturnType<jasmine.ImplementationCallback>
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { ProviderToken } from '@angular/core';
2+
import { ComponentFixture } from '@angular/core/testing';
13
import { fromInjector } from '../../../injector';
2-
import { ComponentTestBedFactory } from '../component-test-bed-factory';
34
import { InjectionStore } from './models/injected-store.model';
45

5-
export function buildInjected(factory: ComponentTestBedFactory<unknown>): InjectionStore['injected'] {
6+
export function buildInjected(fixture: ComponentFixture<unknown>, injectedMap: Map<ProviderToken<any>, string>): InjectionStore['injected'] {
67
const injected: InjectionStore<any>['injected'] = {};
7-
for (const [key, value] of factory['injected'].entries()) {
8-
injected[value] = fromInjector(factory['fixture'], key);
8+
for (const [key, value] of injectedMap.entries()) {
9+
injected[value] = fromInjector(fixture, key);
910
}
1011
return injected;
1112
}

projects/ngx-testing-tools/src/lib/models/shared.model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type MaybeArray<T> =
88

99
export type NonEmptyString<T extends string> = T extends '' ? never : T;
1010

11-
export type Merge<T> = {
11+
export type Merge<T extends {}> = {
1212
[K in keyof T]: T[K];
1313
} & {};
1414

projects/ngx-testing-tools/src/tests/fixtures/mocks/test-compiler.mock.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Provider, Type } from '@angular/core';
21
import { TestModuleMetadata } from '@angular/core/testing';
2+
import { AnyProvider, Declaration, Importation } from '../../../lib/components/test-bed/models/metadata-type.model';
33

44
export class MockTestCompiler {
55

6-
public declarations: Type<any>[] = [];
7-
public imports: Type<any>[] = [];
8-
public providers: Provider[] = [];
6+
public declarations: Declaration[] = [];
7+
public imports: Importation[] = [];
8+
public providers: AnyProvider[] = [];
99

1010
public configureTestingModule(moduleDef: TestModuleMetadata): void {
1111
if (Array.isArray(moduleDef.declarations)) {

0 commit comments

Comments
 (0)