Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a3975cd

Browse files
feat: waitForDomChange, waitForElement, waitForElementToBeRemoved (testing-library#60)
1 parent 924382c commit a3975cd

File tree

6 files changed

+199
-10
lines changed

6 files changed

+199
-10
lines changed

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

Lines changed: 37 additions & 8 deletions
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 } from '@testing-library/dom';
4+
import {
5+
BoundFunction,
6+
FireObject,
7+
Queries,
8+
queries,
9+
waitForElement,
10+
waitForElementToBeRemoved,
11+
waitForDomChange,
12+
} from '@testing-library/dom';
513
import { UserEvents } from './user-events';
614

715
export type RenderResultQueries<Q extends Queries = typeof queries> = { [P in keyof Q]: BoundFunction<Q[P]> };
@@ -34,9 +42,11 @@ export interface RenderResult<ComponentType, WrapperType = ComponentType>
3442
detectChanges: () => void;
3543
/**
3644
* @description
37-
* Re-render the same component with different props.
45+
* The Angular `DebugElement` of the component.
46+
*
47+
* For more info see https://angular.io/api/core/DebugElement
3848
*/
39-
rerender: (componentProperties: Partial<ComponentType>)=>void;
49+
debugElement: DebugElement;
4050
/**
4151
* @description
4252
* The Angular `ComponentFixture` of the component or the wrapper.
@@ -47,17 +57,36 @@ export interface RenderResult<ComponentType, WrapperType = ComponentType>
4757
fixture: ComponentFixture<WrapperType>;
4858
/**
4959
* @description
50-
* The Angular `DebugElement` of the component.
60+
* Navigates to the href of the element or to the path.
5161
*
52-
* For more info see https://angular.io/api/core/DebugElement
5362
*/
54-
debugElement: DebugElement;
63+
navigate: (elementOrPath: Element|string,basePath?: string)=>Promise<boolean>;
5564
/**
5665
* @description
57-
* Navigates to the href of the element or to the path.
66+
* Re-render the same component with different props.
67+
*/
68+
rerender: (componentProperties: Partial<ComponentType>) => void;
69+
/**
70+
* @description
71+
* Wait for the DOM to change.
5872
*
73+
* For more info see https://testing-library.com/docs/dom-testing-library/api-async#waitfordomchange
5974
*/
60-
navigate: (elementOrPath: Element | string, basePath?: string) => Promise<boolean>;
75+
waitForDomChange: typeof waitForDomChange;
76+
/**
77+
* @description
78+
* Wait for DOM elements to appear, disappear, or change.
79+
*
80+
* For more info see https://testing-library.com/docs/dom-testing-library/api-async#waitforelement
81+
*/
82+
waitForElement: typeof waitForElement;
83+
/**
84+
* @description
85+
* Wait for the removal of element(s) from the DOM.
86+
*
87+
* For more info see https://testing-library.com/docs/dom-testing-library/api-async#waitforelementtoberemoved
88+
*/
89+
waitForElementToBeRemoved: typeof waitForElementToBeRemoved;
6190
}
6291

