From bd8f101e09b8e89c285e70907e9aeed51cfae254 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Sun, 5 Jul 2026 17:05:27 +0000 Subject: [PATCH 1/2] fix(site): flush deferred Radix focus-scope timer before jsdom teardown Radix's FocusScope defers its unmount event dispatch and focus restore with setTimeout(0). The cleanup() in the shared afterEach schedules that timer, and for the last test in a file it could still be pending when vitest tears down the jsdom environment. The callback then constructs a CustomEvent from Node's built-in constructor instead of jsdom's, and jsdom rejects the dispatch with "parameter 1 is not of type 'Event'" as an unhandled error, failing the run. Await one timer turn in afterAll, while the jsdom environment is still alive, to deterministically drain the 0ms timers scheduled by cleanup(). Timers with the same delay run in FIFO order, so this is not a race. Fixes coder/internal#1613 --- site/test/setup/msw.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/site/test/setup/msw.ts b/site/test/setup/msw.ts index 2458492c29622..d5b1cb40c6dd3 100644 --- a/site/test/setup/msw.ts +++ b/site/test/setup/msw.ts @@ -8,4 +8,21 @@ afterEach(() => { server.resetHandlers(); vi.clearAllMocks(); }); -afterAll(() => server.close()); +afterAll(async () => { + // If a test file left fake timers installed, restore real timers so the + // flush below cannot hang until the hook timeout. + if (vi.isFakeTimers()) { + vi.useRealTimers(); + } + // Radix's FocusScope defers its unmount event dispatch and focus restore + // with setTimeout(0) (react#17894 workaround). The cleanup() call in + // afterEach schedules that timer, and for the last test in a file it + // would otherwise still be pending when vitest tears down the jsdom + // environment. It then fires with Node's CustomEvent instead of jsdom's, + // and jsdom rejects the dispatch with "parameter 1 is not of type + // 'Event'" as an unhandled error. Same-delay timers run in FIFO order, + // so awaiting one timer turn here deterministically drains every 0ms + // timer scheduled by cleanup() while the environment is still alive. + await new Promise((resolve) => setTimeout(resolve, 0)); + server.close(); +}); From dd688319820bde5747b2da692b3a4a2d431f0a6d Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Sun, 5 Jul 2026 17:09:38 +0000 Subject: [PATCH 2/2] chore(site/test/setup): tighten timer flush comments --- site/test/setup/msw.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/site/test/setup/msw.ts b/site/test/setup/msw.ts index d5b1cb40c6dd3..a42e582ec88c6 100644 --- a/site/test/setup/msw.ts +++ b/site/test/setup/msw.ts @@ -9,20 +9,13 @@ afterEach(() => { vi.clearAllMocks(); }); afterAll(async () => { - // If a test file left fake timers installed, restore real timers so the - // flush below cannot hang until the hook timeout. + // A leftover fake clock would make the timer flush below hang. if (vi.isFakeTimers()) { vi.useRealTimers(); } - // Radix's FocusScope defers its unmount event dispatch and focus restore - // with setTimeout(0) (react#17894 workaround). The cleanup() call in - // afterEach schedules that timer, and for the last test in a file it - // would otherwise still be pending when vitest tears down the jsdom - // environment. It then fires with Node's CustomEvent instead of jsdom's, - // and jsdom rejects the dispatch with "parameter 1 is not of type - // 'Event'" as an unhandled error. Same-delay timers run in FIFO order, - // so awaiting one timer turn here deterministically drains every 0ms - // timer scheduled by cleanup() while the environment is still alive. + // Radix FocusScope defers its unmount dispatch with setTimeout(0). Flush + // it before vitest tears down the jsdom environment, where it would throw + // an unhandled "parameter 1 is not of type 'Event'" TypeError. await new Promise((resolve) => setTimeout(resolve, 0)); server.close(); });