/** * Shared vitest setup for core and for every plugin suite. * * The root vitest.setup.ts re-exports this so there is one copy of the * polyfills rather than two that drift apart. */ import { afterEach, vi } from "vitest"; import { installSafePorts } from "./safe-ports.js"; installSafePorts(); // Radix measures some controls (Checkbox, Switch) and jsdom has no observer. if (typeof globalThis.ResizeObserver === "undefined") { globalThis.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} } as unknown as typeof ResizeObserver; } if (typeof window !== "undefined" && !window.matchMedia) { window.matchMedia = vi.fn().mockImplementation((query: string) => ({ matches: false, media: query, onchange: null, addEventListener: vi.fn(), removeEventListener: vi.fn(), addListener: vi.fn(), removeListener: vi.fn(), dispatchEvent: vi.fn(), })); } /** * jsdom in this project ships without a `localStorage` global, and Node only * provides one when started with --localstorage-file (which persists to disk * and is shared across test files -- both wrong for tests). Suites that touch * storage therefore threw `Cannot read properties of undefined`, taking out * every test in the file rather than just the ones that used it. * * A per-process in-memory implementation is enough: it satisfies the Storage * interface the app uses and keeps nothing on disk. */ if (typeof globalThis.localStorage === "undefined") { const createMemoryStorage = (): Storage => { const store = new Map(); return { get length() { return store.size; }, key: (index: number) => Array.from(store.keys())[index] ?? null, getItem: (key: string) => (store.has(key) ? store.get(key)! : null), setItem: (key: string, value: string) => { store.set(key, String(value)); }, removeItem: (key: string) => { store.delete(key); }, clear: () => { store.clear(); }, } as Storage; }; Object.defineProperty(globalThis, "localStorage", { value: createMemoryStorage(), configurable: true, writable: true, }); Object.defineProperty(globalThis, "sessionStorage", { value: createMemoryStorage(), configurable: true, writable: true, }); } /** * A plain plugin host for components a test renders on its own: keys come * back as text, every permission is held, and nothing reaches a server. * renderWithApp installs Termix's real host instead, which takes precedence. */ const noop = () => {}; const offline = () => Promise.reject(new Error("No plugin API in a unit test; mock it")); (globalThis as Record).__termixTestPluginHost = { usePluginId: () => "test-plugin", useDocsUrl: () => null, useTranslation: () => ({ t: (key: string) => key, language: "en", }), usePermission: () => true, useSettings: () => ({ values: {}, loaded: true, save: async () => {} }), useHost: () => null, useHosts: () => ({ hosts: [], loaded: true }), useHostStatus: () => null, useCurrentUser: () => null, useTheme: () => ({ theme: "dark" }), toast: { success: noop, error: noop, info: noop, warning: noop }, getApi: () => ({ get: offline, delete: offline, post: offline, put: offline, patch: offline, }), useTabs: () => ({ openTab: noop, openSingletonTab: noop, connectHost: noop, closeTab: noop, getLayout: () => null, applyLayout: async () => ({ skipped: [] }), onChange: () => noop, onReady: () => noop, }), invokeAction: async () => undefined, useSshAuthTypes: () => ({ types: ["password", "key", "credential", "agent", "none"].map((type) => ({ type, labelKey: type, pluginId: "core", credentialType: type === "password" || type === "key", supportsBackground: type !== "none", quickConnect: true, })), loaded: true, }), useSlotContributions: () => [], usePluginComponent: () => undefined, getApiFor: () => ({ get: offline, delete: offline, post: offline, put: offline, patch: offline, }), hostProtocols: () => ["ssh"], useHostActions: () => [], useActivityTypes: () => [], activityTarget: () => undefined, useExtensions: () => [], getExtension: () => undefined, usePluginUiPreferences: () => ({ values: {}, set: noop }), core: { logActivity: async () => {}, getHostPassword: async () => null, patchOpenTab: async () => {}, getCustomKeybindings: async () => [], runKeybindingAction: () => false, getClientPreference: () => undefined, setClientPreference: () => {}, listHosts: async () => [], listCredentials: async () => [], notifyHostsChanged: () => {}, getHostStatusColorScheme: () => "accent", getLocalAuthToken: () => null, }, }; afterEach(() => { vi.restoreAllMocks(); });