Files
gitmost/apps/client/vitest.setup.ts
T
agent_coder 90c02a0a66 feat(client): страница управления API-ключами в настройках аккаунта (#506)
Фаза 2 поверх серверных эндпоинтов #501: страница «Настройки → Аккаунт →
API-ключи» (lazy-роут, отдельный чанк) — список, создание, показ токена
один раз и отзыв.

- Список: явная дата истечения (не «через N дней»), подсветка ключей с
  истечением < 30 дней, «использован» с семантикой «в течение последнего
  часа» (last_used_at троттлится на 1 ч серверно).
- Создание: имя + срок (30д/90д/1 год[дефолт]/бессрочный → null). После
  submit — модалка показа токена один раз с копированием и явной датой.
- Токен-материал живёт ТОЛЬКО в state открытой модалки: mutateAsync +
  reset() чистит копию из query-cache, ничего не пишется в localStorage.
  Закрытие модалки — токен исчезает навсегда.
- Отзыв: подтверждение → revoke → строка уходит из списка.
- Admin (CASL Manage на API = owner/admin) видит ключи всего воркспейса с
  колонкой «автор»; обычный член — только свои.

Тесты (vitest): показ-один-раз + отсутствие токена в localStorage/кэше,
явная дата + подсветка < 30 дней, отзыв убирает строку, admin/member вид,
дефолт срока = 1 год и «бессрочный» → null. Стаб ResizeObserver добавлен в
общий vitest.setup для рендера Mantine ScrollArea/Table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 01:23:15 +03:00

78 lines
2.8 KiB
TypeScript

// Vitest global setup (test-infra only — no production app source).
//
// Under Node 25 / jsdom 25 / vitest 4 the jsdom `localStorage` exposed on the
// global is not a usable Storage: its methods (`setItem`/`getItem`/...) are not
// callable, so any code touching `localStorage` throws `... is not a function`.
// Production code such as `isHtmlEmbedFeatureEnabled()` reads
// `localStorage.getItem("currentUser")`, which made dependent tests fail.
//
// We install a correct in-memory Storage stub on the global BEFORE tests run so
// the Web Storage contract holds: string coercion of keys/values, `null` for
// missing keys, working `length`/`key(index)`, and `clear()`.
import { vi } from "vitest";
// Minimal, spec-faithful in-memory implementation of the Web Storage API.
function createStorage(): Storage {
let store = new Map<string, string>();
const storage: Storage = {
get length(): number {
return store.size;
},
clear(): void {
store = new Map<string, string>();
},
getItem(key: string): string | null {
// Missing keys must return `null`, not `undefined`.
const value = store.get(String(key));
return value === undefined ? null : value;
},
setItem(key: string, value: string): void {
// Web Storage coerces both key and value to strings.
store.set(String(key), String(value));
},
removeItem(key: string): void {
store.delete(String(key));
},
key(index: number): string | null {
// Insertion order matches Map iteration order; out-of-range => null.
const keys = Array.from(store.keys());
return index >= 0 && index < keys.length ? keys[index] : null;
},
};
return storage;
}
// Install on the jsdom global. `vi.stubGlobal` also reflects onto `window`
// (jsdom shares `globalThis` and `window`), so both `localStorage` and
// `window.localStorage` resolve to the same working stub.
vi.stubGlobal("localStorage", createStorage());
vi.stubGlobal("sessionStorage", createStorage());
// MantineProvider (and other components) read `window.matchMedia` on mount, which
// jsdom does not implement. Provide a minimal stub here so any test rendering
// Mantine works without re-stubbing matchMedia in every file.
vi.stubGlobal("matchMedia", (query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
}));
// Mantine's ScrollArea (used by Table.ScrollContainer, ScrollArea, etc.) reads
// `ResizeObserver` in a layout effect on mount, which jsdom does not implement.
// A no-op stub lets any test rendering those components mount cleanly.
vi.stubGlobal(
"ResizeObserver",
class {
observe() {}
unobserve() {}
disconnect() {}
},
);