test: cover the structured-output cap and the list_items showcase end to end

Two gaps from the Copilot review on #1975:

- The `Large` story asserted nothing, so dropping the section's `mah` (or
  applying it to the wrong element) would still pass CI. Its play function
  now measures the real ScrollArea viewport: the visible height stays at
  the cap and the overflow is scrollable rather than clipped.

- Nothing exercised the `list_items` fixture, so a preset-registration
  typo would leave the documented showcase broken with a green suite. A
  new integration test loads `structured-output-http.json` through
  loadConfig/resolveConfig — covering the file, the registry entry, and
  the fixture — connects, calls the tool, and asserts both the summary
  block and the nested structuredContent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt
This commit is contained in:
cliffhall 2026-08-11 10:28:01 -04:00
commit 8049b4f7c8
2 changed files with 68 additions and 0 deletions

View file

@ -59,10 +59,29 @@ export const Flat: Story = {
},
};
// A payload taller than the section's cap scrolls inside it rather than
// pushing the rest of the result panel down. Asserted on the real geometry,
// so dropping `mah` (or moving it to the wrong element) fails here.
export const Large: Story = {
args: {
structuredContent: large,
},
play: async ({ canvasElement }) => {
await waitFor(() =>
expect(canvasElement.textContent).toContain('"Row 60"'),
);
const viewport = canvasElement.querySelector(
".mantine-ScrollArea-viewport",
);
if (!(viewport instanceof HTMLElement)) {
throw new Error("scroll viewport not found");
}
// Capped: the visible height stays at the section's `mah`, well under the
// payload's natural height…
expect(viewport.clientHeight).toBeLessThanOrEqual(400);
// …and the overflow is reachable by scrolling rather than clipped away.
expect(viewport.scrollHeight).toBeGreaterThan(viewport.clientHeight);
},
};
// Collapsing hides the payload; expanding brings it back.

View file

@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { resolve } from "node:path";
import * as z from "zod/v4";
import { InspectorClient } from "@inspector/core/mcp/inspectorClient.js";
import {
@ -45,6 +46,8 @@ import {
createAddResourceTool,
createAddToolTool,
createAddPromptTool,
loadConfig,
resolveConfig,
} from "@modelcontextprotocol/inspector-test-server";
import type {
MessageEntry,
@ -1214,6 +1217,52 @@ describe("InspectorClient", () => {
});
});
describe("Structured output showcase config (#1908)", () => {
// Drives the shipped `structured-output-http.json` end to end — the file
// itself, the `list_items` preset registration, and the fixture's nested
// payload. A typo in any of the three fails here rather than leaving the
// documented showcase quietly broken.
const showcaseConfigPath = resolve(
import.meta.dirname,
"../../../../../../test-servers/configs/structured-output-http.json",
);
it("serves list_items with a summary block and a nested structuredContent", async () => {
server = createTestServerHttp(
resolveConfig(loadConfig(showcaseConfigPath)),
);
await server.start();
client = new InspectorClient(
{ type: "streamable-http", url: server.url },
{ environment: { transport: createTransportNode } },
);
await client.connect();
const tool = await getTool(client, "list_items");
expect(tool.outputSchema).toBeDefined();
const result = await client.callTool(tool, {});
expect(result.success).toBe(true);
// The text block only summarizes — the payload is the structured half.
const content = result.result!.content as Array<{
type: string;
text?: string;
}>;
expect(content[0].type).toBe("text");
expect(content[0].text).toBe("Found 2 items.");
expect(result.result!.structuredContent).toEqual({
items: [
{ id: 1, name: "Item A", tags: ["foo", "bar"] },
{ id: 2, name: "Item B", tags: ["baz"] },
],
total: 2,
});
});
});
describe("Default metadata (server-wide _meta)", () => {
function metaOf(req: { message: unknown }): Record<string, unknown> {
const params = (req.message as { params?: { _meta?: unknown } }).params;