/
wait-for-element-to-be-removed.js
197 lines (173 loc) 路 5.81 KB
/
wait-for-element-to-be-removed.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import {renderIntoDocument} from './helpers/test-utils'
function importModule() {
return require('../').waitForElementToBeRemoved
}
let waitForElementToBeRemoved
beforeEach(() => {
jest.useRealTimers()
jest.resetModules()
waitForElementToBeRemoved = importModule()
})
test('resolves on mutation only when the element is removed', async () => {
const {queryAllByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
<div data-testid="div"></div>
`)
const divs = queryAllByTestId('div')
// first mutation
setTimeout(() => {
divs.forEach(d => d.setAttribute('id', 'mutated'))
})
// removal
setTimeout(() => {
divs.forEach(div => div.parentElement.removeChild(div))
}, 100)
// the timeout is here for two reasons:
// 1. It helps test the timeout config
// 2. The element should be removed immediately
// so if it doesn't in the first 100ms then we know something's wrong
// so we'll fail early and not wait the full timeout
await waitForElementToBeRemoved(() => queryAllByTestId('div'), {timeout: 200})
})
test('resolves on mutation if callback throws an error', async () => {
const {getByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
`)
const div = getByTestId('div')
setTimeout(() => {
div.parentElement.removeChild(div)
})
await waitForElementToBeRemoved(() => getByTestId('div'), {timeout: 100})
})
test('requires an element to exist first', () => {
return expect(
waitForElementToBeRemoved(null),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal."`,
)
})
test('requires an unempty array of elements to exist first', () => {
return expect(
waitForElementToBeRemoved([]),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal."`,
)
})
test('requires an element to exist first (function form)', () => {
return expect(
waitForElementToBeRemoved(() => null),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal."`,
)
})
test('requires an unempty array of elements to exist first (function form)', () => {
return expect(
waitForElementToBeRemoved(() => []),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal."`,
)
})
test('after successful removal, fullfills promise with empty value (undefined)', () => {
const {getByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
`)
const div = getByTestId('div')
const waitResult = waitForElementToBeRemoved(() => getByTestId('div'), {
timeout: 100,
})
div.parentElement.removeChild(div)
return expect(waitResult).resolves.toBeUndefined()
})
describe('timers', () => {
const expectElementToBeRemoved = async () => {
const importedWaitForElementToBeRemoved = importModule()
const {queryAllByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
<div data-testid="div"></div>
`)
const divs = queryAllByTestId('div')
// first mutation
setTimeout(() => {
divs.forEach(d => d.setAttribute('id', 'mutated'))
})
// removal
setTimeout(() => {
divs.forEach(div => div.parentElement.removeChild(div))
}, 100)
const promise = importedWaitForElementToBeRemoved(
() => queryAllByTestId('div'),
{
timeout: 200,
},
)
if (setTimeout._isMockFunction) {
jest.advanceTimersByTime(110)
}
await promise
}
it('works with real timers', async () => {
jest.useRealTimers()
await expectElementToBeRemoved()
})
it('works with fake timers', async () => {
jest.useFakeTimers()
await expectElementToBeRemoved()
})
})
test("doesn't change jest's timers value when importing the module", () => {
jest.useFakeTimers()
importModule()
expect(window.setTimeout._isMockFunction).toEqual(true)
})
test('rethrows non-testing-lib errors', () => {
let throwIt = false
const div = document.createElement('div')
const error = new Error('my own error')
return expect(
waitForElementToBeRemoved(() => {
if (throwIt) {
throw error
}
throwIt = true
return div
}),
).rejects.toBe(error)
})
test('logs timeout error when it times out', async () => {
const div = document.createElement('div')
await expect(
waitForElementToBeRemoved(() => div, {timeout: 1}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Timed out in waitForElementToBeRemoved."`,
)
})
test('accepts an element as an argument and waits for it to be removed from its top-most parent', async () => {
const {queryByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
`)
const div = queryByTestId('div')
setTimeout(() => {
div.parentElement.removeChild(div)
}, 20)
await waitForElementToBeRemoved(div, {timeout: 200})
})
test('accepts an array of elements as an argument and waits for those elements to be removed from their top-most parent', async () => {
const {queryAllByTestId} = renderIntoDocument(`
<div>
<div>
<div data-testid="div"></div>
</div>
<div>
<div data-testid="div"></div>
</div>
</div>
`)
const [div1, div2] = queryAllByTestId('div')
setTimeout(() => {
div1.parentElement.removeChild(div1)
}, 20)
setTimeout(() => {
div2.parentElement.removeChild(div2)
}, 50)
await waitForElementToBeRemoved([div1, div2], {timeout: 200})
})