Skip to content

Commit 25d1857

Browse files
committed
Ensure aborted signal works
1 parent f2dcbdc commit 25d1857

File tree

2 files changed

+46
-25
lines changed

2 files changed

+46
-25
lines changed

src/__tests__/waitForNode.js

+38-16
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,6 @@ describe('using fake modern timers', () => {
156156
expect(testStackFrame).toMatch(fileLocationRegexp)
157157
const [, fileLocation] = testStackFrame.match(fileLocationRegexp)
158158
expect(fileLocation).toBe(__filename)
159-
160-
expect(waitForError.stack).toMatchInlineSnapshot(`
161-
Error: Timed out in waitFor.
162-
at waitFor (<PROJECT_ROOT>/src/waitFor.ts:163:27)
163-
at waitFor (<PROJECT_ROOT>/src/__tests__/waitForNode.js:52:20)
164-
at Object.<anonymous> (<PROJECT_ROOT>/src/__tests__/waitForNode.js:142:13)
165-
at Promise.then.completed (<PROJECT_ROOT>/node_modules/jest-circus/build/utils.js:391:28)
166-
at new Promise (<anonymous>)
167-
at callAsyncCircusFn (<PROJECT_ROOT>/node_modules/jest-circus/build/utils.js:316:10)
168-
at _callCircusTest (<PROJECT_ROOT>/node_modules/jest-circus/build/run.js:218:40)
169-
at processTicksAndRejections (node:internal/process/task_queues:96:5)
170-
at _runTest (<PROJECT_ROOT>/node_modules/jest-circus/build/run.js:155:3)
171-
at _runTestsForDescribeBlock (<PROJECT_ROOT>/node_modules/jest-circus/build/run.js:66:9)
172-
`)
173159
})
174160

175161
test('does not crash in runtimes without Error.prototype.stack', async () => {
@@ -221,16 +207,52 @@ describe('using fake modern timers', () => {
221207
// An actual test would not have any frames pointing to this test.
222208
expect(waitForError.stack).toMatchInlineSnapshot(`
223209
Error: Timed out in waitFor.
224-
at handleTimeout (<PROJECT_ROOT>/src/waitFor.ts:147:17)
210+
at handleTimeout (<PROJECT_ROOT>/src/waitFor.ts:146:17)
225211
at callTimer (<PROJECT_ROOT>/node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:729:24)
226212
at doTickInner (<PROJECT_ROOT>/node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1289:29)
227213
at doTick (<PROJECT_ROOT>/node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1370:20)
228214
at Object.tick (<PROJECT_ROOT>/node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1378:20)
229215
at FakeTimers.advanceTimersByTime (<PROJECT_ROOT>/node_modules/@jest/fake-timers/build/modernFakeTimers.js:101:19)
230216
at Object.advanceTimersByTime (<PROJECT_ROOT>/node_modules/jest-runtime/build/index.js:2228:26)
231217
at Object.advanceTimersByTime (<PROJECT_ROOT>/src/__tests__/waitForNode.js:41:12)
232-
at <PROJECT_ROOT>/src/waitFor.ts:75:15
218+
at <PROJECT_ROOT>/src/waitFor.ts:80:15
233219
at new Promise (<anonymous>)
234220
`)
235221
})
222+
223+
test('can be aborted with an AbortSignal', async () => {
224+
const callback = jest.fn(() => {
225+
throw new Error('not done')
226+
})
227+
const controller = new AbortController()
228+
const waitForError = waitFor(callback, {
229+
signal: controller.signal,
230+
})
231+
232+
controller.abort('Bailing out')
233+
234+
await expect(waitForError).rejects.toThrowErrorMatchingInlineSnapshot(
235+
`Aborted: Bailing out`,
236+
)
237+
// Initial check + one ping (after which we yield which gives us a chance to advance to the controller.abort call)
238+
expect(callback).toHaveBeenCalledTimes(2)
239+
})
240+
241+
test('does not even ping if the signal is already aborted', async () => {
242+
const callback = jest.fn(() => {
243+
throw new Error('not done')
244+
})
245+
const controller = new AbortController()
246+
controller.abort('Bailing out')
247+
248+
const waitForError = waitFor(callback, {
249+
signal: controller.signal,
250+
})
251+
252+
await expect(waitForError).rejects.toThrowErrorMatchingInlineSnapshot(
253+
`Aborted: Bailing out`,
254+
)
255+
// Just the initial check
256+
expect(callback).toHaveBeenCalledTimes(1)
257+
})
236258
})

src/waitFor.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ function waitForImpl<T>(
5959
const overallTimeoutTimer = setTimeout(handleTimeout, timeout)
6060
const intervalId = setInterval(checkCallback, interval)
6161

62-
signal?.addEventListener('abort', () => {
63-
onDone(new Error(`Aborted: ${signal.reason}`), null)
64-
})
62+
if (signal !== undefined) {
63+
if (signal.aborted) {
64+
onDone(new Error(`Aborted: ${signal.reason}`), null)
65+
}
66+
signal.addEventListener('abort', () => {
67+
onDone(new Error(`Aborted: ${signal.reason}`), null)
68+
})
69+
}
6570

6671
checkCallback()
6772

@@ -74,12 +79,6 @@ function waitForImpl<T>(
7479
while (!finished && !signal?.aborted) {
7580
clock.advanceTimersByTime(interval)
7681

77-
// It's really important that checkCallback is run *before* we flush
78-
// in-flight promises. To be honest, I'm not sure why, and I can't quite
79-
// think of a way to reproduce the problem in a test, but I spent
80-
// an entire day banging my head against a wall on this.
81-
checkCallback()
82-
8382
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- No it isn't
8483
if (finished) {
8584
break

0 commit comments

Comments
 (0)