Commit graph inspector/clients/web/src/test
Author SHA1 Message Date
Cliff Hall
e3e70470b5
feat(core): port v1.5 hook layer (#1243) (#1300)
* feat(core): port v1.5 hook layer (useInspectorClient + useManaged*/usePaged* + log hooks)

Wholesale port of v1.5's React hook layer and the EventTarget-based state machinery the hooks subscribe to (#1243). Hooks decouple from the concrete InspectorClient via a new `InspectorClientProtocol` interface so this layer can land before the runtime class is ported.

Added under `core/`:
- `core/react/` — useInspectorClient + 5 useManaged* + 5 usePaged* + 3 log hooks
- `core/mcp/state/` — 5 managed*State + 5 paged*State + messageLogState + stderrLogState + fetchRequestLogState
- `core/mcp/inspectorClientProtocol.ts`, `inspectorClientEventTarget.ts`, `typedEventTarget.ts`, `samplingCreateMessage.ts`, `sessionStorage.ts`
- v2-only wrapper types in `core/mcp/types.ts`: InspectorUrlElicitRequest, InspectorPendingRequest, InspectorRequestHistoryItem

Tests live under `clients/web/src/test/core/` (28 new test files, 264 new tests). Test fake: `core/mcp/__tests__/fakeInspectorClient.ts`.

Build wiring:
- `vite.config.ts`: dedupe react/react-dom so core/ files resolve them via clients/web/node_modules.
- `tsconfig.app.json` / `tsconfig.test.json`: paths map react/react-dom/vitest to clients/web/node_modules so `tsc -b` resolves bare imports from core/ source files (which sit above the package's node_modules). Exclude core/__tests__ from app config; include in test config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: address PR #1300 review findings

Bugs:
- fetchRequestLogState: add .catch() on sessionStorage.loadSession so a corrupt
  or unreadable session doesn't surface as an unhandled rejection (matches the
  saveSession swallow pattern).
- messageLogState: sync pendingRequestEntries when clearMessages() evicts a
  pending request entry, so a later response with the same id falls through
  to the append branch instead of mutating an unreachable map entry. Add tests
  for both predicate and no-predicate paths.

Cleanups:
- Extract mergeTaskIntoList helper to core/mcp/state/mergeTaskIntoList.ts so
  managed/paged RequestorTasks state classes share one definition.
- Add a one-line JSDoc note on each PagedTools/Prompts/Resources/Templates
  state explaining why they deliberately do not subscribe to *ListChanged
  (cursor invalidation mid-pagination).
- Document on useInspectorClient that appRendererClient is read lazily on
  every render and is not subscribed (changes once at connect time).

Carry-overs from v1.5 deliberately left for the follow-up InspectorClient
runtime port:
- Refresh/status-change race in Managed*State.refresh() (status check is only
  at entry; a disconnect during await can re-clear before refresh resumes).
- nextCursor exposure asymmetry across usePaged* (only usePagedRequestorTasks
  surfaces it via state field; others return it from loadPage() only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:43:07 -04:00
Cliff Hall
1ffcdc38ca
feat(apps): add AppListItem and AppDetailPanel groups (#1261) (#1273)
* feat(apps): add AppListItem and AppDetailPanel groups (#1261)

Two presentational groups for the upcoming AppsScreen.

AppListItem (clients/web/src/components/groups/AppListItem/):
- Props { tool, selected, onClick }
- Renders tool.icons[0] (sized sm) when present, label (title ?? name),
  two-line clamped description, and a trailing chevron affordance.
- Reuses ToolListItem's selected-state highlight (UnstyledButton bg via
  --mantine-primary-color-light).

AppDetailPanel (clients/web/src/components/groups/AppDetailPanel/):
- Props { tool, formValues, isOpening, onFormChange, onOpenApp }
- Layout: optional icon + title row, optional description, divider,
  SchemaForm bound to tool.inputSchema, full-width "Open App" button.
- "Open App" is disabled while isOpening or while any required input
  field is empty (validation derived from tool.inputSchema.required).
- No annotations, progress, or cancel — execution is delegated to the
  embedded app, not run as an MCP tool call from the panel.

Convention notes:
- Issue text references @tabler IconChevronRight / IconPlayerPlay; the
  v2 web client already uses react-icons (md/ri namespaces), so this PR
  uses MdChevronRight and MdPlayArrow for consistency rather than
  introducing a second icon library.

Stories: AppListItem (Default, Selected, WithIcon, NoDescription, LongName);
AppDetailPanel (NoFields, SimpleStringParam, MultipleParams, WithIcon,
Opening, ComplexSchema).

Tests: 22 cases covering rendering branches, callback wiring, and the
disabled-button derivation. Per-file coverage 100% across lines,
statements, functions, and branches for both new components.

Closes #1261

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(stories): inline SVG for WithIcon stories so they render reliably

The Wikipedia-hosted PNG used in the AppListItem and AppDetailPanel
WithIcon stories returned a broken image in Storybook (rate-limited /
hot-link blocked). Replace it with an inline SVG sun icon as a data
URI so the stories render offline, in Chromatic, and on any network.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(apps): address PR #1273 review feedback

- Extract resolveDisplayLabel(name, title?) to src/utils/toolUtils.ts and
  migrate all four call sites (AppListItem, AppDetailPanel, ToolListItem,
  ToolDetailPanel) to consume it. Avoids the fourth copy when the next
  list/detail screen arrives. Includes a small unit suite.

- AppDetailPanel: only render the <Divider/> when the schema has at least
  one property — previously a no-input app showed an orphaned separator
  between the description and the Open App button.

- AppDetailPanel: align PanelTitle truncate to `true` (matches the
  ItemLabel form used in AppListItem / ToolListItem); behaviorally
  identical, just less surprising for future readers.

- AppDetailPanel: add an inline comment explaining that SchemaForm is
  disabled only while isOpening, not while validation fails — users
  should be able to keep filling required fields. The validation gate
  lives on the Open App button.

- Extract the inline SUN_ICON_SVG data URI used by the WithIcon stories
  to src/test/fixtures/storyIcons.ts so the next story that needs it
  doesn't grow a third copy.

- Tests: add the AppDetailPanel divider-conditional cases (rendered
  with fields, omitted without). Per-file coverage stays at 100% for
  AppListItem, AppDetailPanel, ToolListItem, ToolDetailPanel, and the
  new toolUtils. Suite: 70 files / 618 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(toolUtils): rename empty-string test for readability

Drops the doubled "is ... is preserved" phrase in the test name.
Behavior unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:28:58 -04:00
Cliff Hall
8fa1a0c074
feat(apps): add isAppTool helper in core (#1260) (#1272)
* feat(apps): add isAppTool helper in core (#1260)

Adds @modelcontextprotocol/ext-apps as a dependency (root + clients/web)
and a thin core/mcp/apps.ts wrapper exposing:

- getAppResourceUri(tool): re-export of getToolUiResourceUri so all
  Inspector clients consume the same nested/flat _meta resolution.
- isAppTool(tool): boolean predicate built on top of getAppResourceUri.

Both surface the underlying ext-apps throw on a malformed _meta.ui.resourceUri
(string that does not start with "ui://") rather than swallowing it — server
bugs should be visible.

Tests cover nested format, deprecated flat format, nested-wins precedence,
missing _meta, and the invalid-URI throw.

Refs #1260

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(apps): address PR #1272 review feedback

- core/mcp/apps.ts: convert getAppResourceUri from a typed const re-export to
  a function declaration so its export style matches isAppTool. Behavior is
  unchanged.
- core/mcp/apps.ts: document the filter-iteration implication on isAppTool —
  callers using tools.filter(isAppTool) will halt on a server-malformed URI.
- apps.test.ts: add the symmetric isAppTool false-case for `_meta.ui` present
  without `resourceUri`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:03:11 -04:00
Cliff Hall
75f74d64c1
test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245) (#1269)
* test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245)

Establishes the unit-test runtime AGENTS.md mandates: a vitest "unit"
project alongside the existing storybook project, RTL + jest-dom +
happy-dom, a Mantine-aware render helper, and a 90%-line per-file
coverage gate enforced by CI. Adds 66 test files covering all 62
components under elements/groups/screens/views, plus core/mcp and
clients/web/src/utils. Total: 577 tests, 98.7% line coverage globally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(coverage): address PR #1269 review feedback

- AGENTS.md: correct coverage gate description (lines ≥ 90, statements ≥ 85,
  functions ≥ 80, branches ≥ 50) with rationale for the relaxed branch
  threshold
- InspectorView.test.tsx: document what the Math.random() mock values
  (0.1 / 0.99) control vs. STUB_SUCCESS_RATE in InspectorView.tsx
- vite.config.ts / tsconfig.test.json: drop unused `globals: true` and the
  `vitest/globals` type now that every test file imports describe/it/expect/vi
  explicitly; manual `afterEach(cleanup)` in setup.ts remains required because
  testing-library auto-cleanup relies on globalThis.afterEach

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:15:32 -04:00