From 83576657f4e3a1b4a6a48d8db9f1d86854fbbed3 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Thu, 10 Sep 2026 21:59:07 +0200 Subject: [PATCH] test: unblock vitest 5 (localStorage accessor, hoisted vi.mock) Two independent breakages from the vitest 4.1.11 -> 5.0.0 bump: 1. vitest 5 no longer shadows the jsdom window globals with writable ones, so `global.localStorage = ...` hits jsdom's getter-only accessor and throws "Cannot set property localStorage of [object Window] which has only a getter". test/setup.ts is a setup file, so both test files failed to collect. Object.defineProperty replaces the accessor outright. 2. vitest 5 rejects a vi.mock() call outside module top level instead of silently hoisting it. The call sat inside beforeEach, which was already misleading -- it is hoisted above the imports and its factory runs once, on first import of the mocked module, so it was never re-applied per test. Moved to top level, where it actually executes; the beforeEach keeps its resetModules/mockClear. Verified against both vitest 4.1.11 and 5.0.0: 2 files / 9 tests pass on each. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01T2BPPeWMdYEmYbN6eGCBSg --- .../__tests__/OrganizationSwitcher.spec.ts | 23 +++++++++++-------- test/setup.ts | 10 +++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/components/__tests__/OrganizationSwitcher.spec.ts b/app/components/__tests__/OrganizationSwitcher.spec.ts index 752ba5c..90ce996 100644 --- a/app/components/__tests__/OrganizationSwitcher.spec.ts +++ b/app/components/__tests__/OrganizationSwitcher.spec.ts @@ -14,20 +14,23 @@ const mockOrganizations = [ // For now, the component is indirectly tested through useOrganizationSelector tests // and manual testing shows it works correctly +// vi.mock is hoisted above the imports and the factory runs once, on the first +// import of the mocked module -- it was never re-applied per test despite sitting +// in beforeEach. vitest 5 rejects the nested call outright rather than silently +// hoisting it, so declare it where it actually executes. +vi.mock('~/composables/useOrganizationSelector', () => ({ + useOrganizationSelector: () => ({ + organizations: ref([mockOrganizations[0]]), + selectedOrganization: ref(mockOrganizations[0]), + selectedOrgId: ref('1'), + selectOrganization: mockSelectOrganization, + }), +})) + describe('OrganizationSwitcher - Single Organization', () => { beforeEach(() => { vi.resetModules() mockSelectOrganization.mockClear() - - // Mock with single organization - vi.mock('~/composables/useOrganizationSelector', () => ({ - useOrganizationSelector: () => ({ - organizations: ref([mockOrganizations[0]]), - selectedOrganization: ref(mockOrganizations[0]), - selectedOrgId: ref('1'), - selectOrganization: mockSelectOrganization, - }), - })) }) it('should show a chip for single organization', async () => { diff --git a/test/setup.ts b/test/setup.ts index 8da6362..85185ca 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -31,7 +31,15 @@ class LocalStorageMock { } } -global.localStorage = new LocalStorageMock() as unknown as Storage +// jsdom defines localStorage as a getter-only accessor on the window object, so a +// plain assignment throws ("Cannot set property localStorage ... which has only a +// getter") under vitest 5, which no longer shadows it with a writable global. +// defineProperty replaces the accessor outright and works on both. +Object.defineProperty(globalThis, 'localStorage', { + value: new LocalStorageMock() as unknown as Storage, + writable: true, + configurable: true, +}) // Make Vue composables globally available global.ref = Vue.ref