Files
hermes-agent/apps/desktop/vitest.setup.ts
teknium1 6cb7a64e59 revert: Skills and Plugins card catalog browser, third landing (reverts #119381)
Reverts all seven commits of #119381 (59c54c78..476ff4dd), which
re-landed the native card-based Skills/Plugins catalog browser after it
was reverted twice (#113037 reverting #112669, #115787 reverting
#113128). Capabilities -> Skills and Plugins return to the list/detail
tabs with the embedded catalog picker they had before #119381.

Removed with it:
- Desktop catalog/ module (browser, cards, detail dialog, filters,
  reels/masonry, install switch) and skill deep-link install
  (hermes://skill/install); skills/embedded-hub-picker.tsx comes back.
- apps/shared catalog-browse.ts and catalog-install.ts.
- The `catalog` i18n namespace in every locale (+ regenerated
  locales/_keys.desktop.json); the two i18n pack tests and the keys
  test now exercise connectorsPage.searchPlaceholder instead.
- Website /skills "Open in Hermes Desktop" skill links and the
  extract-skills install identifiers; docs return to the pre-#119381 text.
- Later fixes that only patched the reverted catalog code go with it:
  5832a6eb93 + 7aba2cbbe9 (catalog download deadline),
  9e63cdc4b8 (catalog lookalike rows), c756986db2 (catalog counts),
  the catalog half of c13232e90c / f265273347.

Kept on purpose (later work that depends on code #119381 introduced):
- DialogContent `chrome` / `overlayClassName` / nested-dialog layering
  (the image lightbox uses them).
- vitest.setup.ts / src/test/jsdom.ts stubs (other suites rely on them).
- Kanban toolset wiring on the Plugins Desktop switch (cd30fdadf6,
  7ca2f5665f) and its tests, the `hermes_not_connected` server tone,
  the SearchField flex-1 fix, skill provenance helpers. SkillDetail shows
  a provenance pill only for agent/hub skills, since the pre-#119381
  labels have no `external` entry.
2026-10-02 10:29:28 -07:00

81 lines
3.5 KiB
TypeScript

import { configure } from '@testing-library/react'
import { InertResizeObserver } from './src/test/jsdom'
// Shared tooltips measure their arrow through Radix's useSize hook.
// Geometry assertions still belong in a real browser, not this inert observer. Assigned,
// not `vi.stubGlobal`, so a test's `vi.unstubAllGlobals()` cannot strip it.
globalThis.ResizeObserver ??= InertResizeObserver as unknown as typeof ResizeObserver
// Node 26 defines its own `localStorage` accessor on the global object, which
// returns `undefined` unless the process was started with --localstorage-file
// (it warns: "localStorage is not available because --localstorage-file was
// not provided"). In the jsdom environment `globalThis` IS the window, so that
// accessor shadows jsdom's Storage and every `localStorage.getItem(...)` in a
// test throws "Cannot read properties of undefined". Install a real in-memory
// Storage when the global resolves to nothing, before any test module reads it.
if (typeof (globalThis as any).localStorage === 'undefined') {
const store = new Map<string, string>()
const storage: Storage = {
get length() {
return store.size
},
key: (i: number) => [...store.keys()][i] ?? null,
getItem: (k: string) => store.get(String(k)) ?? null,
setItem: (k: string, v: string) => void store.set(String(k), String(v)),
removeItem: (k: string) => void store.delete(String(k)),
clear: () => store.clear()
}
for (const target of [globalThis, (globalThis as any).window].filter(Boolean)) {
Object.defineProperty(target, 'localStorage', {
value: storage,
configurable: true,
writable: true
})
}
}
// jsdom has no layout or intersection delivery. Tests of observer behavior
// supply their own callbacks; ordinary component tests only need the lifecycle.
globalThis.IntersectionObserver = class {
readonly root = null
readonly rootMargin = '0px'
readonly thresholds = [0]
observe() {}
unobserve() {}
disconnect() {}
takeRecords(): IntersectionObserverEntry[] {
return []
}
} as typeof IntersectionObserver
// Idle prefetches and feature queries (`CSS.supports`) run on mount.
// jsdom has neither API; idle work never fires and every feature reads as absent.
globalThis.requestIdleCallback ??= () => 0
globalThis.cancelIdleCallback ??= () => undefined
globalThis.CSS ??= { escape: (value: string) => value, supports: () => false } as unknown as typeof CSS
if ('Element' in globalThis) {
Element.prototype.scrollTo ??= () => undefined
// React 19 + Testing Library 16: opt into the act environment so render(),
// fireEvent(), and findBy* queries automatically flush state updates without
// spurious "not wrapped in act(...)" warnings.
}
;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true
// findBy*/waitFor default to a 1000ms deadline — too tight for async-heavy
// panels (radix menus, refetch chains) when the full suite runs under xdist
// CPU contention in CI. Success still resolves the instant the node appears;
// the wider deadline only absorbs a starved runner, killing timing flakes.
// 5s proved insufficient on saturated runners (2026-08-31: gateway-settings,
// messaging, session-unread-tile, toolset-config-panel each tripped a
// waitFor(mock-called) deadline on runs whose only common factor was load —
// including a plugins-only commit on main). 12s mirrors the same reasoning
// as the 15s testTimeout above it while still finishing below it, so a
// genuinely hung await still surfaces as this assertion, not a test timeout.
configure({ asyncUtilTimeout: 12_000 })