6392
export interface RenderComponentOptions<ComponentType, Q extends Queries = typeof queries> {

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ import { By } from '@angular/platform-browser';
44
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
55
import { Router } from '@angular/router';
66
import { RouterTestingModule } from '@angular/router/testing';
7-
import { fireEvent, FireFunction, FireObject, getQueriesForElement, prettyDOM } from '@testing-library/dom';
7+
import {
8+
fireEvent,
9+
FireFunction,
10+
FireObject,
11+
getQueriesForElement,
12+
prettyDOM,
13+
waitForDomChange,
14+
waitForElement,
15+
waitForElementToBeRemoved,
16+
} from '@testing-library/dom';
817
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
918
import { createSelectOptions, createType } from './user-events';
1019

@@ -111,6 +120,45 @@ export async function render<SutType, WrapperType = SutType>(
111120
return result;
112121
};
113122

123+
function componentWaitForDomChange<Result>(options?: {
124+
container?: HTMLElement;
125+
timeout?: number;
126+
mutationObserverOptions?: MutationObserverInit;
127+
}): Promise<Result> {
128+
const interval = setInterval(detectChanges, 10);
129+
return waitForDomChange<Result>({ container: fixture.nativeElement, ...options }).finally(() =>
130+
clearInterval(interval),
131+
);
132+
}
133+
134+
function componentWaitForElement<Result>(
135+
callback: () => Result,
136+
options?: {
137+
container?: HTMLElement;
138+
timeout?: number;
139+
mutationObserverOptions?: MutationObserverInit;
140+
},
141+
): Promise<Result> {
142+
const interval = setInterval(detectChanges, 10);
143+
return waitForElement(callback, { container: fixture.nativeElement, ...options }).finally(() =>
144+
clearInterval(interval),
145+
);
146+
}
147+
148+
function componentWaitForElementToBeRemoved<Result>(
149+
callback: () => Result,
150+
options?: {
151+
container?: HTMLElement;
152+
timeout?: number;
153+
mutationObserverOptions?: MutationObserverInit;
154+
},
155+
): Promise<Result> {
156+
const interval = setInterval(detectChanges, 10);
157+
return waitForElementToBeRemoved(callback, { container: fixture.nativeElement, ...options }).finally(() =>
158+
clearInterval(interval),
159+
);
160+
}
161+
114162
return {
115163
fixture,
116164
detectChanges,
@@ -121,6 +169,9 @@ export async function render<SutType, WrapperType = SutType>(
121169
debug: (element = fixture.nativeElement) => console.log(prettyDOM(element)),
122170
type: createType(eventsWithDetectChanges),
123171
selectOptions: createSelectOptions(eventsWithDetectChanges),
172+
waitForDomChange: componentWaitForDomChange,
173+
waitForElement: componentWaitForElement,
174+
waitForElementToBeRemoved: componentWaitForElementToBeRemoved,
124175
...getQueriesForElement(fixture.nativeElement, queries),
125176
...eventsWithDetectChanges,
126177
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { render } from '../src/public_api';
3+
import { timer } from 'rxjs';
4+
5+
@Component({
6+
selector: 'fixture',
7+
template: `
8+
<div *ngIf="oneVisible" data-testid="block-one">One</div>
9+
<div *ngIf="twoVisible" data-testid="block-two">Two</div>
10+
`,
11+
})
12+
class FixtureComponent implements OnInit {
13+
oneVisible = false;
14+
twoVisible = false;
15+
16+
ngOnInit() {
17+
timer(200).subscribe(() => (this.oneVisible = true));
18+
timer(400).subscribe(() => (this.twoVisible = true));
19+
}
20+
}
21+
22+
test('waits for the DOM to change', async () => {
23+
const { queryByTestId, getByTestId, waitForDomChange } = await render(FixtureComponent);
24+
25+
await waitForDomChange();
26+
27+
getByTestId('block-one');
28+
expect(queryByTestId('block-two')).toBeNull();
29+
30+
await waitForDomChange();
31+
32+
getByTestId('block-one');
33+
getByTestId('block-two');
34+
});
35+
36+
test('allows to override options', async () => {
37+
const { waitForDomChange } = await render(FixtureComponent);
38+
39+
await expect(waitForDomChange({ timeout: 100 })).rejects.toThrow(/TimedoutinwaitForDomChange/i);
40+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { render } from '../src/public_api';
3+
import { timer } from 'rxjs';
4+
5+
@Component({
6+
selector: 'fixture',
7+
template: `
8+
<div *ngIf="visible" data-testid="im-here">👋</div>
9+
`,
10+
})
11+
class FixtureComponent implements OnInit {
12+
visible = true;
13+
ngOnInit() {
14+
timer(500).subscribe(() => (this.visible = false));
15+
}
16+
}
17+
18+
test('waits for element to be removed', async () => {
19+
const { queryByTestId, getByTestId, waitForElementToBeRemoved } = await render(FixtureComponent);
20+
21+
await waitForElementToBeRemoved(() => getByTestId('im-here'));
22+
23+
expect(queryByTestId('im-here')).toBeNull();
24+
});
25+
26+
test('allows to override options', async () => {
27+
const { getByTestId, waitForElementToBeRemoved } = await render(FixtureComponent);
28+
29+
await expect(waitForElementToBeRemoved(() => getByTestId('im-here'), { timeout: 200 })).rejects.toThrow(
30+
/TimedoutinwaitForElementToBeRemoved/i,
31+
);
32+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Component } from '@angular/core';
2+
import { render } from '../src/public_api';
3+
import { timer } from 'rxjs';
4+
5+
@Component({
6+
selector: 'fixture',
7+
template: `
8+
<button data-testid="button" (click)="load()">Load</button>
9+
<div>{{ result }}</div>
10+
`,
11+
})
12+
class FixtureComponent {
13+
result = '';
14+
15+
load() {
16+
timer(500).subscribe(() => (this.result = 'Success'));
17+
}
18+
}
19+
20+
test('waits for element to be visible', async () => {
21+
const { getByTestId, click, waitForElement, getByText } = await render(FixtureComponent);
22+
23+
click(getByTestId('button'));
24+
25+
await waitForElement(() => getByText('Success'));
26+
getByText('Success');
27+
});
28+
29+
test('allows to override options', async () => {
30+
const { getByTestId, click, waitForElement, getByText } = await render(FixtureComponent);
31+
32+
click(getByTestId('button'));
33+
34+
await expect(waitForElement(() => getByText('Success'), { timeout: 200 })).rejects.toThrow(
35+
/Unabletofindanelementwiththetext:Success/i,
36+
);
37+
});

‎projects/testing-library/tsconfig.lib.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"experimentalDecorators": true,
1313
"importHelpers": true,
1414
"types": [],
15-
"lib": ["dom", "es2015"]
15+
"lib": ["dom", "es2015", "es2018.promise"]
1616
},
1717
"angularCompilerOptions": {
1818
"annotateForClosureCompiler": true,

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /