fix(frontend): deduplicate live workflow activity
This commit is contained in:
@@ -1,43 +1,85 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { beforeEach } from "vitest";
|
||||
import { useSessionStore } from "../store/sessionStore";
|
||||
import { CentralStatus } from "./CentralStatus";
|
||||
|
||||
beforeEach(() => useSessionStore.getState().resetSession());
|
||||
|
||||
test("the working spinner is a pure status indicator (no activity toggle on it)", () => {
|
||||
useSessionStore.getState().applyEvent({ type: "text_delta", text: "reading the schema" });
|
||||
function setScrollGeometry(
|
||||
element: HTMLElement,
|
||||
values: { scrollHeight: number; clientHeight: number; scrollTop: number },
|
||||
) {
|
||||
Object.defineProperties(element, {
|
||||
scrollHeight: { configurable: true, value: values.scrollHeight },
|
||||
clientHeight: { configurable: true, value: values.clientHeight },
|
||||
scrollTop: { configurable: true, writable: true, value: values.scrollTop },
|
||||
});
|
||||
}
|
||||
|
||||
test("renders only the chronological assistant transcript log while working", () => {
|
||||
useSessionStore.setState({
|
||||
lastUserEntry: { kind: "input", text: "how many patients?" },
|
||||
stepMessages: [{ level: "warning", text: "Searching the schema…" }],
|
||||
transcript: [
|
||||
{ role: "assistant", text: "First line\n\nSecond line" },
|
||||
{ role: "assistant", text: "Third line" },
|
||||
],
|
||||
});
|
||||
|
||||
render(<CentralStatus working />);
|
||||
// The spinner no longer opens the panel — that moved to the header arrow toggle.
|
||||
expect(screen.queryByRole("button", { name: /model activity/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("status")).toBeInTheDocument();
|
||||
|
||||
const log = screen.getByRole("log", { name: "Live model activity" });
|
||||
expect(log).toHaveTextContent("First line");
|
||||
expect(log).toHaveTextContent("Second line");
|
||||
expect(log).toHaveTextContent("Third line");
|
||||
expect(log).toHaveClass("max-h-28", "overflow-y-auto");
|
||||
expect(screen.queryByText("how many patients?")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Searching the schema…")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders nothing when not working and there is no user entry or step messages", () => {
|
||||
const { container } = render(<CentralStatus working={false} />);
|
||||
test("wraps live log lines instead of truncating them", () => {
|
||||
useSessionStore.setState({
|
||||
transcript: [{ role: "assistant", text: "a_very_long_model_activity_line" }],
|
||||
});
|
||||
render(<CentralStatus working />);
|
||||
expect(screen.getByText("a_very_long_model_activity_line")).toHaveClass(
|
||||
"whitespace-pre-wrap",
|
||||
"break-words",
|
||||
);
|
||||
expect(screen.getByText("a_very_long_model_activity_line")).not.toHaveClass("truncate");
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ working: false, transcript: [{ role: "assistant" as const, text: "hidden" }] },
|
||||
{ working: true, transcript: [] },
|
||||
{ working: true, transcript: [{ role: "assistant" as const, text: " \n " }] },
|
||||
])("renders nothing without active non-blank transcript content", ({ working, transcript }) => {
|
||||
useSessionStore.setState({ transcript });
|
||||
const { container } = render(<CentralStatus working={working} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
test("echoes the last user entry and the step messages", () => {
|
||||
const st = useSessionStore.getState();
|
||||
st.setLastUserEntry({ kind: "input", text: "how many patients?" });
|
||||
st.applyEvent({ type: "info", level: "info", text: "Searching the schema…" });
|
||||
render(<CentralStatus working={false} />);
|
||||
expect(screen.getByText("how many patients?")).toBeInTheDocument();
|
||||
expect(screen.getByText("Searching the schema…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("shows a working indicator with elapsed time and the model-stream tail while working", () => {
|
||||
useSessionStore.getState().applyEvent({ type: "text_delta", text: "Reading the schema for dim_patient" });
|
||||
test("bottom-follows appended transcript text only while near the bottom", () => {
|
||||
useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] });
|
||||
render(<CentralStatus working />);
|
||||
// elapsed time (e.g. "0s")
|
||||
expect(screen.getByText(/\d+s/)).toBeInTheDocument();
|
||||
// a short tail of the model's current output
|
||||
expect(screen.getByText(/Reading the schema for dim_patient/)).toBeInTheDocument();
|
||||
const log = screen.getByRole("log", { name: "Live model activity" });
|
||||
setScrollGeometry(log, { scrollHeight: 400, clientHeight: 100, scrollTop: 300 });
|
||||
|
||||
act(() => useSessionStore.getState().applyEvent({ type: "text_delta", text: " second" }));
|
||||
|
||||
expect(log.scrollTop).toBe(400);
|
||||
});
|
||||
|
||||
test("no working indicator when not working, even with a transcript", () => {
|
||||
useSessionStore.getState().applyEvent({ type: "text_delta", text: "background narration" });
|
||||
render(<CentralStatus working={false} />);
|
||||
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument();
|
||||
test("preserves manual live-log scroll position away from the bottom", () => {
|
||||
useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] });
|
||||
render(<CentralStatus working />);
|
||||
const log = screen.getByRole("log", { name: "Live model activity" });
|
||||
setScrollGeometry(log, { scrollHeight: 400, clientHeight: 100, scrollTop: 100 });
|
||||
fireEvent.scroll(log);
|
||||
|
||||
act(() => useSessionStore.getState().applyEvent({ type: "text_delta", text: " second" }));
|
||||
|
||||
expect(log.scrollTop).toBe(100);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user