Merge branch 'v2/main' into v2/chore/2156-use-mcp-apps

This commit is contained in:
Cliff Hall 2026-08-26 23:58:41 -04:00 • committed by GitHub
commit d79e96b0f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 885 additions and 59 deletions

View file

@ -64,3 +64,14 @@ describe("PromptMessagesDisplay", () => {
).not.toBeInTheDocument();
});
});
// The `data-testid` the headless tab smoke waits on (#2148). It asserts the
// messages RENDERED, which `data-get-status="ok"` alone does not — that flips
// as soon as `prompts/get` resolves. Pinned here so a rename fails loudly in a
// unit test rather than as an opaque timeout in a five-minute gate.
describe("automation contract (#2148)", () => {
it("exposes a stable data-testid on the panel", () => {
renderWithMantine(<PromptMessagesDisplay messages={messages} />);
expect(screen.getByTestId("prompt-messages")).toBeInTheDocument();
});
});

View file

@ -67,7 +67,10 @@ export function PromptMessagesDisplay({
onClose,
}: PromptMessagesDisplayProps) {
return (
<PanelStack>
// `data-testid` so a driver can wait on the messages having actually
// RENDERED, not merely on `prompts/get` having returned (#2148) — see the
// ResourcePreviewPanel counterpart for why the RPC status is not enough.
<PanelStack data-testid="prompt-messages">
<HeaderRow>
<HeaderLeft>
{onClose && (

View file

@ -566,3 +566,22 @@ describe("ResourcePreviewPanel", () => {
});
});
});
// The `data-testid` the headless tab smoke waits on (#2148) — see the
// PromptMessagesDisplay counterpart: `data-read-status="ok"` flips as soon as
// `resources/read` resolves, so only this proves the preview rendered.
describe("automation contract (#2148)", () => {
it("exposes a stable data-testid on the panel", () => {
renderWithMantine(
<ResourcePreviewPanel
resource={textResource}
contents={textContents}
isSubscribed={false}
onRefresh={vi.fn()}
onSubscribe={vi.fn()}
onUnsubscribe={vi.fn()}
/>,
);
expect(screen.getByTestId("resource-preview")).toBeInTheDocument();
});
});

View file

