style: normalize formatting (tabs) in ChatStream tests + index.css from review pass
This commit is contained in:
+74
-39
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user