Skip to content

Commit 289c08b

Browse files
committed
fix: findBy runs the detection cycle before checking
1 parent ad0b2db commit 289c08b

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

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

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
waitFor,
1313
waitForElementToBeRemoved,
1414
fireEvent as dtlFireEvent,
15+
screen as dtlScreen,
16+
queries as dtlQueries,
1517
} from '@testing-library/dom';
1618
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
1719
import { createSelectOptions, createType, tab } from './user-events';
@@ -178,7 +180,7 @@ export async function render<SutType, WrapperType = SutType>(
178180
tab,
179181
waitFor: componentWaitFor,
180182
waitForElementToBeRemoved: componentWaitForElementToBeRemoved,
181-
...getQueriesForElement(fixture.nativeElement, queries),
183+
...replaceFindWithFindAndDetectChanges(fixture.nativeElement, getQueriesForElement(fixture.nativeElement, queries)),
182184
...eventsWithDetectChanges,
183185
};
184186
}
@@ -241,6 +243,30 @@ function addAutoImports({ imports, routes }: Pick<RenderComponentOptions<any>, '
241243
return [...imports, ...animations(), ...routing()];
242244
}
243245

246+
// for the findBy queries we first want to run a change detection cycle
247+
function replaceFindWithFindAndDetectChanges<T>(container: HTMLElement, originalQueriesForContainer: T): T {
248+
return Object.keys(originalQueriesForContainer).reduce(
249+
(newQueries, key) => {
250+
if (key.startsWith('find')) {
251+
const getByQuery = dtlQueries[key.replace('find', 'get')];
252+
newQueries[key] = async (text, options, waitForOptions) => {
253+
// original implementation at https://github.com/testing-library/dom-testing-library/blob/master/src/query-helpers.js
254+
const result = await waitFor(() => {
255+
detectChangesForMountedFixtures();
256+
return getByQuery(container, text, options);
257+
}, waitForOptions);
258+
return result;
259+
};
260+
} else {
261+
newQueries[key] = originalQueriesForContainer[key];
262+
}
263+
264+
return newQueries;
265+
},
266+
{} as T,
267+
);
268+
}
269+
244270
function cleanup() {
245271
mountedFixtures.forEach(cleanupAtFixture);
246272
}
@@ -258,20 +284,24 @@ if (typeof afterEach === 'function' && !process.env.ATL_SKIP_AUTO_CLEANUP) {
258284
});
259285
}
260286

287+
function detectChangesForMountedFixtures() {
288+
mountedFixtures.forEach(fixture => fixture.detectChanges());
289+
}
290+
261291
export * from '@testing-library/dom';
262292

263293
const fireEvent = Object.keys(dtlFireEvent).reduce(
264294
(events, key) => {
265295
events[key] = (element: HTMLElement, options?: {}) => {
266296
const result = dtlFireEvent[key](element, options);
267-
mountedFixtures.forEach(fixture => {
268-
fixture.detectChanges();
269-
});
297+
detectChangesForMountedFixtures();
270298
return result;
271299
};
272300
return events;
273301
},
274-
{} as FireFunction & FireObject,
302+
{} as typeof dtlFireEvent,
275303
);
276304

277-
export { fireEvent };
305+
const screen = replaceFindWithFindAndDetectChanges(document.body, dtlScreen);
306+
307+
export { fireEvent, screen };
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Component } from '@angular/core';
2+
import { timer } from 'rxjs';
3+
import { render, screen } from '../src/public_api';
4+
import { mapTo, timeout } from 'rxjs/operators';
5+
6+
@Component({
7+
selector: 'fixture',
8+
template: `
9+
<div>{{ result | async }}</div>
10+
`,
11+
})
12+
class FixtureComponent {
13+
result = timer(30).pipe(mapTo('I am visible'));
14+
}
15+
16+
describe('screen', () => {
17+
test('waits for element to be added to the DOM', async () => {
18+
await render(FixtureComponent);
19+
await expect(screen.findByText('I am visible')).resolves.toBeTruthy();
20+
});
21+
22+
test('rejects when something cannot be found', async () => {
23+
await render(FixtureComponent);
24+
await expect(screen.findByText('I am invisible', {}, { timeout: 40 })).rejects.toThrow('x');
25+
});
26+
});
27+
28+
describe('rendered component', () => {
29+
test('waits for element to be added to the DOM', async () => {
30+
const { findByText } = await render(FixtureComponent);
31+
await expect(findByText('I am visible')).resolves.toBeTruthy();
32+
});
33+
34+
test('rejects when something cannot be found', async () => {
35+
const { findByText } = await render(FixtureComponent);
36+
await expect(findByText('I am invisible', {}, { timeout: 40 })).rejects.toThrow('x');
37+
});
38+
});

0 commit comments

Comments
 (0)