From d202319c87fd328829b558e9795eca7a5de70da6 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 3 May 2026 22:37:00 -0400 Subject: [PATCH] =?UTF-8?q?fix(web/m7-364):=20vitest=20failures=20?= =?UTF-8?q?=E2=80=94=20exact-match=20query,=20inert=20prop,=20-0,=20user?= =?UTF-8?q?=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI test-web was blocking on: - QueueDrawer.test close-button query: getByLabelText(/close queue/i) matched BOTH the backdrop button (aria-label="Close queue backdrop") and the close button (aria-label="Close queue"). Switched to exact string match. - QueueDrawer.test inert assertion: Svelte 5 + jsdom uses property binding for `inert`, not attribute, so hasAttribute('inert') returns false. Check the DOM property (with attribute fallback for robustness) instead. - queue-row-math.ts -0 normalization: Math.round(-0.5) returns -0, and Object.is(-0, 0) is false — vitest's .toBe(0) fails. Added `|| 0` to normalize -0 to +0 at the function boundary so consumers never see -0. - player/store.svelte.ts user import guard: persistence $effect.root reads `user.value?.id` at module-init, but in test files where auth/store.svelte.ts is imported transitively (auth/store.test, playlists/playlists.test), the player module loads via auth's import chain before auth's `user` binding is assigned. `user?.value` guards against that init-order race. The proper fix is to invert the auth↔player import dep (filed as I2 follow-up under #375). --- web/src/lib/components/QueueDrawer.test.ts | 10 +++++++--- web/src/lib/components/queue-row-math.ts | 4 +++- web/src/lib/player/store.svelte.ts | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/web/src/lib/components/QueueDrawer.test.ts b/web/src/lib/components/QueueDrawer.test.ts index abeae2e4..a42cb202 100644 --- a/web/src/lib/components/QueueDrawer.test.ts +++ b/web/src/lib/components/QueueDrawer.test.ts @@ -63,7 +63,7 @@ describe('QueueDrawer', () => { it('clicking the close button calls closeQueueDrawer', async () => { render(QueueDrawer); - const close = screen.getByLabelText(/close queue/i); + const close = screen.getByLabelText('Close queue'); await fireEvent.click(close); expect(closeQueueDrawer).toHaveBeenCalled(); }); @@ -84,12 +84,16 @@ describe('QueueDrawer', () => { expect(aside!.getAttribute('aria-hidden')).toBe('true'); }); - it('drawer has inert attribute when closed', () => { + it('drawer is inert when closed', () => { openValue = false; queueValue = [t('a')]; render(QueueDrawer); const aside = document.querySelector('aside[aria-label="Playback queue"]'); expect(aside).not.toBeNull(); - expect(aside!.hasAttribute('inert')).toBe(true); + // Svelte 5 + jsdom uses property binding for `inert`, not attribute. Check both + // to be robust regardless of which jsdom version handles the mirror. + const inertProp = (aside as unknown as { inert?: boolean }).inert === true; + const inertAttr = aside!.hasAttribute('inert'); + expect(inertProp || inertAttr).toBe(true); }); }); diff --git a/web/src/lib/components/queue-row-math.ts b/web/src/lib/components/queue-row-math.ts index e3cac63a..44cfa154 100644 --- a/web/src/lib/components/queue-row-math.ts +++ b/web/src/lib/components/queue-row-math.ts @@ -4,5 +4,7 @@ // the user's drop intent maps to a clean queue index. export function offsetToDelta(offsetY: number, rowHeight: number): number { if (rowHeight <= 0) return 0; - return Math.round(offsetY / rowHeight); + // `|| 0` normalizes -0 to +0. Math.round(-0.5) === -0; downstream + // consumers (and Object.is in tests) treat -0 and +0 as distinct. + return Math.round(offsetY / rowHeight) || 0; } diff --git a/web/src/lib/player/store.svelte.ts b/web/src/lib/player/store.svelte.ts index 649d482c..f4bf151d 100644 --- a/web/src/lib/player/store.svelte.ts +++ b/web/src/lib/player/store.svelte.ts @@ -397,7 +397,10 @@ $effect.root(() => { // queue/index changes drive it. The throttled-write effect below // covers position drift. $effect(() => { - const userId = user.value?.id; + // `user?.value` (not `user.value`) guards against module-init order during + // tests: auth/store.svelte.ts imports from this file, so when this module + // loads via that chain, `user` may not yet be assigned. See I2 follow-up. + const userId = user?.value?.id; if (!userId) return; // Reading these registers the reactive dependency; the values // themselves are unused — writePersistedQueue re-reads live state. @@ -413,7 +416,7 @@ $effect.root(() => { // Throttled write on position changes. $effect(() => { - const userId = user.value?.id; + const userId = user?.value?.id; if (!userId) return; const _p = _position; void _p;