From 8049b4f7c8d3fb6cf2d6e9adf99aa6334c100f8a Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 11 Aug 2026 10:28:01 -0400 Subject: [PATCH] test: cover the structured-output cap and the list_items showcase end to end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt --- .../StructuredOutputPanel.stories.tsx | 19 +++++++ .../integration/mcp/inspectorClient.test.ts | 49 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/clients/web/src/components/groups/StructuredOutputPanel/StructuredOutputPanel.stories.tsx b/clients/web/src/components/groups/StructuredOutputPanel/StructuredOutputPanel.stories.tsx index 443c78ab..c66760f0 100644 --- a/clients/web/src/components/groups/StructuredOutputPanel/StructuredOutputPanel.stories.tsx +++ b/clients/web/src/components/groups/StructuredOutputPanel/StructuredOutputPanel.stories.tsx @@ -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. diff --git a/clients/web/src/test/integration/mcp/inspectorClient.test.ts b/clients/web/src/test/integration/mcp/inspectorClient.test.ts index 7937b219..1a1645d8 100644 --- a/clients/web/src/test/integration/mcp/inspectorClient.test.ts +++ b/clients/web/src/test/integration/mcp/inspectorClient.test.ts @@ -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 { const params = (req.message as { params?: { _meta?: unknown } }).params;