Skip to content

Commit 0cbd0fb

Browse files
fix: invoke change detection after callback in waitForElementToBeRemoved (#236)
1 parent 78646b0 commit 0cbd0fb

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,9 @@ async function waitForElementToBeRemovedWrapper<T>(
352352
}
353353

354354
return await dtlWaitForElementToBeRemoved(() => {
355+
const result = cb();
355356
detectChanges();
356-
return cb();
357+
return result;
357358
}, options);
358359
}
359360

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Component, EventEmitter, Injectable, Input, Output } from '@angular/core';
2+
import { TestBed } from '@angular/core/testing';
3+
import userEvent from '@testing-library/user-event';
4+
import { of, BehaviorSubject } from 'rxjs';
5+
import { debounceTime, switchMap, map, startWith } from 'rxjs/operators';
6+
import { render, screen, waitFor, waitForElementToBeRemoved, within } from '../src/lib/testing-library';
7+
8+
const DEBOUNCE_TIME = 1_000;
9+
10+
@Injectable()
11+
class EntitiesService {
12+
fetchAll() {
13+
return of([]);
14+
}
15+
}
16+
17+
@Injectable()
18+
class ModalService {
19+
open(...args: any[]) {
20+
console.log('open', ...args);
21+
}
22+
}
23+
24+
@Component({
25+
template: `
26+
<h1>Entities Title</h1>
27+
<button (click)="newEntityClicked()">Create New Entity</button>
28+
<label>
29+
Search entities
30+
<input type="text" (input)="query.next($event.target.value)" />
31+
</label>
32+
<atl-table [entities]="entities | async" (edit)="editEntityClicked($event)"></atl-table>
33+
`,
34+
})
35+
class EntitiesComponent {
36+
query = new BehaviorSubject<string>('');
37+
readonly entities = this.query.pipe(
38+
debounceTime(DEBOUNCE_TIME),
39+
switchMap((q) => this.entitiesService.fetchAll().pipe(map((ent) => ent.filter((e) => e.name.includes(q))))),
40+
startWith(entities),
41+
);
42+
43+
constructor(private entitiesService: EntitiesService, private modalService: ModalService) {}
44+
45+
newEntityClicked() {
46+
this.modalService.open('new entity');
47+
}
48+
49+
editEntityClicked(entity: string) {
50+
setTimeout(() => {
51+
this.modalService.open('edit entity', entity);
52+
}, 100);
53+
}
54+
}
55+
56+
@Component({
57+
selector: 'atl-table',
58+
template: `
59+
<table>
60+
<tr *ngFor="let entity of entities">
61+
<td>{{ entity.name }}</td>
62+
<td><button (click)="edit.next(entity.name)">Edit</button></td>
63+
</tr>
64+
</table>
65+
`,
66+
})
67+
class TableComponent {
68+
@Input() entities: any[];
69+
@Output() edit = new EventEmitter<string>();
70+
}
71+
72+
const entities = [
73+
{
74+
id: 1,
75+
name: 'Entity 1',
76+
},
77+
{
78+
id: 2,
79+
name: 'Entity 2',
80+
},
81+
{
82+
id: 3,
83+
name: 'Entity 3',
84+
},
85+
];
86+
87+
it('renders the table', async () => {
88+
jest.useFakeTimers();
89+
90+
await render(EntitiesComponent, {
91+
declarations: [TableComponent],
92+
providers: [
93+
{
94+
provide: EntitiesService,
95+
useValue: {
96+
fetchAll: jest.fn().mockReturnValue(of(entities)),
97+
},
98+
},
99+
{
100+
provide: ModalService,
101+
useValue: {
102+
open: jest.fn(),
103+
},
104+
},
105+
],
106+
});
107+
const modalMock = TestBed.inject(ModalService);
108+
109+
expect(await screen.findByRole('heading', { name: /Entities Title/i })).toBeInTheDocument();
110+
111+
expect(await screen.findByRole('cell', { name: /Entity 1/i })).toBeInTheDocument();
112+
expect(await screen.findByRole('cell', { name: /Entity 2/i })).toBeInTheDocument();
113+
expect(await screen.findByRole('cell', { name: /Entity 3/i })).toBeInTheDocument();
114+
115+
userEvent.type(await screen.findByRole('textbox', { name: /Search entities/i }), 'Entity 2', {});
116+
117+
jest.advanceTimersByTime(DEBOUNCE_TIME);
118+
119+
await waitForElementToBeRemoved(() => screen.queryByRole('cell', { name: /Entity 1/i }));
120+
expect(await screen.findByRole('cell', { name: /Entity 2/i })).toBeInTheDocument();
121+
122+
userEvent.click(await screen.findByRole('button', { name: /New Entity/i }));
123+
expect(modalMock.open).toHaveBeenCalledWith('new entity');
124+
125+
const row = await screen.findByRole('row', {
126+
name: /Entity 2/i,
127+
});
128+
userEvent.click(
129+
await within(row).findByRole('button', {
130+
name: /edit/i,
131+
}),
132+
);
133+
waitFor(() => expect(modalMock.open).toHaveBeenCalledWith('edit entity', 'Entity 2'));
134+
});

0 commit comments

Comments
 (0)