import { act, fireEvent, render, screen, within } from "@testing-library/react"; import { beforeEach } from "vitest"; import { useSessionStore } from "../store/sessionStore"; import { CentralStatus } from "./CentralStatus"; beforeEach(() => useSessionStore.getState().resetSession()); 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(); 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( within(log).getAllByRole("listitem").map((row) => row.lastElementChild?.textContent), ).toEqual(["First line", "Second line", "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("wraps live log lines instead of truncating them", () => { useSessionStore.setState({ transcript: [{ role: "assistant", text: "a_very_long_model_activity_line" }], }); render(); 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("uses accessible muted text for historical live-log rows", () => { useSessionStore.setState({ transcript: [ { role: "assistant", text: "Earlier activity" }, { role: "assistant", text: "Current activity" }, ], }); render(); const historicalRow = screen.getByText("Earlier activity").closest("li"); expect(historicalRow).not.toBeNull(); expect(historicalRow).toHaveClass("text-muted-foreground"); expect(historicalRow).not.toHaveClass("text-muted-foreground/55"); }); 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(); expect(container).toBeEmptyDOMElement(); }); test("bottom-follows appended transcript text only while near the bottom", () => { useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] }); render(); 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("preserves manual live-log scroll position away from the bottom", () => { useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] }); render(); 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); }); test("resets bottom-follow when the live log resumes after becoming inactive", () => { useSessionStore.setState({ transcript: [{ role: "assistant", text: "First" }] }); const { rerender } = render(); const log = screen.getByRole("log", { name: "Live model activity" }); setScrollGeometry(log, { scrollHeight: 400, clientHeight: 100, scrollTop: 100 }); fireEvent.scroll(log); rerender(); expect(screen.queryByRole("log", { name: "Live model activity" })).not.toBeInTheDocument(); const scrollProperties = ["scrollHeight", "clientHeight", "scrollTop"] as const; const originalDescriptors = scrollProperties.map((property) => Object.getOwnPropertyDescriptor(HTMLElement.prototype, property), ); Object.defineProperties(HTMLElement.prototype, { scrollHeight: { configurable: true, value: 400 }, clientHeight: { configurable: true, value: 100 }, scrollTop: { configurable: true, writable: true, value: 0 }, }); try { rerender(); const resumedLog = screen.getByRole("log", { name: "Live model activity" }); expect(resumedLog.scrollTop).toBe(400); Object.defineProperty(resumedLog, "scrollHeight", { configurable: true, value: 500 }); act(() => useSessionStore.getState().applyEvent({ type: "text_delta", text: " second" })); expect(resumedLog.scrollTop).toBe(500); } finally { scrollProperties.forEach((property, index) => { const descriptor = originalDescriptors[index]; if (descriptor) Object.defineProperty(HTMLElement.prototype, property, descriptor); else Reflect.deleteProperty(HTMLElement.prototype, property); }); } });