From c1b581b04747e6b75987b25c9d638270ccab7cb7 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Thu, 14 May 2026 14:34:33 +0000 Subject: [PATCH] =?UTF-8?q?fix(test):=20Hotfix=20#6=20=E2=80=94=20polyfill?= =?UTF-8?q?=20ResizeObserver=20in=20vitest=20setup=20(Phase=201=20Combobox?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI surfaced an Unhandled Error after the full vitest suite ran clean: ReferenceError: ResizeObserver is not defined at p (node_modules/@headlessui/react/dist/utils/element-movement.js:1:332) at combobox-machine.js:1:8089 at y.send (machine.js:1:1383) at Object.closeCombobox (combobox-machine.js:1:5820) ... originating from src/components/Combobox.test.tsx Test Files 60 passed (60) Tests 654 passed (654) Errors 1 error ← vitest exits 1 on unhandled Diagnosis ========= Headless UI's Combobox + Dialog use ResizeObserver internally to track trigger-element position (focus-management edge cases on scroll / resize). jsdom does not implement ResizeObserver — without a polyfill, Headless UI's async cleanup fires *after* the vitest test completes (during the keyboard-nav close path) and throws the ReferenceError as an Unhandled Error. The test assertions had already passed; the unhandled exception alone causes vitest's process exit to flip to 1. Locally the error appeared as a "1 error" line below the green summary but exit was still 0 because we ran with a tight timeout that masked the post-test cleanup. The amd64 CI runner with the full ~40s budget triggers the unhandled handler and propagates the non-zero exit. Fix === web/src/test/setup.ts adds a minimal ResizeObserverStub class (observe / unobserve / disconnect are no-ops) and assigns it to globalThis.ResizeObserver iff undefined. The component never reads the observed dimensions in our test paths — the read sites fire only after layout has settled in a real browser — so a no-op construct + observer trio is sufficient to silence Headless UI's internal calls. Also stubs Element.prototype.scrollIntoView (Headless UI touches it during Combobox.Options keyboard nav; jsdom warns rather than throws but the CI log stays cleaner). Verification ============ $ cd web && npx vitest run src/components/Combobox.test.tsx Test Files 1 passed (1) Tests 5 passed (5) (no Unhandled Errors line; exit 0 — the post-test cleanup no longer touches the undefined global) $ cd web && npx tsc --noEmit (exit 0) This commit ships on top of Phase 1 (e37403ed). The 654-test green-suite count is unchanged; only the post-suite cleanup behaviour changes. --- web/src/test/setup.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts index 7b0828b..d575873 100644 --- a/web/src/test/setup.ts +++ b/web/src/test/setup.ts @@ -1 +1,32 @@ import '@testing-library/jest-dom'; + +// Phase 1 (Foundation Primitives) global polyfills +// ================================================= +// Headless UI's Combobox + Dialog use ResizeObserver internally to +// track trigger-element movement (focus-management edge cases on +// scroll / resize). jsdom does not implement ResizeObserver — without +// a polyfill, Combobox.closeCombobox's async cleanup fires after the +// vitest test exits and throws "ReferenceError: ResizeObserver is not +// defined" as an Unhandled Error. The test assertions pass; the +// unhandled exception causes vitest to exit 1. +// +// A minimal stub is sufficient. The component never reads the +// observed dimensions in our test paths (those code paths fire only +// after layout has settled in a real browser); it just needs the +// constructor + observe/unobserve/disconnect to exist as no-ops. +class ResizeObserverStub { + observe(): void {} + unobserve(): void {} + disconnect(): void {} +} +if (typeof globalThis.ResizeObserver === 'undefined') { + globalThis.ResizeObserver = ResizeObserverStub as unknown as typeof ResizeObserver; +} + +// Headless UI also touches Element.prototype.scrollIntoView during +// keyboard navigation of Combobox.Options. jsdom emits a noisy +// warning but doesn't throw — still cheaper to stub it so the +// CI log stays clean. +if (typeof Element !== 'undefined' && !Element.prototype.scrollIntoView) { + Element.prototype.scrollIntoView = function () {}; +}