Skip to content

Commit aa5f891

Browse files
fix: fireEvent runs a change detection cycle (#104)
1 parent 95f7de0 commit aa5f891

File tree

13 files changed

+1372
-1293
lines changed

13 files changed

+1372
-1293
lines changed

Diff for: jest.base.config.js

+3-13
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,10 @@ module.exports = {
22
preset: 'jest-preset-angular',
33
rootDir: '../',
44
setupFilesAfterEnv: ['<rootDir>/test.ts'],
5-
testURL: 'http://localhost',
6-
globals: {
7-
'ts-jest': {
8-
tsConfig: './tsconfig.spec.json',
9-
stringifyContentPathRegex: '\\.html$',
10-
astTransformers: [require.resolve('jest-preset-angular/InlineHtmlStripStylesTransformer')],
11-
},
12-
},
13-
transform: {
14-
'^.+\\.(ts|js|html)$': 'ts-jest',
15-
},
165
transformIgnorePatterns: ['node_modules/(?!@ngrx)'],
176
snapshotSerializers: [
18-
'jest-preset-angular/AngularSnapshotSerializer.js',
19-
'jest-preset-angular/HTMLCommentSerializer.js',
7+
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
8+
'jest-preset-angular/build/AngularSnapshotSerializer.js',
9+
'jest-preset-angular/build/HTMLCommentSerializer.js',
2010
],
2111
};

Diff for: lint-staged.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { '*.{ts,json,md}': ['prettier --write', 'git add'] };

Diff for: package.json

+12-17
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@
1616
"lint": "ng lint",
1717
"semantic-release": "semantic-release"
1818
},
19-
"lint-staged": {
20-
"*.{ts,json,md}": [
21-
"prettier --write",
22-
"git add"
23-
]
24-
},
2519
"dependencies": {
2620
"@angular/animations": "^9.0.3",
2721
"@angular/cdk": "^9.1.0",
@@ -35,8 +29,8 @@
3529
"@angular/router": "^9.0.3",
3630
"@ngrx/store": "^9.0.0-rc.0",
3731
"@phenomnomnominal/tsquery": "^3.0.0",
38-
"@testing-library/dom": "^7.7.3",
39-
"@testing-library/user-event": "^8.1.0",
32+
"@testing-library/dom": "^7.9.0",
33+
"@testing-library/user-event": "^11.0.0",
4034
"core-js": "^3.1.3",
4135
"rxjs": "^6.5.4",
4236
"tslib": "^1.11.1",
@@ -49,20 +43,21 @@
4943
"@angular/cli": "~9.0.3",
5044
"@angular/compiler-cli": "^9.0.3",
5145
"@angular/language-service": "^9.0.3",
52-
"@testing-library/jest-dom": "^4.1.0",
46+
"@testing-library/jest-dom": "^5.9.0",
5347
"@types/jest": "~24.0.11",
54-
"@types/node": "^13.7.6",
48+
"@types/node": "^14.0.11",
5549
"codelyzer": "^5.1.2",
5650
"cpy-cli": "^3.1.0",
57-
"husky": "^2.3.0",
58-
"jest": "^24.1.0",
59-
"jest-preset-angular": "^7.1.1",
60-
"lint-staged": "^8.1.7",
51+
"husky": "^4.2.5",
52+
"jest": "^25.5.4",
53+
"jest-preset-angular": "^8.2.0",
54+
"lint-staged": "^10.2.9",
6155
"ng-packagr": "^9.0.0",
62-
"prettier": "^1.17.1",
63-
"rimraf": "^2.6.3",
56+
"prettier": "^2.0.5",
57+
"rimraf": "^3.0.2",
6458
"semantic-release": "^17.0.2",
65-
"ts-node": "~8.2.0",
59+
"ts-jest": "^26.1.0",
60+
"ts-node": "~8.10.2",
6661
"typescript": "~3.7.5"
6762
}
6863
}

Diff for: projects/testing-library/migrations/4_0_0/rules/noComponentPropertyRule.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ const FAILURE_MESSAGE = 'Found component propety syntax, signature looks differe
1010

