Skip to content

Commit 16b6447

Browse files
committed
feat: add imports option to configure
1 parent 9baca38 commit 16b6447

File tree

8 files changed

+116
-56
lines changed

8 files changed

+116
-56
lines changed

projects/testing-library/src/lib/models.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { Type, DebugElement } from '@angular/core';
22
import { ComponentFixture } from '@angular/core/testing';
33
import { Routes } from '@angular/router';
4-
import { BoundFunction, FireObject, Queries, queries, waitFor, waitForElementToBeRemoved } from '@testing-library/dom';
4+
import {
5+
BoundFunction,
6+
FireObject,
7+
Queries,
8+
queries,
9+
waitFor,
10+
waitForElementToBeRemoved,
11+
Config as dtlConfig,
12+
} from '@testing-library/dom';
513
import { UserEvents } from './user-events';
614
import { OptionsReceived } from 'pretty-format';
715

@@ -304,3 +312,5 @@ export interface RenderDirectiveOptions<DirectiveType, WrapperType, Q extends Qu
304312
wrapper?: Type<WrapperType>;
305313
componentProperties?: Partial<any>;
306314
}
315+
316+
export type Config = dtlConfig & { defaultImports: any[] };

projects/testing-library/src/lib/testing-library.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
queries as dtlQueries,
1717
waitForOptions as dtlWaitForOptions,
1818
configure as dtlConfigure,
19+
getConfig as dtlGetConfig,
20+
Config as dtlConfig,
1921
} from '@testing-library/dom';
20-
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
22+
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult, Config } from './models';
2123
import { createSelectOptions, createType, tab } from './user-events';
2224

2325
const mountedFixtures = new Set<ComponentFixture<any>>();
@@ -59,9 +61,11 @@ export async function render<SutType, WrapperType = SutType>(
5961
removeAngularAttributes = false,
6062
} = renderOptions as RenderDirectiveOptions<SutType, WrapperType>;
6163

