test: fix pre-existing failures — localStorage polyfill + prefs shape drift
- jsdom 29's localStorage methods are undefined under this vitest config, so every frontend test calling localStorage.clear()/setItem() threw. Add a shared in-memory Web Storage polyfill via setupFiles (no-op when a working Storage exists / in node env). Fixes quick-reply, push, projects-panel (135 tests). - Update desktop prefs test: defaultPrefs gained remoteHosts/selectedHostId (remote-host mTLS work); sync EXPECTED_DEFAULTS and the round-trip fixture. Full suite now green: 1473 passed (was 142 failing).
This commit is contained in:
@@ -18,6 +18,8 @@ const EXPECTED_DEFAULTS: DesktopPrefs = {
|
|||||||
openAtLogin: false,
|
openAtLogin: false,
|
||||||
notifyOnApproval: true,
|
notifyOnApproval: true,
|
||||||
notifyOnStatusChange: true,
|
notifyOnStatusChange: true,
|
||||||
|
remoteHosts: [],
|
||||||
|
selectedHostId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('defaultPrefs', () => {
|
describe('defaultPrefs', () => {
|
||||||
@@ -60,6 +62,8 @@ describe('validatePrefs', () => {
|
|||||||
openAtLogin: true,
|
openAtLogin: true,
|
||||||
notifyOnApproval: false,
|
notifyOnApproval: false,
|
||||||
notifyOnStatusChange: false,
|
notifyOnStatusChange: false,
|
||||||
|
remoteHosts: [],
|
||||||
|
selectedHostId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
66
test/setup/local-storage.ts
Normal file
66
test/setup/local-storage.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* test/setup/local-storage.ts — in-memory Web Storage polyfill for the test env.
|
||||||
|
*
|
||||||
|
* jsdom 29 (the `@vitest-environment jsdom` files) exposes a `localStorage`
|
||||||
|
* object whose Storage methods (getItem/setItem/clear/…) are `undefined` under
|
||||||
|
* this vitest config — so any test that calls `localStorage.clear()` throws
|
||||||
|
* "localStorage.clear is not a function". This registered setupFile installs a
|
||||||
|
* spec-compliant in-memory Storage whenever the ambient one is missing or
|
||||||
|
* non-functional, so frontend unit tests (quick-reply, push, projects-panel)
|
||||||
|
* get real persistence semantics. It is a no-op if a working Storage already
|
||||||
|
* exists, and harmless in the node environment (just adds an unused global).
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MemoryStorage implements Storage {
|
||||||
|
private store = new Map<string, string>()
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return this.store.size
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.store.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
getItem(key: string): string | null {
|
||||||
|
return this.store.has(key) ? (this.store.get(key) as string) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem(key: string, value: string): void {
|
||||||
|
this.store.set(String(key), String(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(key: string): void {
|
||||||
|
this.store.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
key(index: number): string | null {
|
||||||
|
return Array.from(this.store.keys())[index] ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True when `s` is a usable Storage (has a callable `clear`). */
|
||||||
|
function isUsableStorage(s: unknown): s is Storage {
|
||||||
|
return s != null && typeof (s as Storage).clear === 'function'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Install an in-memory Storage on `target[name]` unless a working one exists. */
|
||||||
|
function ensureStorage(target: Record<string, unknown>, name: string): void {
|
||||||
|
if (isUsableStorage(target[name])) return
|
||||||
|
Object.defineProperty(target, name, {
|
||||||
|
value: new MemoryStorage(),
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalTarget = globalThis as unknown as Record<string, unknown>
|
||||||
|
ensureStorage(globalTarget, 'localStorage')
|
||||||
|
ensureStorage(globalTarget, 'sessionStorage')
|
||||||
|
|
||||||
|
// Mirror onto `window` (jsdom env) so `window.localStorage` resolves too.
|
||||||
|
const win = (globalThis as { window?: Record<string, unknown> }).window
|
||||||
|
if (win) {
|
||||||
|
ensureStorage(win, 'localStorage')
|
||||||
|
ensureStorage(win, 'sessionStorage')
|
||||||
|
}
|
||||||
@@ -4,6 +4,10 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
include: ['test/**/*.test.ts'],
|
include: ['test/**/*.test.ts'],
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
|
// jsdom 29's localStorage methods are undefined under this config; a shared
|
||||||
|
// in-memory Web Storage polyfill fixes the frontend (@vitest-environment
|
||||||
|
// jsdom) tests that call localStorage.clear()/setItem(). No-op in node env.
|
||||||
|
setupFiles: ['test/setup/local-storage.ts'],
|
||||||
// Scaffold has no tests yet; don't fail the script until modules add theirs.
|
// Scaffold has no tests yet; don't fail the script until modules add theirs.
|
||||||
passWithNoTests: true,
|
passWithNoTests: true,
|
||||||
// Coverage target is the global 80% rule; enforced with `--coverage`.
|
// Coverage target is the global 80% rule; enforced with `--coverage`.
|
||||||
|
|||||||
Reference in New Issue
Block a user