You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
memos/web/tests/setup.ts

84 lines
3.2 KiB
TypeScript

import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";
// With `globals: false`, @testing-library/react does not auto-register a
// cleanup hook, so unmount rendered trees between tests explicitly. This keeps
// `screen.getByTestId` from seeing DOM from prior tests in the same file.
afterEach(() => {
cleanup();
});
// ProseMirror probes layout APIs jsdom doesn't implement.
if (typeof document !== "undefined") {
if (!document.elementFromPoint) {
document.elementFromPoint = () => null;
}
if (typeof Range !== "undefined" && !Range.prototype.getClientRects) {
Range.prototype.getClientRects = () => [] as unknown as DOMRectList;
Range.prototype.getBoundingClientRect = () =>
({ x: 0, y: 0, top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0, toJSON: () => ({}) }) as DOMRect;
}
// CodeMirror probes additional layout APIs jsdom doesn't implement while measuring.
if (typeof Element !== "undefined" && !Element.prototype.getClientRects) {
Element.prototype.getClientRects = () => [] as unknown as DOMRectList;
}
}
// jsdom runs with an opaque origin (no URL set), so its built-in localStorage
// implementation throws SecurityError on every access. Install a Map-backed shim
// so any test that touches localStorage directly can call getItem/setItem/removeItem/clear
// without additional per-file setup. Tests that need special behavior (hooks.test.tsx,
// memo-editor-cache.test.ts) override this via Object.defineProperty / vi.stubGlobal.
if (typeof globalThis.localStorage === "undefined" || typeof globalThis.localStorage.clear !== "function") {
let _store = new Map<string, string>();
const localStorageShim: Storage = {
get length() {
return _store.size;
},
getItem: (key: string) => _store.get(key) ?? null,
setItem: (key: string, value: string) => _store.set(key, value),
removeItem: (key: string) => _store.delete(key),
clear: () => {
_store = new Map<string, string>();
},
key: (index: number) => Array.from(_store.keys())[index] ?? null,
};
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
value: localStorageShim,
});
}
// Defensive shim: `@/auth-state` constructs a BroadcastChannel at module load
// to coordinate token refreshes across tabs. jsdom historically has not shipped
// BroadcastChannel, so any future test that transitively imports auth-state
// would otherwise throw. Current tests avoid that import path on purpose, but
// installing the shim keeps authoring new tests frictionless. No-op when jsdom
// already provides an implementation.
if (typeof globalThis.BroadcastChannel === "undefined") {
class NoopBroadcastChannel {
readonly name: string;
onmessage: ((event: MessageEvent) => void) | null = null;
constructor(name: string) {
this.name = name;
}
postMessage(_data: unknown): void {}
close(): void {}
addEventListener(): void {}
removeEventListener(): void {}
dispatchEvent(): boolean {
return true;
}
}
// @ts-expect-error — attach the shim to the global scope for tests.
globalThis.BroadcastChannel = NoopBroadcastChannel;
}