213 lines
5 KiB
TypeScript
Executable file
213 lines
5 KiB
TypeScript
Executable file
/**
|
|
* Browser API Mocks
|
|
*
|
|
* Common browser API mocks for testing React components
|
|
* in a jsdom environment.
|
|
*/
|
|
|
|
import { vi } from 'vitest'
|
|
|
|
/**
|
|
* Mock localStorage with in-memory storage
|
|
*/
|
|
export function mockLocalStorage(): Storage {
|
|
const store: Record<string, string> = {}
|
|
|
|
const storageMock: Storage = {
|
|
getItem: vi.fn((key: string) => store[key] || null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store[key] = value
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
delete store[key]
|
|
}),
|
|
clear: vi.fn(() => {
|
|
for (const key in store) {
|
|
delete store[key]
|
|
}
|
|
}),
|
|
key: vi.fn((index: number) => Object.keys(store)[index] || null),
|
|
get length() {
|
|
return Object.keys(store).length
|
|
},
|
|
}
|
|
|
|
Object.defineProperty(window, 'localStorage', {
|
|
value: storageMock,
|
|
writable: true,
|
|
})
|
|
|
|
return storageMock
|
|
}
|
|
|
|
/**
|
|
* Mock sessionStorage with in-memory storage
|
|
*/
|
|
export function mockSessionStorage(): Storage {
|
|
const store: Record<string, string> = {}
|
|
|
|
const storageMock: Storage = {
|
|
getItem: vi.fn((key: string) => store[key] || null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store[key] = value
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
delete store[key]
|
|
}),
|
|
clear: vi.fn(() => {
|
|
for (const key in store) {
|
|
delete store[key]
|
|
}
|
|
}),
|
|
key: vi.fn((index: number) => Object.keys(store)[index] || null),
|
|
get length() {
|
|
return Object.keys(store).length
|
|
},
|
|
}
|
|
|
|
Object.defineProperty(window, 'sessionStorage', {
|
|
value: storageMock,
|
|
writable: true,
|
|
})
|
|
|
|
return storageMock
|
|
}
|
|
|
|
/**
|
|
* Mock window.matchMedia for responsive components
|
|
*/
|
|
export function mockMatchMedia(defaultMatches = false): void {
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
writable: true,
|
|
value: vi.fn().mockImplementation((query: string) => ({
|
|
matches: defaultMatches,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
})),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mock IntersectionObserver for lazy-loading components
|
|
* Sets on both window and global for maximum compatibility
|
|
*/
|
|
export function mockIntersectionObserver(): void {
|
|
const mockIntersectionObserver = vi.fn().mockImplementation(() => ({
|
|
observe: vi.fn(),
|
|
unobserve: vi.fn(),
|
|
disconnect: vi.fn(),
|
|
takeRecords: vi.fn(() => []),
|
|
}))
|
|
|
|
Object.defineProperty(window, 'IntersectionObserver', {
|
|
writable: true,
|
|
value: mockIntersectionObserver,
|
|
})
|
|
|
|
Object.defineProperty(global, 'IntersectionObserver', {
|
|
writable: true,
|
|
value: mockIntersectionObserver,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mock ResizeObserver for responsive components
|
|
* Sets on both window and global for maximum compatibility
|
|
*/
|
|
export function mockResizeObserver(): void {
|
|
const mockResizeObserver = vi.fn().mockImplementation(() => ({
|
|
observe: vi.fn(),
|
|
unobserve: vi.fn(),
|
|
disconnect: vi.fn(),
|
|
}))
|
|
|
|
Object.defineProperty(window, 'ResizeObserver', {
|
|
writable: true,
|
|
value: mockResizeObserver,
|
|
})
|
|
|
|
Object.defineProperty(global, 'ResizeObserver', {
|
|
writable: true,
|
|
value: mockResizeObserver,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mock window.scrollTo for scroll behavior
|
|
* Sets on both window and global for maximum compatibility
|
|
*/
|
|
export function mockScrollTo(): void {
|
|
const mockScrollToFn = vi.fn()
|
|
|
|
Object.defineProperty(window, 'scrollTo', {
|
|
writable: true,
|
|
value: mockScrollToFn,
|
|
})
|
|
|
|
Object.defineProperty(global, 'scrollTo', {
|
|
writable: true,
|
|
value: mockScrollToFn,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mock BroadcastChannel for cross-tab communication
|
|
*/
|
|
export function mockBroadcastChannel(): void {
|
|
const channels: Map<string, Set<(event: MessageEvent) => void>> = new Map()
|
|
|
|
class MockBroadcastChannel {
|
|
name: string
|
|
onmessage: ((event: MessageEvent) => void) | null = null
|
|
|
|
constructor(name: string) {
|
|
this.name = name
|
|
if (!channels.has(name)) {
|
|
channels.set(name, new Set())
|
|
}
|
|
}
|
|
|
|
postMessage(data: unknown): void {
|
|
const listeners = channels.get(this.name)
|
|
if (listeners) {
|
|
const event = new MessageEvent('message', { data })
|
|
listeners.forEach((listener) => {
|
|
if (listener !== this.onmessage) {
|
|
listener(event)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
addEventListener(_type: string, listener: (event: MessageEvent) => void): void {
|
|
const listeners = channels.get(this.name)
|
|
if (listeners) {
|
|
listeners.add(listener)
|
|
}
|
|
}
|
|
|
|
removeEventListener(_type: string, listener: (event: MessageEvent) => void): void {
|
|
const listeners = channels.get(this.name)
|
|
if (listeners) {
|
|
listeners.delete(listener)
|
|
}
|
|
}
|
|
|
|
close(): void {
|
|
const listeners = channels.get(this.name)
|
|
if (listeners) {
|
|
listeners.clear()
|
|
}
|
|
}
|
|
}
|
|
|
|
Object.defineProperty(window, 'BroadcastChannel', {
|
|
writable: true,
|
|
value: MockBroadcastChannel,
|
|
})
|
|
}
|