Files
schemas-app/test/setup.ts
T
argoyleandClaude Opus 5 83576657f4
schemas-app / build (push) Skipped
schemas-app / build (pull_request) Successful in 2m41s
schemas-app / deploy-prod (pull_request) Skipped
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2BPPeWMdYEmYbN6eGCBSg
2026-09-10 21:59:07 +02:00

146 lines
4.4 KiB
TypeScript

import { config } from '@vue/test-utils'
import { vi } from 'vitest'
import * as Vue from 'vue'
// Mock localStorage with actual implementation
class LocalStorageMock {
private store: Map<string, string> = new Map()
getItem(key: string): string | null {
return this.store.get(key) ?? null
}
setItem(key: string, value: string): void {
this.store.set(key, value)
}
removeItem(key: string): void {
this.store.delete(key)
}
clear(): void {
this.store.clear()
}
key(index: number): string | null {
return Array.from(this.store.keys())[index] ?? null
}
get length(): number {
return this.store.size
}
}
// 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
global.computed = Vue.computed
global.reactive = Vue.reactive
global.watch = Vue.watch
global.watchEffect = Vue.watchEffect
global.defineModel = Vue.defineModel
global.onMounted = Vue.onMounted
global.onUnmounted = Vue.onUnmounted
global.nextTick = Vue.nextTick
// Mock Nuxt composables
global.useRuntimeConfig = vi.fn(() => ({
public: {},
}))
global.useNuxtApp = vi.fn(() => ({
$apollo: {},
}))
global.navigateTo = vi.fn()
global.definePageMeta = vi.fn()
// Mock Vuetify components globally for tests
config.global.stubs = {
VAlert: {
name: 'v-alert',
template: '<div class="v-alert" :type="type" :variant="variant" :color="color"><slot /></div>',
props: ['color', 'type', 'variant'],
},
VTextField: {
template: '<div class="v-text-field"><input :readonly="readonly || disabled" :disabled="disabled" :error="error" :value="modelValue" @input="$emit(\'update:model-value\', $event.target.value)" @blur="$emit(\'blur\')" /></div>',
props: ['modelValue', 'readonly', 'disabled', 'active', 'focused', 'variant', 'error', 'errorMessages', 'label'],
emits: ['update:model-value', 'blur'],
},
VBtn: {
name: 'v-btn',
template: '<button :disabled="disabled" :color="color" :data-testid="$attrs[\'data-testid\']" @click="$emit(\'click\')"><slot /></button>',
props: ['disabled', 'variant', 'icon', 'color', 'flat', 'depressed', 'xLarge'],
emits: ['click'],
},
VDialog: {
name: 'v-dialog',
template: '<div class="v-dialog"><slot name="activator" /><div class="v-dialog__content"><slot /></div></div>',
props: ['modelValue', 'width', 'maxWidth', 'persistent'],
},
VForm: {
name: 'v-form',
template: '<form @submit="$emit(\'submit\', $event)"><slot /></form>',
emits: ['submit'],
},
VCard: {
name: 'v-card',
template: '<div class="v-card" :variant="variant" :rounded="rounded" :loading="loading"><div v-if="title" class="v-card-title">{{ title }}</div><slot /></div>',
props: ['variant', 'rounded', 'loading', 'title'],
},
VCardTitle: {
template: '<div class="v-card-title"><slot /></div>',
},
VCardText: {
name: 'v-card-text',
template: '<div class="v-card-text"><slot /></div>',
},
VCardActions: {
name: 'v-card-actions',
template: '<div class="v-card-actions"><slot /></div>',
},
VSpacer: {
name: 'v-spacer',
template: '<div class="v-spacer"></div>',
},
VIcon: {
name: 'v-icon',
template: '<i class="v-icon" :class="icon" @click="$emit(\'click\')"></i>',
props: ['icon', 'large', 'start', 'end', 'size'],
emits: ['click'],
},
VMenu: {
name: 'v-menu',
template: '<div class="v-menu"><slot name="activator" :props="{}" /><slot /></div>',
props: ['modelValue', 'closeOnContentClick'],
},
VList: {
name: 'v-list',
template: '<div class="v-list"><slot /></div>',
},
VListItem: {
name: 'v-list-item',
template: '<div class="v-list-item" :data-active="active" @click="$emit(\'click\')"><slot /><slot name="append" /></div>',
props: ['active', 'value'],
emits: ['click'],
},
VListItemTitle: {
name: 'v-list-item-title',
template: '<div class="v-list-item-title"><slot /></div>',
},
VChip: {
name: 'v-chip',
template: '<div class="v-chip" :class="variant"><slot /></div>',
props: ['size', 'variant', 'prependIcon'],
},
}