1111
export class Rule extends Rules.AbstractRule {
1212
public apply(ast: ts.SourceFile): Array<RuleFailure> {
13-
return tsquery(ast, IS_COMPONENT_PROPERTY_QUERY).map(result => {
13+
return tsquery(ast, IS_COMPONENT_PROPERTY_QUERY).map((result) => {
1414
const [valueNode] = tsquery(result, COMPONENT_PROPERTY_VALUE_QUERY);
15-
const replacement = new Replacement(result.getStart(), result.getWidth(), (valueNode || result).text);
15+
const replacement = new Replacement(result.getStart(), result.getWidth(), (valueNode || result).getText());
1616
const start = result.getStart();
1717
const end = result.getEnd();
1818

Diff for: projects/testing-library/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"@angular/core": "^9.0.0"
3030
},
3131
"dependencies": {
32-
"@testing-library/dom": "^7.7.3",
32+
"@testing-library/dom": "^7.9.0",
3333
"@testing-library/user-event": "^8.1.0",
3434
"@phenomnomnominal/tsquery": "^3.0.0",
3535
"tslint": "^5.16.0"

Diff for: projects/testing-library/src/lib/models.ts

+6
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,19 @@ export interface RenderResult<ComponentType, WrapperType = ComponentType>
6363
*/
6464
rerender: (componentProperties: Partial<ComponentType>) => void;
6565
/**
66+
* @deprecated
67+
* Usage of `waitForElementToBeRemoved` from render is deprecated, use this method directly from `@testing-library/angular`
68+
*
6669
* @description
6770
* Wait for the removal of element(s) from the DOM.
6871
*
6972
* For more info see https://testing-library.com/docs/dom-testing-library/api-async#waitforelementtoberemoved
7073
*/
7174
waitForElementToBeRemoved: typeof waitForElementToBeRemoved;
7275
/**
76+
* @deprecated
77+
* Usage of `waitFor` from render is deprecated, use this method directly from `@testing-library/angular`
78+
*
7379
* @description
7480
* When in need to wait for any period of time you can use waitFor, to wait for your expectations to pass.
7581
*

Diff for: projects/testing-library/src/lib/testing-library.ts

+39-16
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,29 @@ import { RouterTestingModule } from '@angular/router/testing';
77
import {
88
FireFunction,
99
FireObject,
10-
getQueriesForElement,
11-
prettyDOM,
10+
getQueriesForElement as dtlGetQueriesForElement,
11+
prettyDOM as dtlPrettyDOM,
1212
waitFor as dtlWaitFor,
1313
waitForElementToBeRemoved as dtlWaitForElementToBeRemoved,
1414
fireEvent as dtlFireEvent,
1515
screen as dtlScreen,
1616
queries as dtlQueries,
17-
waitForOptions,
17+
waitForOptions as dtlWaitForOptions,
18+
configure as dtlConfigure,
1819
} from '@testing-library/dom';
1920
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
2021
import { createSelectOptions, createType, tab } from './user-events';
2122

22-
@Component({ selector: 'wrapper-component', template: '' })
23-
class WrapperComponent {}
24-
2523
const mountedFixtures = new Set<ComponentFixture<any>>();
2624

25+
dtlConfigure({
26+
eventWrapper: cb => {
27+
const result = cb();
28+
detectChangesForMountedFixtures();
29+
return result;
30+
},
31+
});
32+
2733
export async function render<ComponentType>(
2834
component: Type<ComponentType>,
2935
renderOptions?: RenderComponentOptions<ComponentType>,
@@ -148,13 +154,16 @@ export async function render<SutType, WrapperType = SutType>(
148154
return result;
149155
};
150156

151-
function componentWaitFor<T>(callback, options: waitForOptions = { container: fixture.nativeElement }): Promise<T> {
157+
function componentWaitFor<T>(
158+
callback,
159+
options: dtlWaitForOptions = { container: fixture.nativeElement },
160+
): Promise<T> {
152161
return waitForWrapper(detectChanges, callback, options);
153162
}
154163

155164
function componentWaitForElementToBeRemoved<T>(
156165
callback: (() => T) | T,
157-
options: waitForOptions = { container: fixture.nativeElement },
166+
options: dtlWaitForOptions = { container: fixture.nativeElement },
158167
): Promise<T> {
159168
return waitForElementToBeRemovedWrapper(detectChanges, callback, options);
160169
}
@@ -168,14 +177,17 @@ export async function render<SutType, WrapperType = SutType>(
168177
container: fixture.nativeElement,
169178
debug: (element = fixture.nativeElement, maxLength, options) =>
170179
Array.isArray(element)
171-
? element.forEach(e => console.log(prettyDOM(e, maxLength, options)))
172-
: console.log(prettyDOM(element, maxLength, options)),
180+
? element.forEach(e => console.log(dtlPrettyDOM(e, maxLength, options)))
181+
: console.log(dtlPrettyDOM(element, maxLength, options)),
173182
type: createType(eventsWithDetectChanges),
174183
selectOptions: createSelectOptions(eventsWithDetectChanges),
175184
tab,
176185
waitFor: componentWaitFor,
177186
waitForElementToBeRemoved: componentWaitForElementToBeRemoved,
178-
...replaceFindWithFindAndDetectChanges(fixture.nativeElement, getQueriesForElement(fixture.nativeElement, queries)),
187+
...replaceFindWithFindAndDetectChanges(
188+
fixture.nativeElement,
189+
dtlGetQueriesForElement(fixture.nativeElement, queries),
190+
),
179191
...eventsWithDetectChanges,
180192
};
181193
}
@@ -244,7 +256,7 @@ function addAutoImports({ imports, routes }: Pick<RenderComponentOptions<any>, '
244256
async function waitForWrapper<T>(
245257
detectChanges: () => void,
246258
callback: () => T extends Promise<any> ? never : T,
247-
options?: waitForOptions,
259+
options?: dtlWaitForOptions,
248260
): Promise<T> {
249261
return await dtlWaitFor(() => {
250262
detectChanges();
@@ -258,7 +270,7 @@ async function waitForWrapper<T>(
258270
async function waitForElementToBeRemovedWrapper<T>(
259271
detectChanges: () => void,
260272
callback: (() => T) | T,
261-
options?: waitForOptions,
273+
options?: dtlWaitForOptions,
262274
): Promise<T> {
263275
let cb;
264276
if (typeof callback !== 'function') {
@@ -298,6 +310,9 @@ if (typeof afterEach === 'function' && !process.env.ATL_SKIP_AUTO_CLEANUP) {
298310
});
299311
}
300312

313+
@Component({ selector: 'wrapper-component', template: '' })
314+
class WrapperComponent {}
315+
301316
/**
302317
* Wrap findBy queries to poke the Angular change detection cycle
303318
*/
@@ -329,7 +344,15 @@ function replaceFindWithFindAndDetectChanges<T>(container: HTMLElement, original
329344
* Call detectChanges for all fixtures
330345
*/
331346
function detectChangesForMountedFixtures() {
332-
mountedFixtures.forEach(fixture => fixture.detectChanges());
347+
mountedFixtures.forEach(fixture => {
348+
try {
349+
fixture.detectChanges();
350+
} catch (err) {
351+
if (!err.message.startsWith('ViewDestroyedError')) {
352+
throw err;
353+
}
354+
}
355+
});
333356
}
334357

335358
/**
@@ -355,14 +378,14 @@ const screen = replaceFindWithFindAndDetectChanges(document.body, dtlScreen);
355378
/**
356379
* Re-export waitFor with patched waitFor
357380
*/
358-
async function waitFor<T>(callback: () => T extends Promise<any> ? never : T, options?: waitForOptions): Promise<T> {
381+
async function waitFor<T>(callback: () => T extends Promise<any> ? never : T, options?: dtlWaitForOptions): Promise<T> {
359382
return waitForWrapper(detectChangesForMountedFixtures, callback, options);
360383
}
361384

362385
/**
363386
* Re-export waitForElementToBeRemoved with patched waitForElementToBeRemoved
364387
*/
365-
async function waitForElementToBeRemoved<T>(callback: (() => T) | T, options?: waitForOptions): Promise<T> {
388+
async function waitForElementToBeRemoved<T>(callback: (() => T) | T, options?: dtlWaitForOptions): Promise<T> {
366389
return waitForElementToBeRemovedWrapper(detectChangesForMountedFixtures, callback, options);
367390
}
368391

Diff for: projects/testing-library/src/lib/user-events/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { tab } from './tab';
44

55
export interface UserEvents {
66
/**
7+
* @deprecated
8+
* Use `userEvents.type` from @testing-library/user-event
9+
*
710
* @description
811
* Types a value in an input field with the same interactions as the user would do.
912
*
@@ -19,6 +22,9 @@ export interface UserEvents {
1922
type: ReturnType<typeof createType>;
2023

2124
/**
25+
* @deprecated
26+
* Use `userEvents.selectOptions` from @testing-library/user-event
27+
*
2228
* @description
2329
* Select an option(s) from a select field with the same interactions as the user would do.
2430
*
@@ -33,6 +39,9 @@ export interface UserEvents {
3339
selectOptions: ReturnType<typeof createSelectOptions>;
3440

3541
/**
42+
* @deprecated
43+
* Use `userEvents.tab` from @testing-library/user-event
44+
*
3645
* @description
3746
* Fires a tab event changing the document.activeElement in the same way the browser does.
3847
*

Diff for: src/app/examples/03-forms.spec.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ReactiveFormsModule } from '@angular/forms';
2-
import { render, screen, fireEvent, userEvent } from '@testing-library/angular';
2+
import { render, screen, fireEvent } from '@testing-library/angular';
3+
import userEvent from '@testing-library/user-event';
34

45
import { FormsComponent } from './03-forms';
56

@@ -19,16 +20,18 @@ test('is possible to fill in a form and verify error messages (with the help of
1920

2021
expect(nameControl).toBeInvalid();
2122
userEvent.type(nameControl, 'Tim');
23+
userEvent.clear(scoreControl);
2224
userEvent.type(scoreControl, '12');
2325
fireEvent.blur(scoreControl);
24-
userEvent.selectOptions(colorControl, 'Green');
26+
userEvent.selectOptions(colorControl, 'G');
2527

2628
expect(screen.queryByText('name is required')).not.toBeInTheDocument();
2729
expect(screen.queryByText('score must be lesser than 10')).toBeInTheDocument();
2830
expect(screen.queryByText('color is required')).not.toBeInTheDocument();
2931

3032
expect(scoreControl).toBeInvalid();
31-
userEvent.type(scoreControl, 7);
33+
userEvent.clear(scoreControl);
34+
userEvent.type(scoreControl, '7');
3235
fireEvent.blur(scoreControl);
3336
expect(scoreControl).toBeValid();
3437

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ReactiveFormsModule } from '@angular/forms';
2-
import { render, screen, userEvent } from '@testing-library/angular';
2+
import { render, screen } from '@testing-library/angular';
3+
import userEvent from '@testing-library/user-event';
34

45
import { MaterialModule } from '../material.module';
56
import { MaterialFormsComponent } from './04-forms-with-material';
@@ -19,15 +20,18 @@ test('is possible to fill in a form and verify error messages (with the help of
1920
expect(errors).toContainElement(screen.queryByText('color is required'));
2021

2122
userEvent.type(nameControl, 'Tim');
22-
userEvent.type(scoreControl, 12);
23-
userEvent.selectOptions(colorControl, 'Green');
23+
userEvent.clear(scoreControl);
24+
userEvent.type(scoreControl, '12');
25+
userEvent.click(colorControl);
26+
userEvent.click(screen.getByText(/green/i));
2427

2528
expect(screen.queryByText('name is required')).not.toBeInTheDocument();
2629
expect(screen.queryByText('score must be lesser than 10')).toBeInTheDocument();
2730
expect(screen.queryByText('color is required')).not.toBeInTheDocument();
2831

2932
expect(scoreControl).toBeInvalid();
30-
userEvent.type(scoreControl, 7);
33+
userEvent.clear(scoreControl);
34+
userEvent.type(scoreControl, '7');
3135
expect(scoreControl).toBeValid();
3236

3337
expect(errors).not.toBeInTheDocument();

Diff for: test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
import 'jest-preset-angular';
2-
import '@testing-library/jest-dom/extend-expect';
2+
import '@testing-library/jest-dom';

Diff for: tslint.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"no-trailing-whitespace": true,
4343
"no-unnecessary-initializer": true,
4444
"no-unused-expression": true,
45-
"no-use-before-declare": true,
45+
"no-use-before-declare": false,
4646
"no-var-keyword": true,
4747
"object-literal-sort-keys": false,
4848
"one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"],

0 commit comments

Comments
 (0)