Skip to content

Commit bf8d7dd

Browse files
committed
Add tests and snapshots
1 parent 7e64ae0 commit bf8d7dd

File tree

2 files changed

+330
-0
lines changed

2 files changed

+330
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`it returns immediately if the callback returns the value before any mutations 1`] = `
4+
<div
5+
data-test-attribute="something changed once"
6+
/>
7+
`;
8+
9+
exports[`it throws if timeout is exceeded 1`] = `
10+
Array [
11+
[Error: Timed out in waitForElementToBeRemoved.],
12+
]
13+
`;
14+
15+
exports[`it throws if timeout is exceeded 2`] = `
16+
<div
17+
data-test-attribute="something changed twice"
18+
/>
19+
`;
20+
21+
exports[`it waits characterData mutation 1`] = `
22+
<div>
23+
<div>
24+
initial text
25+
</div>
26+
</div>
27+
`;
28+
29+
exports[`it waits characterData mutation 2`] = `
30+
<div>
31+
<div>
32+
new text
33+
</div>
34+
</div>
35+
`;
36+
37+
exports[`it waits for the callback to throw error or a falsy value and only reacts to DOM mutations 1`] = `
38+
<div>
39+
<div
40+
data-testid="initial-element"
41+
>
42+
43+
44+
</div>
45+
<div
46+
data-testid="another-element-that-causes-mutation-1"
47+
/>
48+
<div
49+
data-testid="another-element-that-causes-mutation-2"
50+
/>
51+
</div>
52+
`;
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import {waitForElementToBeRemoved, wait} from '../'
2+
// adds special assertions like toBeTruthy
3+
import 'jest-dom/extend-expect'
4+
import {render} from './helpers/test-utils'
5+
import document from './helpers/document'
6+
7+
const skipSomeTime = delayMs =>
8+
new Promise(resolve => setTimeout(resolve, delayMs))
9+
10+
// Using `setTimeout` >30ms instead of `wait` here because `mutationobserver-shim` uses `setTimeout` ~30ms.
11+
const skipSomeTimeForMutationObserver = (delayMs = 50) =>
12+
skipSomeTime(delayMs, 50)
13+
14+
test('it waits for the callback to throw error or a falsy value and only reacts to DOM mutations', async () => {
15+
const {container, getByTestId} = render(
16+
`<div data-testid="initial-element">
17+
</div>`,
18+
)
19+
20+
const testEle = render(
21+
`<div data-testid="the-element-we-are-looking-for"></div>`,
22+
).container.firstChild
23+
testEle.parentNode.removeChild(testEle)
24+
container.appendChild(testEle)
25+
26+
let nextElIndex = 0
27+
const makeMutationFn = () => () => {
28+
container.appendChild(
29+
render(
30+
`<div data-testid="another-element-that-causes-mutation-${++nextElIndex}"></div>`,
31+
).container.firstChild,
32+
)
33+
}
34+
35+
const mutationsAndCallbacks = [
36+
[makeMutationFn(), () => true],
37+
[makeMutationFn(), () => getByTestId('the-element-we-are-looking-for')],
38+
[
39+
() =>
40+
container.removeChild(getByTestId('the-element-we-are-looking-for')),
41+
() => getByTestId('the-element-we-are-looking-for'),
42+
],
43+
]
44+
45+
const callback = jest
46+
.fn(() => {
47+
throw new Error('No more calls are expected.')
48+
})
49+
.mockName('callback')
50+
.mockImplementation(() => true)
51+
52+
const successHandler = jest.fn().mockName('successHandler')
53+
const errorHandler = jest.fn().mockName('errorHandler')
54+
55+
waitForElementToBeRemoved(callback, {container}).then(
56+
successHandler,
57+
errorHandler,
58+
)
59+
60+
// One synchronous `callback` call is expected.
61+
expect(callback).toHaveBeenCalledTimes(1)
62+
expect(successHandler).toHaveBeenCalledTimes(0)
63+
expect(errorHandler).toHaveBeenCalledTimes(0)
64+
65+
await skipSomeTimeForMutationObserver()
66+
67+
// No more expected calls without DOM mutations.
68+
expect(callback).toHaveBeenCalledTimes(1)
69+
expect(successHandler).toHaveBeenCalledTimes(0)
70+
expect(errorHandler).toHaveBeenCalledTimes(0)
71+
72+
// Perform mutations one by one, waiting for each to trigger `MutationObserver`.
73+
for (const [mutationImpl, callbackImpl] of mutationsAndCallbacks) {
74+
callback.mockImplementation(callbackImpl)
75+
mutationImpl()
76+
await skipSomeTimeForMutationObserver() // eslint-disable-line no-await-in-loop
77+
}
78+
79+
expect(callback).toHaveBeenCalledTimes(1 + mutationsAndCallbacks.length)
80+
expect(successHandler).toHaveBeenCalledTimes(1)
81+
expect(successHandler).toHaveBeenCalledWith(true)
82+
expect(errorHandler).toHaveBeenCalledTimes(0)
83+
expect(container).toMatchSnapshot()
84+
})
85+
86+
test('it waits characterData mutation', async () => {
87+
const {container} = render(`<div>initial text</div>`)
88+
89+
const callback = jest
90+
.fn(() => container.textContent === 'initial text')
91+
.mockName('callback')
92+
const successHandler = jest.fn().mockName('successHandler')
93+
const errorHandler = jest.fn().mockName('errorHandler')
94+
95+
waitForElementToBeRemoved(callback, {container}).then(
96+
successHandler,
97+
errorHandler,
98+
)
99+
100+
// Promise callbacks are always asynchronous.
101+
expect(successHandler).toHaveBeenCalledTimes(0)
102+
expect(errorHandler).toHaveBeenCalledTimes(0)
103+
expect(callback).toHaveBeenCalledTimes(1)
104+
expect(container).toMatchSnapshot()
105+
106+
await skipSomeTimeForMutationObserver()
107+
108+
expect(callback).toHaveBeenCalledTimes(1)
109+
expect(successHandler).toHaveBeenCalledTimes(0)
110+
expect(errorHandler).toHaveBeenCalledTimes(0)
111+
112+
container.querySelector('div').innerHTML = 'new text'
113+
await skipSomeTimeForMutationObserver()
114+
115+
expect(successHandler).toHaveBeenCalledTimes(1)
116+
expect(errorHandler).toHaveBeenCalledTimes(0)
117+
expect(callback).toHaveBeenCalledTimes(2)
118+
expect(container).toMatchSnapshot()
119+
})
120+
121+
test('it waits for the attributes mutation', async () => {
122+
const {container} = render(``)
123+
container.setAttribute('data-test-attribute', 'PASSED')
124+
125+
const callback = jest
126+
.fn(() => container.getAttribute('data-test-attribute'))
127+
.mockName('callback')
128+
const successHandler = jest.fn().mockName('successHandler')
129+
const errorHandler = jest.fn().mockName('errorHandler')
130+
131+
waitForElementToBeRemoved(callback, {
132+
container,
133+
}).then(successHandler, errorHandler)
134+
135+
expect(callback).toHaveBeenCalledTimes(1)
136+
expect(successHandler).toHaveBeenCalledTimes(0)
137+
expect(errorHandler).toHaveBeenCalledTimes(0)
138+
139+
await skipSomeTimeForMutationObserver()
140+
141+
expect(callback).toHaveBeenCalledTimes(1)
142+
expect(successHandler).toHaveBeenCalledTimes(0)
143+
expect(errorHandler).toHaveBeenCalledTimes(0)
144+
145+
container.removeAttribute('data-test-attribute')
146+
await skipSomeTimeForMutationObserver()
147+
148+
expect(callback).toHaveBeenCalledTimes(2)
149+
expect(successHandler).toHaveBeenCalledTimes(1)
150+
expect(errorHandler).toHaveBeenCalledTimes(0)
151+
})
152+
153+
test('it throws if timeout is exceeded', async () => {
154+
const {container} = render(``)
155+
156+
const callback = jest.fn(() => true).mockName('callback')
157+
const successHandler = jest.fn().mockName('successHandler')
158+
const errorHandler = jest.fn().mockName('errorHandler')
159+
160+
waitForElementToBeRemoved(callback, {
161+
container,
162+
timeout: 70,
163+
mutationObserverOptions: {attributes: true},
164+
}).then(successHandler, errorHandler)
165+
166+
expect(callback).toHaveBeenCalledTimes(1)
167+
expect(successHandler).toHaveBeenCalledTimes(0)
168+
expect(errorHandler).toHaveBeenCalledTimes(0)
169+
170+
container.setAttribute('data-test-attribute', 'something changed once')
171+
await skipSomeTimeForMutationObserver(50)
172+
173+
expect(callback).toHaveBeenCalledTimes(2)
174+
expect(successHandler).toHaveBeenCalledTimes(0)
175+
expect(errorHandler).toHaveBeenCalledTimes(0)
176+
177+
container.setAttribute('data-test-attribute', 'something changed twice')
178+
await skipSomeTimeForMutationObserver(50)
179+
180+
expect(callback).toHaveBeenCalledTimes(3)
181+
expect(successHandler).toHaveBeenCalledTimes(0)
182+
expect(errorHandler).toHaveBeenCalledTimes(1)
183+
expect(errorHandler.mock.calls[0]).toMatchSnapshot()
184+
expect(container).toMatchSnapshot()
185+
})
186+
187+
test('it returns immediately if the callback returns the value before any mutations', async () => {
188+
const {container, getByTestId} = render(``)
189+
190+
const callback = jest
191+
.fn(() => getByTestId('initial-element'))
192+
.mockName('callback')
193+
const successHandler = jest.fn().mockName('successHandler')
194+
const errorHandler = jest.fn().mockName('errorHandler')
195+
196+
waitForElementToBeRemoved(callback, {
197+
container,
198+
timeout: 70,
199+
mutationObserverOptions: {attributes: true},
200+
}).then(successHandler, errorHandler)
201+
202+
// One synchronous `callback` call is expected.
203+
expect(callback).toHaveBeenCalledTimes(1)
204+
205+
// The promise callbacks are expected to be called asyncronously.
206+
expect(successHandler).toHaveBeenCalledTimes(0)
207+
expect(errorHandler).toHaveBeenCalledTimes(0)
208+
await wait()
209+
expect(successHandler).toHaveBeenCalledTimes(1)
210+
expect(errorHandler).toHaveBeenCalledTimes(0)
211+
212+
container.setAttribute('data-test-attribute', 'something changed once')
213+
await skipSomeTimeForMutationObserver(50)
214+
215+
// No more calls are expected.
216+
expect(callback).toHaveBeenCalledTimes(1)
217+
expect(successHandler).toHaveBeenCalledTimes(1)
218+
expect(errorHandler).toHaveBeenCalledTimes(0)
219+
220+
expect(container).toMatchSnapshot()
221+
})
222+
223+
test('works if a container is not defined', async () => {
224+
render(``)
225+
const el = document.createElement('p')
226+
document.body.appendChild(el)
227+
el.innerHTML = 'I changed!'
228+
const callback = jest
229+
.fn(() => el.textContent === 'I changed!')
230+
.mockName('callback')
231+
const successHandler = jest.fn().mockName('successHandler')
232+
const errorHandler = jest.fn().mockName('errorHandler')
233+
234+
if (typeof window === 'undefined') {
235+
waitForElementToBeRemoved(callback, {container: document}).then(
236+
successHandler,
237+
errorHandler,
238+
)
239+
} else {
240+
waitForElementToBeRemoved(callback).then(successHandler, errorHandler)
241+
}
242+
243+
await skipSomeTimeForMutationObserver()
244+
245+
expect(callback).toHaveBeenCalledTimes(1)
246+
expect(successHandler).toHaveBeenCalledTimes(0)
247+
expect(errorHandler).toHaveBeenCalledTimes(0)
248+
249+
el.innerHTML = 'Changed!'
250+
await skipSomeTimeForMutationObserver()
251+
252+
expect(callback).toHaveBeenCalledTimes(2)
253+
expect(successHandler).toHaveBeenCalledTimes(1)
254+
expect(errorHandler).toHaveBeenCalledTimes(0)
255+
256+
document.getElementsByTagName('html')[0].innerHTML = '' // cleans the document
257+
})
258+
259+
test('throws an error if callback is not a function', async () => {
260+
const successHandler = jest.fn().mockName('successHandler')
261+
const errorHandler = jest.fn().mockName('errorHandler')
262+
263+
if (typeof window === 'undefined') {
264+
waitForElementToBeRemoved(undefined, {container: document}).then(
265+
successHandler,
266+
errorHandler,
267+
)
268+
} else {
269+
waitForElementToBeRemoved().then(successHandler, errorHandler)
270+
}
271+
272+
await skipSomeTimeForMutationObserver()
273+
274+
expect(errorHandler).toHaveBeenLastCalledWith(
275+
'waitForElementToBeRemoved requires a callback as the first parameter',
276+
)
277+
expect(successHandler).toHaveBeenCalledTimes(0)
278+
})

0 commit comments

Comments
 (0)