64+
const config = dtlGetConfig();
65+
6266
TestBed.configureTestingModule({
6367
declarations: addAutoDeclarations(sut, { declarations, excludeComponentDeclaration, template, wrapper }),
64-
imports: addAutoImports({ imports, routes }),
68+
imports: addAutoImports({ imports: imports.concat((config as Config).defaultImports || []), routes }),
6569
providers: [...providers],
6670
schemas: [...schemas],
6771
});
@@ -102,7 +106,7 @@ export async function render<SutType, WrapperType = SutType>(
102106
// Call ngOnChanges on initial render
103107
if (hasOnChangesHook(fixture.componentInstance)) {
104108
const changes = getChangesObj(null, fixture.componentInstance);
105-
fixture.componentInstance.ngOnChanges(changes)
109+
fixture.componentInstance.ngOnChanges(changes);
106110
}
107111

108112
if (detectChangesOnRender) {
@@ -224,20 +228,21 @@ function setComponentProperties<SutType>(
224228
}
225229

226230
function hasOnChangesHook<SutType>(componentInstance: SutType): componentInstance is SutType & OnChanges {
227-
return 'ngOnChanges' in componentInstance
228-
&& typeof (componentInstance as SutType & OnChanges).ngOnChanges === 'function';
229-
};
231+
return (
232+
'ngOnChanges' in componentInstance && typeof (componentInstance as SutType & OnChanges).ngOnChanges === 'function'
233+
);
234+
}
230235

231-
function getChangesObj<SutType>(
232-
oldProps: Partial<SutType> | null,
233-
newProps: Partial<SutType>
234-
) {
236+
function getChangesObj<SutType>(oldProps: Partial<SutType> | null, newProps: Partial<SutType>) {
235237
const isFirstChange = oldProps === null;
236-
return Object.keys(newProps).reduce<SimpleChanges>((changes, key) => ({
237-
...changes,
238-
[key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange)
239-
}), {});
240-
};
238+
return Object.keys(newProps).reduce<SimpleChanges>(
239+
(changes, key) => ({
240+
...changes,
241+
[key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange),
242+
}),
243+
{},
244+
);
245+
}
241246

242247
function addAutoDeclarations<SutType>(
243248
component: Type<SutType>,
@@ -418,14 +423,19 @@ const userEvent = {
418423
tab: tab,
419424
};
420425

426+
function configure(config: Partial<Config>) {
427+
dtlConfigure({
428+
defaultImports: config.defaultImports,
429+
} as Partial<dtlConfig>);
430+
}
431+
421432
/**
422433
* Manually export otherwise we get the following error while running Jest tests
423434
* TypeError: Cannot set property fireEvent of [object Object] which has only a getter
424435
* exports.fireEvent = fireEvent
425436
*/
426437
export {
427438
buildQueries,
428-
configure,
429439
getByLabelText,
430440
getAllByLabelText,
431441
queryByLabelText,
@@ -491,4 +501,4 @@ export {
491501
within,
492502
} from '@testing-library/dom';
493503

494-
export { fireEvent, screen, userEvent, waitFor, waitForElementToBeRemoved };
504+
export { configure, fireEvent, screen, userEvent, waitFor, waitForElementToBeRemoved };

projects/testing-library/tests/render.spec.ts

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Component, NgModule, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
22
import { NoopAnimationsModule, BrowserAnimationsModule } from '@angular/platform-browser/animations';
33
import { TestBed } from '@angular/core/testing';
4-
import { render } from '../src/public_api';
4+
import { render, configure } from '../src/public_api';
5+
import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
56

67
@Component({
78
selector: 'fixture',
@@ -40,20 +41,20 @@ describe('removeAngularAttributes', () => {
4041
});
4142
});
4243

43-
@NgModule({
44-
declarations: [FixtureComponent],
45-
})
46-
export class FixtureModule {}
47-
describe('excludeComponentDeclaration', () => {
48-
test('will throw if component is declared in an import', async () => {
49-
await render(FixtureComponent, {
50-
imports: [FixtureModule],
51-
excludeComponentDeclaration: true,
44+
describe('animationModule', () => {
45+
@NgModule({
46+
declarations: [FixtureComponent],
47+
})
48+
class FixtureModule {}
49+
describe('excludeComponentDeclaration', () => {
50+
test('will throw if component is declared in an import', async () => {
51+
await render(FixtureComponent, {
52+
imports: [FixtureModule],
53+
excludeComponentDeclaration: true,
54+
});
5255
});
5356
});
54-
});
5557

56-
describe('animationModule', () => {
5758
test('adds NoopAnimationsModule by default', async () => {
5859
await render(FixtureComponent);
5960
const noopAnimationsModule = TestBed.inject(NoopAnimationsModule);
@@ -72,28 +73,29 @@ describe('animationModule', () => {
7273
});
7374
});
7475

75-
@Component({
76-
selector: 'fixture',
77-
template: ` {{ name }} `,
78-
})
79-
class FixtureWithNgOnChangesComponent implements OnInit, OnChanges {
80-
@Input() name = 'Sarah';
81-
@Input() nameInitialized?: (name: string) => void;
82-
@Input() nameChanged?: (name: string, isFirstChange: boolean) => void;
83-
84-
ngOnInit() {
85-
if (this.nameInitialized) {
86-
this.nameInitialized(this.name);
76+
describe('Angular component life-cycle hooks', () => {
77+
@Component({
78+
selector: 'fixture',
79+
template: ` {{ name }} `,
80+
})
81+
class FixtureWithNgOnChangesComponent implements OnInit, OnChanges {
82+
@Input() name = 'Sarah';
83+
@Input() nameInitialized?: (name: string) => void;
84+
@Input() nameChanged?: (name: string, isFirstChange: boolean) => void;
85+
86+
ngOnInit() {
87+
if (this.nameInitialized) {
88+
this.nameInitialized(this.name);
89+
}
8790
}
88-
}
8991

90-
ngOnChanges(changes: SimpleChanges) {
91-
if (changes.name && this.nameChanged) {
92-
this.nameChanged(changes.name.currentValue, changes.name.isFirstChange());
92+
ngOnChanges(changes: SimpleChanges) {
93+
if (changes.name && this.nameChanged) {
94+
this.nameChanged(changes.name.currentValue, changes.name.isFirstChange());
95+
}
9396
}
9497
}
95-
}
96-
describe('Angular component life-cycle hooks', () => {
98+
9799
test('will call ngOnInit on initial render', async () => {
98100
const nameInitialized = jest.fn();
99101
const componentProperties = { nameInitialized };
@@ -115,3 +117,37 @@ describe('Angular component life-cycle hooks', () => {
115117
expect(nameChanged.mock.invocationCallOrder[0]).toBeLessThan(nameInitialized.mock.invocationCallOrder[0]);
116118
});
117119
});
120+
121+
describe('configure: default imports', () => {
122+
@Component({
123+
selector: 'app-fixture',
124+
template: `
125+
<form [formGroup]="form" name="form">
126+
<div>
127+
<label for="name">Name</label>
128+
<input type="text" id="name" name="name" formControlName="name" />
129+
</div>
130+
</form>
131+
`,
132+
})
133+
class FormsComponent {
134+
form = this.formBuilder.group({
135+
name: [''],
136+
});
137+
138+
constructor(private formBuilder: FormBuilder) {}
139+
}
140+
141+
beforeEach(() => {
142+
configure({
143+
defaultImports: [ReactiveFormsModule],
144+
});
145+
});
146+
147+
test('adds default imports to the testbed', async () => {
148+
await render(FormsComponent);
149+
150+
const reactive = TestBed.inject(ReactiveFormsModule);
151+
expect(reactive).not.toBeNull();
152+
});
153+
});

src/app/examples/03-forms.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import { ReactiveFormsModule } from '@angular/forms';
21
import { render, screen, fireEvent } from '@testing-library/angular';
32
import userEvent from '@testing-library/user-event';
43

54
import { FormsComponent } from './03-forms';
65

76
test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => {
8-
await render(FormsComponent, {
9-
imports: [ReactiveFormsModule],
10-
});
7+
await render(FormsComponent);
118

129
const nameControl = screen.getByRole('textbox', { name: /name/i });
1310
const scoreControl = screen.getByRole('spinbutton', { name: /score/i });

src/app/examples/03-forms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component } from '@angular/core';
2-
import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms';
2+
import { FormBuilder, Validators, ValidationErrors } from '@angular/forms';
33

44
@Component({
55
selector: 'app-fixture',

src/app/examples/04-forms-with-material.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { ReactiveFormsModule } from '@angular/forms';
21
import { render, screen } from '@testing-library/angular';
32
import userEvent from '@testing-library/user-event';
43

@@ -7,7 +6,7 @@ import { MaterialFormsComponent } from './04-forms-with-material';
76

87
test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => {
98
const { fixture } = await render(MaterialFormsComponent, {
10-
imports: [ReactiveFormsModule, MaterialModule],
9+
imports: [MaterialModule],
1110
});
1211

1312
const nameControl = screen.getByLabelText(/name/i);

src/app/examples/04-forms-with-material.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component } from '@angular/core';
2-
import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms';
2+
import { FormBuilder, Validators, ValidationErrors } from '@angular/forms';
33

44
@Component({
55
selector: 'app-fixture',

test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
import 'jest-preset-angular';
22
import '@testing-library/jest-dom';
3+
import { configure } from '@testing-library/angular';
4+
import { ReactiveFormsModule } from '@angular/forms';
5+
6+
beforeEach(() => {
7+
configure({
8+
defaultImports: [ReactiveFormsModule],
9+
});
10+
});

0 commit comments

Comments
 (0)