style: normalize formatting (tabs) in ChatStream tests + index.css from review pass

This commit is contained in:
2026-09-01 13:52:03 +00:00
parent 3b799c69fb
commit 5962b50c8d
2 changed files with 100 additions and 53 deletions
+74 -39
View File
@@ -63,10 +63,7 @@ function stream(p: {
describe("Bubble", () => { describe("Bubble", () => {
it("renders plain text per role class", () => { it("renders plain text per role class", () => {
const { container } = render( const { container } = render(
<Bubble <Bubble msg={msg({ role: "user", text: "hi there" })} tools={new Map()} />,
msg={msg({ role: "user", text: "hi there" })}
tools={new Map()}
/>,
); );
expect(container.querySelector(".bubble-row.user")).not.toBeNull(); expect(container.querySelector(".bubble-row.user")).not.toBeNull();
expect(container.textContent).toContain("hi there"); expect(container.textContent).toContain("hi there");
@@ -94,10 +91,7 @@ describe("Bubble", () => {
it("toolResult collapses to a one-line preview, expands to full text", async () => { it("toolResult collapses to a one-line preview, expands to full text", async () => {
const long = `${"x".repeat(200)}`; const long = `${"x".repeat(200)}`;
render( render(
<Bubble <Bubble msg={msg({ role: "toolResult", text: long })} tools={new Map()} />,
msg={msg({ role: "toolResult", text: long })}
tools={new Map()}
/>,
); );
const details = screen const details = screen
.getByText("result") .getByText("result")
@@ -160,9 +154,7 @@ describe("Bubble", () => {
expect(screen.getByText("🛠 bash")).toBeInTheDocument(); expect(screen.getByText("🛠 bash")).toBeInTheDocument();
expect(screen.getByText("finished")).toBeInTheDocument(); expect(screen.getByText("finished")).toBeInTheDocument();
const summary = screen const summary = screen.getByText("🛠 bash").closest("summary") as HTMLElement;
.getByText("🛠 bash")
.closest("summary") as HTMLElement;
const card = summary.closest("details") as HTMLDetailsElement; const card = summary.closest("details") as HTMLDetailsElement;
expect(card.open).toBe(false); expect(card.open).toBe(false);
await userEvent.click(summary); await userEvent.click(summary);
@@ -256,9 +248,7 @@ describe("ChatStream", () => {
}), }),
); );
expect(container.querySelector(".typing")).toBeNull(); expect(container.querySelector(".typing")).toBeNull();
rerender( rerender(stream({ messages: [msg({ key: "a", text: "one" })], busy: false }));
stream({ messages: [msg({ key: "a", text: "one" })], busy: false }),
);
expect(container.querySelector(".typing")).toBeNull(); expect(container.querySelector(".typing")).toBeNull();
}); });
@@ -767,15 +757,34 @@ describe("copy button", () => {
describe("pretty tool args", () => { describe("pretty tool args", () => {
const tool = (args: string): ToolState => ({ const tool = (args: string): ToolState => ({
id: "t1", name: "bash", args, running: false, isError: false, preview: "out", id: "t1",
name: "bash",
args,
running: false,
isError: false,
preview: "out",
}); });
it("bash command renders as the bare command, no JSON braces", () => { it("bash command renders as the bare command, no JSON braces", () => {
render(<Bubble msg={{ key: "k", role: "assistant", text: "", thinking: null, render(
toolCalls: [{ id: "t1", name: "bash", argsJson: "{}" }], toolCallId: null, streaming: false, ts: 0 }} <Bubble
tools={new Map([["t1", tool('{"command":"ls -la","foo":1}')]])} />); msg={{
key: "k",
role: "assistant",
text: "",
thinking: null,
toolCalls: [{ id: "t1", name: "bash", argsJson: "{}" }],
toolCallId: null,
streaming: false,
ts: 0,
}}
tools={new Map([["t1", tool('{"command":"ls -la","foo":1}')]])}
/>,
);
expect(document.querySelector(".tool-summary")?.textContent).toBe("ls -la"); expect(document.querySelector(".tool-summary")?.textContent).toBe("ls -la");
expect(document.querySelector(".tool-args")?.textContent).not.toContain('"command"'); expect(document.querySelector(".tool-args")?.textContent).not.toContain(
'"command"',
);
}); });
it("priority ordering puts command first even when not first in JSON", () => { it("priority ordering puts command first even when not first in JSON", () => {
@@ -785,10 +794,24 @@ describe("pretty tool args", () => {
}); });
it("single string arg renders label-less; multi-field keeps labels", () => { it("single string arg renders label-less; multi-field keeps labels", () => {
render(<Bubble msg={{ key: "k", role: "assistant", text: "", thinking: null, render(
toolCalls: [{ id: "t1", name: "read", argsJson: "{}" }], toolCallId: null, streaming: false, ts: 0 }} <Bubble
tools={new Map([["t1", tool('{"path":"src/main.ts"}')]])} />); msg={{
expect(document.querySelector(".tool-summary")?.textContent).toBe("src/main.ts"); key: "k",
role: "assistant",
text: "",
thinking: null,
toolCalls: [{ id: "t1", name: "read", argsJson: "{}" }],
toolCallId: null,
streaming: false,
ts: 0,
}}
tools={new Map([["t1", tool('{"path":"src/main.ts"}')]])}
/>,
);
expect(document.querySelector(".tool-summary")?.textContent).toBe(
"src/main.ts",
);
expect(document.querySelector(".tool-args .arg-k")).toBeNull(); expect(document.querySelector(".tool-args .arg-k")).toBeNull();
}); });
@@ -839,7 +862,9 @@ describe("tool summary + diff", () => {
tools={new Map([["t1", tool('{"path":"src/main.ts"}', "read")]])} tools={new Map([["t1", tool('{"path":"src/main.ts"}', "read")]])}
/>, />,
); );
expect(document.querySelector(".tool-summary")?.textContent).toBe("src/main.ts"); expect(document.querySelector(".tool-summary")?.textContent).toBe(
"src/main.ts",
);
}); });
it("long summary is truncated with ellipsis", () => { it("long summary is truncated with ellipsis", () => {
@@ -863,20 +888,28 @@ describe("tool summary + diff", () => {
msg={msg({ msg={msg({
toolCalls: [{ id: "t1", name: "edit", argsJson: "{}" }], toolCalls: [{ id: "t1", name: "edit", argsJson: "{}" }],
})} })}
tools={new Map([ tools={
[ new Map([
"t1", [
tool( "t1",
'{"path":"a.ts","edits":[{"oldText":"const a = 1;\\nconst b = 2;","newText":"const a = 3;\\nconst b = 2;"}]}', tool(
"edit", '{"path":"a.ts","edits":[{"oldText":"const a = 1;\\nconst b = 2;","newText":"const a = 3;\\nconst b = 2;"}]}',
), "edit",
], ),
])} ],
])
}
/>, />,
); );
expect(document.querySelector(".diff-del")?.textContent).toContain("-const a = 1;"); expect(document.querySelector(".diff-del")?.textContent).toContain(
expect(document.querySelector(".diff-add")?.textContent).toContain("+const a = 3;"); "-const a = 1;",
expect(document.querySelector(".diff-ctx")?.textContent).toContain(" const b = 2;"); );
expect(document.querySelector(".diff-add")?.textContent).toContain(
"+const a = 3;",
);
expect(document.querySelector(".diff-ctx")?.textContent).toContain(
" const b = 2;",
);
expect(document.querySelector(".tool-args")).toBeNull(); expect(document.querySelector(".tool-args")).toBeNull();
}); });
@@ -886,9 +919,11 @@ describe("tool summary + diff", () => {
msg={msg({ msg={msg({
toolCalls: [{ id: "t1", name: "write", argsJson: "{}" }], toolCalls: [{ id: "t1", name: "write", argsJson: "{}" }],
})} })}
tools={new Map([ tools={
["t1", tool('{"path":"new.ts","content":"hello\\nworld"}', "write")], new Map([
])} ["t1", tool('{"path":"new.ts","content":"hello\\nworld"}', "write")],
])
}
/>, />,
); );
const adds = document.querySelectorAll(".diff-add"); const adds = document.querySelectorAll(".diff-add");
+26 -14
View File
@@ -25,8 +25,7 @@
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35); --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35);
--shadow-2: 0 8px 28px rgba(0, 0, 0, 0.45); --shadow-2: 0 8px 28px rgba(0, 0, 0, 0.45);
font-family: font-family:
ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, sans-serif;
sans-serif;
} }
* { * {
@@ -1445,24 +1444,37 @@ mark.hit {
/* ---------- busy activity pip ---------- */ /* ---------- busy activity pip ---------- */
.busy-pip { .busy-pip {
display: inline-block; display: inline-block;
width: 8px; width: 8px;
height: 8px; height: 8px;
margin-left: 7px; margin-left: 7px;
border-radius: 50%; border-radius: 50%;
background: var(--accent); background: var(--accent);
animation: busy-pulse 1.1s ease-in-out infinite; animation: busy-pulse 1.1s ease-in-out infinite;
vertical-align: middle; vertical-align: middle;
} }
@keyframes busy-pulse { @keyframes busy-pulse {
0%, 100% { transform: scale(0.55); opacity: 0.45; box-shadow: 0 0 0 0 rgba(108, 140, 255, 0.5); } 0%,
50% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 5px rgba(108, 140, 255, 0); } 100% {
transform: scale(0.55);
opacity: 0.45;
box-shadow: 0 0 0 0 rgba(108, 140, 255, 0.5);
}
50% {
transform: scale(1);
opacity: 1;
box-shadow: 0 0 0 5px rgba(108, 140, 255, 0);
}
} }
/* ---------- pretty tool args ---------- */ /* ---------- pretty tool args ---------- */
.tool-args { margin-bottom: 10px; } .tool-args {
.tool-arg { margin-bottom: 6px; } margin-bottom: 10px;
}
.tool-arg {
margin-bottom: 6px;
}
.arg-k { .arg-k {
display: block; display: block;
font-size: 10.5px; font-size: 10.5px;