@ -222,7 +222,12 @@ export function ResourcePreviewPanel({
const sourceToggleable = isSourceToggleable(mimeType);
return (
<PanelStack>
// `data-testid` so a driver can wait on the preview having actually
// RENDERED, not merely on `resources/read` having returned (#2148). The
// read status flips as soon as the RPC resolves, so a smoke stopping there
// would stay green with this panel removed. A `data-*` attribute is not
// part of a component's typed props, so it is passed at the call site.
<PanelStack data-testid="resource-preview">
<HeaderRow>
<HeaderLeft>
{onClose && (

View file

@ -85,3 +85,15 @@ describe("StructuredOutputPanel", () => {
expect(screen.getByTestId("code-highlight")).toHaveTextContent("{}");
});
});
// The `data-testid` the headless tab smoke waits on (#2148). Asserting the
// section by its heading copy would make the smoke fail on a rewording, so the
// smoke keys off this attribute — pinned here so a rename fails loudly.
describe("automation contract (#2148)", () => {
it("exposes a stable data-testid on the section", () => {
renderWithMantine(
<StructuredOutputPanel structuredContent={structuredContent} />,
);
expect(screen.getByTestId("structured-output")).toBeInTheDocument();
});
});

View file

@ -72,7 +72,10 @@ export function StructuredOutputPanel({
const [expanded, setExpanded] = useState(defaultExpanded);
return (
<StructuredBox>
// `data-testid` so the headless tab smoke (#2148) can assert this section
// rendered without matching on its heading copy. A `data-*` attribute is
// not part of a component's typed props, so it is passed at the call site.
<StructuredBox data-testid="structured-output">
<StructuredInner>
<HeaderRow>
<StructuredHeader>Structured Output</StructuredHeader>

View file

@ -391,3 +391,34 @@ describe("PromptsScreen", () => {
});
});
});
// See the ToolsScreen counterpart: the `data-*` readiness contract the headless
// tab smoke drives (#2148), pinned here so a rename fails loudly.
describe("automation contract (#2148)", () => {
it("reports the prompt count and an idle get status", () => {
renderWithMantine(<PromptsScreen {...baseProps} />);
const root = screen.getByTestId("prompts-screen");
expect(root).toHaveAttribute(
"data-prompt-count",
String(promptsWithArgs.length),
);
expect(root).toHaveAttribute("data-get-status", "idle");
});
it("tracks the get status through to a result", () => {
renderWithMantine(
<PromptsScreen
{...baseProps}
getPromptState={{
status: "ok",
promptName: promptsWithArgs[0].name,
result: { messages: [] },
}}
/>,
);
expect(screen.getByTestId("prompts-screen")).toHaveAttribute(
"data-get-status",
"ok",
);
});
});

View file

@ -274,7 +274,13 @@ export function PromptsScreen({
}
return (
<ScreenLayout>
// `data-*` readiness contract for the headless tab smoke (#2148); see
// clients/web/README.md#core-tab-automation-contract.
<ScreenLayout
data-testid="prompts-screen"
data-prompt-count={prompts.length}
data-get-status={getPromptState?.status ?? "idle"}
>
<Sidebar>
<SidebarCard>
<PromptControls

View file

@ -403,3 +403,36 @@ describe("ResourcesScreen", () => {
).not.toBeInTheDocument();
});
});
// See the ToolsScreen counterpart: the `data-*` readiness contract the headless
// tab smoke drives (#2148), pinned here so a rename fails loudly.
describe("automation contract (#2148)", () => {
it("reports the resource and template counts separately", () => {
renderWithMantine(<ResourcesScreen {...baseProps} />);
const root = screen.getByTestId("resources-screen");
// Two attributes rather than one: `resources/list` and
// `resources/templates/list` are separate calls and either can fail alone.
expect(root).toHaveAttribute(
"data-resource-count",
String(resources.length),
);
expect(root).toHaveAttribute(
"data-template-count",
String(templates.length),
);
expect(root).toHaveAttribute("data-read-status", "idle");
});
it("tracks the read status through to a result", () => {
renderWithMantine(
<ResourcesScreen
{...baseProps}
readState={{ status: "ok", uri: "file:///x", result: okResult }}
/>,
);
expect(screen.getByTestId("resources-screen")).toHaveAttribute(
"data-read-status",
"ok",
);
});
});

View file

@ -319,7 +319,14 @@ export function ResourcesScreen({
}
return (
<ScreenLayout>
// `data-*` readiness contract for the headless tab smoke (#2148); see
// clients/web/README.md#core-tab-automation-contract.
<ScreenLayout
data-testid="resources-screen"
data-resource-count={resources.length}
data-template-count={templates.length}
data-read-status={readState?.status ?? "idle"}
>
<Sidebar>
<SidebarCard>
<ResourceControls

View file

@ -445,3 +445,41 @@ describe("ToolsScreen", () => {
expect(screen.getByText("invalid_header_tool")).toBeInTheDocument();
});
});
// The `data-*` readiness contract the headless tab smoke drives (#2148). It is
// a documented public contract (clients/web/README.md), and a smoke asserting
// it fails as an opaque 45s timeout rather than a mismatch — so the attribute
// names are pinned here, where a rename fails loudly instead.
describe("automation contract (#2148)", () => {
it("reports the tool count and an idle call status", () => {
renderWithMantine(<ToolsScreen {...baseProps} />);
const root = screen.getByTestId("tools-screen");
expect(root).toHaveAttribute("data-tool-count", String(tools.length));
// Absent call state reads `idle`, not empty: the smoke waits on a value.
expect(root).toHaveAttribute("data-call-status", "idle");
});
it("reports an empty list distinctly from a missing one", () => {
renderWithMantine(<ToolsScreen {...baseProps} tools={[]} />);
expect(screen.getByTestId("tools-screen")).toHaveAttribute(
"data-tool-count",
"0",
);
});
it("tracks the call status through to a result", () => {
renderWithMantine(
<ToolsScreen
{...baseProps}
callState={{
status: "ok",
result: { content: [{ type: "text", text: "done" }] },
}}
/>,
);
expect(screen.getByTestId("tools-screen")).toHaveAttribute(
"data-call-status",
"ok",
);
});
});

View file

@ -198,7 +198,15 @@ export function ToolsScreen({
};
return (
<ScreenLayout>
// `data-*` readiness contract for the headless tab smoke (#2148); see
// clients/web/README.md#core-tab-automation-contract. Attributes are not
// part of a component's typed props, so they are passed here rather than
// baked into the `ScreenLayout` constant.
<ScreenLayout
data-testid="tools-screen"
data-tool-count={tools.length}
data-call-status={callState?.status ?? "idle"}
>
<Sidebar>
<SidebarCard>
<ToolControls