web: professional UI overhaul — ink/slate design system, archive+active sections, skeletons, error boundary, conn banner, usage chip, message copy, spawn progress steps; 162 tests ≥95% coverage

This commit is contained in:
Raphael Westphal
2026-08-18 18:19:25 +02:00
parent 42fcaa1fe3
commit e626b9cc6e
12 changed files with 1583 additions and 552 deletions
+35 -1
View File
@@ -1,4 +1,4 @@
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import type { ChatMessage, ToolState } from "./derive";
@@ -300,3 +300,37 @@ describe("ChatStream", () => {
expect(scroller.scrollTop).toBe(1000);
});
});
describe("copy button", () => {
it("copy button writes message text and flashes copied", async () => {
const writeText = vi.fn(() => Promise.resolve());
Object.assign(navigator, { clipboard: { writeText } });
render(
<Bubble
msg={{ key: "k", role: "assistant", text: "copy me", thinking: null, toolCalls: [], toolCallId: null, streaming: false }}
tools={new Map()}
/>,
);
const btn = screen.getByRole("button", { name: "Copy message" });
fireEvent.click(btn);
expect(writeText).toHaveBeenCalledWith("copy me");
await waitFor(() => expect(screen.getByText("copied")).toBeInTheDocument());
});
it("user bubbles get a copy button, toolResults do not", () => {
const { rerender } = render(
<Bubble
msg={{ key: "u", role: "user", text: "hi", thinking: null, toolCalls: [], toolCallId: null, streaming: false }}
tools={new Map()}
/>,
);
expect(screen.getByRole("button", { name: "Copy message" })).toBeInTheDocument();
rerender(
<Bubble
msg={{ key: "t", role: "toolResult", text: "r", thinking: null, toolCalls: [], toolCallId: "c1", streaming: false }}
tools={new Map()}
/>,
);
expect(screen.queryByRole("button", { name: "Copy message" })).toBeNull();
});
});