Initial Commit
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { webcrypto } from "node:crypto";
|
||||
import z from "zod";
|
||||
import { TEST_VITE_CODES, TestViteError } from "../error-codes/error-codes.js";
|
||||
|
||||
const BrowserEnvironmentOptionsSchema = z
|
||||
.object({
|
||||
target: z.unknown().optional(),
|
||||
mockLocalStorage: z.boolean().default(true),
|
||||
mockMatchMedia: z.boolean().default(true),
|
||||
mockResizeObserver: z.boolean().default(true),
|
||||
mockIntersectionObserver: z.boolean().default(true),
|
||||
mockCrypto: z.boolean().default(true),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type BrowserEnvironmentOptions = z.input<
|
||||
typeof BrowserEnvironmentOptionsSchema
|
||||
>;
|
||||
|
||||
type TargetRecord = Record<string, unknown>;
|
||||
|
||||
class MemoryStorage {
|
||||
private readonly store = new Map<string, string>();
|
||||
public get length(): number {
|
||||
return this.store.size;
|
||||
}
|
||||
public clear(): void {
|
||||
this.store.clear();
|
||||
}
|
||||
public getItem(key: string): string | null {
|
||||
return this.store.has(key) ? (this.store.get(key) as string) : null;
|
||||
}
|
||||
public key(index: number): string | null {
|
||||
return Array.from(this.store.keys())[index] ?? null;
|
||||
}
|
||||
public removeItem(key: string): void {
|
||||
this.store.delete(key);
|
||||
}
|
||||
public setItem(key: string, value: string): void {
|
||||
this.store.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
function fakeMatchMedia(query: string): {
|
||||
matches: boolean;
|
||||
media: string;
|
||||
addEventListener: () => void;
|
||||
removeEventListener: () => void;
|
||||
addListener: () => void;
|
||||
removeListener: () => void;
|
||||
onchange: null;
|
||||
dispatchEvent: () => boolean;
|
||||
} {
|
||||
return {
|
||||
matches: false,
|
||||
media: query,
|
||||
addEventListener: () => undefined,
|
||||
removeEventListener: () => undefined,
|
||||
addListener: () => undefined,
|
||||
removeListener: () => undefined,
|
||||
onchange: null,
|
||||
dispatchEvent: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
class FakeResizeObserver {
|
||||
public observe(): void {
|
||||
/* noop */
|
||||
}
|
||||
public unobserve(): void {
|
||||
/* noop */
|
||||
}
|
||||
public disconnect(): void {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
class FakeIntersectionObserver {
|
||||
public readonly root: null = null;
|
||||
public readonly rootMargin: string = "";
|
||||
public readonly thresholds: readonly number[] = [];
|
||||
public observe(): void {
|
||||
/* noop */
|
||||
}
|
||||
public unobserve(): void {
|
||||
/* noop */
|
||||
}
|
||||
public disconnect(): void {
|
||||
/* noop */
|
||||
}
|
||||
public takeRecords(): readonly unknown[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install DOM environment shims on `target` (defaults to `globalThis`).
|
||||
*
|
||||
* Each shim is installed only when absent on the target — re-running the
|
||||
* function on an already-set-up target is a no-op. Individual shims can
|
||||
* be skipped with the `mock*: false` flags.
|
||||
*
|
||||
* @throws {TestViteError} with `TEST_VITE.INVALID_OPTIONS` on bad input.
|
||||
*/
|
||||
export function setupBrowserEnvironment(
|
||||
opts: BrowserEnvironmentOptions = {}
|
||||
): void {
|
||||
const parsed = BrowserEnvironmentOptionsSchema.safeParse(opts);
|
||||
if (!parsed.success) {
|
||||
throw new TestViteError(
|
||||
TEST_VITE_CODES.INVALID_OPTIONS,
|
||||
"setupBrowserEnvironment: invalid options",
|
||||
{ details: { issues: parsed.error.issues } }
|
||||
);
|
||||
}
|
||||
const o = parsed.data;
|
||||
const target = (o.target ?? globalThis) as TargetRecord;
|
||||
if (o.mockLocalStorage && target["localStorage"] === undefined) {
|
||||
target["localStorage"] = new MemoryStorage();
|
||||
}
|
||||
if (o.mockMatchMedia && target["matchMedia"] === undefined) {
|
||||
target["matchMedia"] = fakeMatchMedia;
|
||||
}
|
||||
if (o.mockResizeObserver && target["ResizeObserver"] === undefined) {
|
||||
target["ResizeObserver"] = FakeResizeObserver;
|
||||
}
|
||||
if (
|
||||
o.mockIntersectionObserver &&
|
||||
target["IntersectionObserver"] === undefined
|
||||
) {
|
||||
target["IntersectionObserver"] = FakeIntersectionObserver;
|
||||
}
|
||||
if (o.mockCrypto && target["crypto"] === undefined) {
|
||||
target["crypto"] = webcrypto;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user