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,
|
||||
notifyOnApproval: true,
|
||||
notifyOnStatusChange: true,
|
||||
remoteHosts: [],
|
||||
selectedHostId: null,
|
||||
}
|
||||
|
||||
describe('defaultPrefs', () => {
|
||||
@@ -60,6 +62,8 @@ describe('validatePrefs', () => {
|
||||
openAtLogin: true,
|
||||
notifyOnApproval: false,
|
||||
notifyOnStatusChange: false,
|
||||
remoteHosts: [],
|
||||
selectedHostId: null,
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
Reference in New Issue
Block a user