import "../../index.css"; import { EnvironmentId } from "@t3tools/contracts";import { createRef } from "react";import type { LegendListRef } from "@legendapp/list/react";import { page } from "vite-plus/test/browser";import { afterEach, describe, expect, it, vi } from "vite-plus/test";import { render } from "vitest-browser-react"; const scrollToEndSpy = vi.fn();const getStateSpy = vi.fn(() => ({ isAtEnd: true })); vi.mock("@legendapp/list/react", async () => { const React = await import("react"); function LegendList(props: { data: Array<{ id: string }>; keyExtractor: (item: { id: string }) => string; renderItem: (args: { item: { id: string } }) => React.ReactNode; ListHeaderComponent?: React.ReactNode; ListFooterComponent?: React.ReactNode; ref?: React.Ref<LegendListRef>; }) { React.useImperativeHandle( props.ref, () => ({ scrollToEnd: scrollToEndSpy, getState: getStateSpy, }) as unknown as LegendListRef, ); return ( <div data-testid="legend-list"> {props.ListHeaderComponent} {props.data.map((item) => ( <div key={props.keyExtractor(item)}>{props.renderItem({ item })}</div> ))} {props.ListFooterComponent} </div> ); } return { LegendList };}); import { MessagesTimeline } from "./MessagesTimeline"; const MESSAGE_CREATED_AT = "2026-04-13T12:00:00.000Z"; function buildProps() { return { isWorking: false, activeTurnInProgress: false, activeTurnStartedAt: null, listRef: createRef<LegendListRef | null>(), latestTurn: null, turnDiffSummaryByAssistantMessageId: new Map(), routeThreadKey: "environment-local:thread-1", onOpenTurnDiff: vi.fn(), revertTurnCountByUserMessageId: new Map(), onRevertUserMessage: vi.fn(), isRevertingCheckpoint: false, onImageExpand: vi.fn(), activeThreadEnvironmentId: EnvironmentId.make("environment-local"), markdownCwd: undefined, resolvedTheme: "dark" as const, timestampFormat: "24-hour" as const, workspaceRoot: undefined, onIsAtEndChange: vi.fn(), };} function buildLongUserMessageText(tail = "deep hidden detail only after expand") { return Array.from({ length: 9 }, (_, index) => index === 8 ? tail : `Line ${index + 1}: ${"verbose prompt content ".repeat(8).trim()}`, ).join("\n");} function buildUserTimelineEntry(text: string) { return { id: "entry-1", kind: "message" as const, createdAt: MESSAGE_CREATED_AT, message: { id: "message-1" as never, role: "user" as const, text, createdAt: MESSAGE_CREATED_AT, streaming: false, }, };} function buildAssistantTimelineEntry(text: string) { return { id: "entry-assistant-1", kind: "message" as const, createdAt: MESSAGE_CREATED_AT, message: { id: "message-assistant-1" as never, role: "assistant" as const, text, createdAt: MESSAGE_CREATED_AT, streaming: false, }, };} describe("MessagesTimeline", () => { afterEach(() => { scrollToEndSpy.mockReset(); getStateSpy.mockClear(); vi.restoreAllMocks(); document.body.innerHTML = ""; }); it("renders activity rows instead of the empty placeholder when a thread has non-message timeline data", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[ { id: "work-1", kind: "work", createdAt: "2026-04-13T12:00:00.000Z", entry: { id: "work-1", createdAt: "2026-04-13T12:00:00.000Z", label: "read files", detail: "Inspecting repository state", tone: "tool", }, }, ]} />, ); try { await expect .element(page.getByText("Send a message to start the conversation.")) .not.toBeInTheDocument(); await expect.element(page.getByText("Inspecting repository state")).toBeVisible(); expect(document.querySelector('[data-testid="legend-list"] [title]')).toBeNull(); } finally { await screen.unmount(); } }); it("uses accessible expansion instead of native titles or preview tooltips for work entry details", async () => { const screen = await render( <MessagesTimeline {...buildProps()} workspaceRoot="/repo" timelineEntries={[ { id: "work-command", kind: "work", createdAt: MESSAGE_CREATED_AT, entry: { id: "work-command", createdAt: MESSAGE_CREATED_AT, label: "command", detail: "Inspecting generated output", command: "git diff -- apps/web/src/components/ChatMarkdown.tsx", rawCommand: "git diff -- apps/web/src/components/ChatMarkdown.tsx --stat", changedFiles: ["/repo/apps/web/src/components/ChatMarkdown.tsx"], tone: "tool", }, }, ]} />, ); try { expect(document.querySelector('[data-testid="legend-list"] [title]')).toBeNull(); const commandTrigger = page.getByLabelText( "Command - git diff -- apps/web/src/components/ChatMarkdown.tsx", ); await commandTrigger.hover(); expect(document.querySelector('[data-slot="tooltip-popup"]')).toBeNull(); await commandTrigger.click(); await expect .element(page.getByText("git diff -- apps/web/src/components/ChatMarkdown.tsx --stat")) .toBeVisible(); } finally { await screen.unmount(); } }); it("snaps to the bottom when timeline rows appear after an initially empty render", async () => { const requestAnimationFrameSpy = vi .spyOn(window, "requestAnimationFrame") .mockImplementation((callback: FrameRequestCallback) => { callback(0); return 1; }); vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => undefined); const props = buildProps(); const screen = await render(<MessagesTimeline {...props} timelineEntries={[]} />); try { await expect .element(page.getByText("Send a message to start the conversation.")) .toBeVisible(); await screen.rerender( <MessagesTimeline {...props} timelineEntries={[ { id: "work-1", kind: "work", createdAt: "2026-04-13T12:00:00.000Z", entry: { id: "work-1", createdAt: "2026-04-13T12:00:00.000Z", label: "read files", detail: "Inspecting repository state", tone: "tool", }, }, ]} />, ); await expect.element(page.getByText("Inspecting repository state")).toBeVisible(); expect(props.onIsAtEndChange).toHaveBeenCalledWith(true); expect(scrollToEndSpy).toHaveBeenCalledWith({ animated: false }); expect(requestAnimationFrameSpy).toHaveBeenCalled(); } finally { await screen.unmount(); } }); it("starts long user messages collapsed by default", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText())]} />, ); try { const toggle = page.getByRole("button", { name: "Show full message" }); await expect.element(toggle).toBeVisible(); await expect.element(toggle).toHaveAttribute("aria-expanded", "false"); const messageBody = document.querySelector( "[data-user-message-body='true']", ) as HTMLDivElement | null; expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true"); expect(messageBody?.className).toContain("max-h-44"); expect(messageBody?.className).toContain("overflow-hidden"); expect(messageBody?.getAttribute("data-user-message-fade")).toBe("true"); expect(messageBody?.style.maskImage).toContain("linear-gradient"); } finally { await screen.unmount(); } }); it("expands and re-collapses long user messages from the toggle", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText())]} />, ); try { const expandButton = page.getByRole("button", { name: "Show full message" }); await expect.element(expandButton).toBeVisible(); expect(document.body.textContent ?? "").toContain("deep hidden detail only after expand"); await expandButton.click(); const collapseButton = page.getByRole("button", { name: "Show less" }); await expect.element(collapseButton).toBeVisible(); await expect.element(collapseButton).toHaveAttribute("aria-expanded", "true"); let messageBody = document.querySelector("[data-user-message-body='true']"); expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("false"); expect(messageBody?.className).not.toContain("max-h-44"); expect(messageBody?.getAttribute("data-user-message-fade")).toBe("false"); expect((messageBody as HTMLDivElement | null)?.style.maskImage ?? "").toBe(""); await collapseButton.click(); await expect.element(page.getByRole("button", { name: "Show full message" })).toBeVisible(); messageBody = document.querySelector("[data-user-message-body='true']"); expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true"); expect(messageBody?.className).toContain("max-h-44"); expect(messageBody?.getAttribute("data-user-message-fade")).toBe("true"); expect((messageBody as HTMLDivElement | null)?.style.maskImage).toContain("linear-gradient"); } finally { await screen.unmount(); } }); it("starts the newest long user prompt collapsed", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText("latest long prompt"))]} />, ); try { await expect.element(page.getByRole("button", { name: "Show full message" })).toBeVisible(); const messageBody = document.querySelector("[data-user-message-body='true']"); expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true"); } finally { await screen.unmount(); } }); it("renders user messages as markdown with chat-style line breaks", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[ buildUserTimelineEntry( "## Plan\nuse **bold** and [a link](https://example.com)\nsecond line", ), ]} />, ); try { await expect.element(page.getByRole("heading", { level: 2, name: "Plan" })).toBeVisible(); await expect .element(page.getByRole("link", { name: "a link" })) .toHaveAttribute("href", "https://example.com"); const messageBody = document.querySelector("[data-user-message-body='true']"); expect(messageBody?.querySelector("strong")?.textContent).toBe("bold"); // remark-breaks: the single newline between the inline runs is a <br>. expect(messageBody?.querySelectorAll("p br").length).toBe(1); } finally { await screen.unmount(); } }); it("renders markdown file tags in user and assistant messages", async () => { const fileLink = "[package.json](path/to/package.json)"; const screen = await render( <MessagesTimeline {...buildProps()} markdownCwd="/repo/project" timelineEntries={[ buildUserTimelineEntry(`Review ${fileLink}`), buildAssistantTimelineEntry(`I reviewed ${fileLink}`), ]} />, ); try { const userFileLink = document.querySelector( '[data-message-role="user"] .chat-markdown-file-link', ); const assistantFileLink = document.querySelector( '[data-message-role="assistant"] .chat-markdown-file-link', ); expect(userFileLink?.textContent).toContain("package.json"); expect(userFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json"); expect(assistantFileLink?.textContent).toContain("package.json"); expect(assistantFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json"); } finally { await screen.unmount(); } }); it("uses the file path without line suffix for markdown file tag icons", async () => { const fileLink = "[package.json](path/to/package.json:25)"; const screen = await render( <MessagesTimeline {...buildProps()} markdownCwd="/repo/project" timelineEntries={[buildAssistantTimelineEntry(`Updated ${fileLink}`)]} />, ); try { const assistantFileLink = document.querySelector( '[data-message-role="assistant"] .chat-markdown-file-link', ); const icon = assistantFileLink?.querySelector("svg[data-pierre-icon]"); expect(assistantFileLink?.textContent).toContain("package.json"); expect(assistantFileLink?.textContent).toContain("L25"); expect(assistantFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json:25"); expect(icon?.getAttribute("data-pierre-icon")).toBe("t3-file-icon-package-json"); } finally { await screen.unmount(); } }); it("folds settled-turn work behind a Worked-for row and expands it on click", async () => { const screen = await render( <MessagesTimeline {...buildProps()} timelineEntries={[ { id: "entry-commentary", kind: "message" as const, createdAt: "2026-04-13T12:00:00.000Z", message: { id: "message-commentary" as never, role: "assistant" as const, text: "Let me look around first.", turnId: "turn-1" as never, createdAt: "2026-04-13T12:00:00.000Z", completedAt: "2026-04-13T12:00:02.000Z", streaming: false, }, }, { id: "entry-work", kind: "work" as const, createdAt: "2026-04-13T12:00:05.000Z", entry: { id: "work-1", createdAt: "2026-04-13T12:00:05.000Z", turnId: "turn-1" as never, label: "read files", detail: "Inspecting repository state", tone: "tool" as const, }, }, { id: "entry-final", kind: "message" as const, createdAt: "2026-04-13T12:00:20.000Z", message: { id: "message-final" as never, role: "assistant" as const, text: "All done.", turnId: "turn-1" as never, createdAt: "2026-04-13T12:00:20.000Z", completedAt: "2026-04-13T12:00:30.000Z", streaming: false, }, }, ]} />, ); try { const foldButton = page.getByRole("button", { name: "Worked for 30s" }); await expect.element(foldButton).toBeVisible(); await expect.element(foldButton).toHaveAttribute("aria-expanded", "false"); expect(document.body.textContent).toContain("All done."); expect(document.body.textContent).not.toContain("Let me look around first."); expect(document.body.textContent).not.toContain("Inspecting repository state"); await foldButton.click(); await expect.element(foldButton).toHaveAttribute("aria-expanded", "true"); expect(document.body.textContent).toContain("Let me look around first."); expect(document.body.textContent).toContain("Inspecting repository state"); await foldButton.click(); await expect.element(foldButton).toHaveAttribute("aria-expanded", "false"); expect(document.body.textContent).not.toContain("Inspecting repository state"); } finally { await screen.unmount(); } });});
import "../../index.css";
import { EnvironmentId } from "@t3tools/contracts";
import { createRef } from "react";
import type { LegendListRef } from "@legendapp/list/react";
import { page } from "vite-plus/test/browser";
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import { render } from "vitest-browser-react";
const scrollToEndSpy = vi.fn();
const getStateSpy = vi.fn(() => ({ isAtEnd: true }));
vi.mock("@legendapp/list/react", async () => {
const React = await import("react");
function LegendList(props: {
data: Array<{ id: string }>;
keyExtractor: (item: { id: string }) => string;
renderItem: (args: { item: { id: string } }) => React.ReactNode;
ListHeaderComponent?: React.ReactNode;
ListFooterComponent?: React.ReactNode;
ref?: React.Ref<LegendListRef>;
}) {
React.useImperativeHandle(
props.ref,
() =>
({
scrollToEnd: scrollToEndSpy,
getState: getStateSpy,
}) as unknown as LegendListRef,
);
return (
<div data-testid="legend-list">
{props.ListHeaderComponent}
{props.data.map((item) => (
<div key={props.keyExtractor(item)}>{props.renderItem({ item })}</div>
))}
{props.ListFooterComponent}
</div>
);
}
return { LegendList };
});
import { MessagesTimeline } from "./MessagesTimeline";
const MESSAGE_CREATED_AT = "2026-04-13T12:00:00.000Z";
function buildProps() {
return {
isWorking: false,
activeTurnInProgress: false,
activeTurnStartedAt: null,
listRef: createRef<LegendListRef | null>(),
latestTurn: null,
turnDiffSummaryByAssistantMessageId: new Map(),
routeThreadKey: "environment-local:thread-1",
onOpenTurnDiff: vi.fn(),
revertTurnCountByUserMessageId: new Map(),
onRevertUserMessage: vi.fn(),
isRevertingCheckpoint: false,
onImageExpand: vi.fn(),
activeThreadEnvironmentId: EnvironmentId.make("environment-local"),
markdownCwd: undefined,
resolvedTheme: "dark" as const,
timestampFormat: "24-hour" as const,
workspaceRoot: undefined,
onIsAtEndChange: vi.fn(),
};
}
function buildLongUserMessageText(tail = "deep hidden detail only after expand") {
return Array.from({ length: 9 }, (_, index) =>
index === 8 ? tail : `Line ${index + 1}: ${"verbose prompt content ".repeat(8).trim()}`,
).join("\n");
}
function buildUserTimelineEntry(text: string) {
return {
id: "entry-1",
kind: "message" as const,
createdAt: MESSAGE_CREATED_AT,
message: {
id: "message-1" as never,
role: "user" as const,
text,
createdAt: MESSAGE_CREATED_AT,
streaming: false,
},
};
}
function buildAssistantTimelineEntry(text: string) {
return {
id: "entry-assistant-1",
kind: "message" as const,
createdAt: MESSAGE_CREATED_AT,
message: {
id: "message-assistant-1" as never,
role: "assistant" as const,
text,
createdAt: MESSAGE_CREATED_AT,
streaming: false,
},
};
}
describe("MessagesTimeline", () => {
afterEach(() => {
scrollToEndSpy.mockReset();
getStateSpy.mockClear();
vi.restoreAllMocks();
document.body.innerHTML = "";
});
it("renders activity rows instead of the empty placeholder when a thread has non-message timeline data", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "work-1",
kind: "work",
createdAt: "2026-04-13T12:00:00.000Z",
entry: {
id: "work-1",
createdAt: "2026-04-13T12:00:00.000Z",
label: "read files",
detail: "Inspecting repository state",
tone: "tool",
},
},
]}
/>,
);
try {
await expect
.element(page.getByText("Send a message to start the conversation."))
.not.toBeInTheDocument();
await expect.element(page.getByText("Inspecting repository state")).toBeVisible();
expect(document.querySelector('[data-testid="legend-list"] [title]')).toBeNull();
} finally {
await screen.unmount();
}
});
it("uses accessible expansion instead of native titles or preview tooltips for work entry details", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
workspaceRoot="/repo"
timelineEntries={[
{
id: "work-command",
kind: "work",
createdAt: MESSAGE_CREATED_AT,
entry: {
id: "work-command",
createdAt: MESSAGE_CREATED_AT,
label: "command",
detail: "Inspecting generated output",
command: "git diff -- apps/web/src/components/ChatMarkdown.tsx",
rawCommand: "git diff -- apps/web/src/components/ChatMarkdown.tsx --stat",
changedFiles: ["/repo/apps/web/src/components/ChatMarkdown.tsx"],
tone: "tool",
},
},
]}
/>,
);
try {
expect(document.querySelector('[data-testid="legend-list"] [title]')).toBeNull();
const commandTrigger = page.getByLabelText(
"Command - git diff -- apps/web/src/components/ChatMarkdown.tsx",
);
await commandTrigger.hover();
expect(document.querySelector('[data-slot="tooltip-popup"]')).toBeNull();
await commandTrigger.click();
await expect
.element(page.getByText("git diff -- apps/web/src/components/ChatMarkdown.tsx --stat"))
.toBeVisible();
} finally {
await screen.unmount();
}
});
it("snaps to the bottom when timeline rows appear after an initially empty render", async () => {
const requestAnimationFrameSpy = vi
.spyOn(window, "requestAnimationFrame")
.mockImplementation((callback: FrameRequestCallback) => {
callback(0);
return 1;
});
vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => undefined);
const props = buildProps();
const screen = await render(<MessagesTimeline {...props} timelineEntries={[]} />);
try {
await expect
.element(page.getByText("Send a message to start the conversation."))
.toBeVisible();
await screen.rerender(
<MessagesTimeline
{...props}
timelineEntries={[
{
id: "work-1",
kind: "work",
createdAt: "2026-04-13T12:00:00.000Z",
entry: {
id: "work-1",
createdAt: "2026-04-13T12:00:00.000Z",
label: "read files",
detail: "Inspecting repository state",
tone: "tool",
},
},
]}
/>,
);
await expect.element(page.getByText("Inspecting repository state")).toBeVisible();
expect(props.onIsAtEndChange).toHaveBeenCalledWith(true);
expect(scrollToEndSpy).toHaveBeenCalledWith({ animated: false });
expect(requestAnimationFrameSpy).toHaveBeenCalled();
} finally {
await screen.unmount();
}
});
it("starts long user messages collapsed by default", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText())]}
/>,
);
try {
const toggle = page.getByRole("button", { name: "Show full message" });
await expect.element(toggle).toBeVisible();
await expect.element(toggle).toHaveAttribute("aria-expanded", "false");
const messageBody = document.querySelector(
"[data-user-message-body='true']",
) as HTMLDivElement | null;
expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true");
expect(messageBody?.className).toContain("max-h-44");
expect(messageBody?.className).toContain("overflow-hidden");
expect(messageBody?.getAttribute("data-user-message-fade")).toBe("true");
expect(messageBody?.style.maskImage).toContain("linear-gradient");
} finally {
await screen.unmount();
}
});
it("expands and re-collapses long user messages from the toggle", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText())]}
/>,
);
try {
const expandButton = page.getByRole("button", { name: "Show full message" });
await expect.element(expandButton).toBeVisible();
expect(document.body.textContent ?? "").toContain("deep hidden detail only after expand");
await expandButton.click();
const collapseButton = page.getByRole("button", { name: "Show less" });
await expect.element(collapseButton).toBeVisible();
await expect.element(collapseButton).toHaveAttribute("aria-expanded", "true");
let messageBody = document.querySelector("[data-user-message-body='true']");
expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("false");
expect(messageBody?.className).not.toContain("max-h-44");
expect(messageBody?.getAttribute("data-user-message-fade")).toBe("false");
expect((messageBody as HTMLDivElement | null)?.style.maskImage ?? "").toBe("");
await collapseButton.click();
await expect.element(page.getByRole("button", { name: "Show full message" })).toBeVisible();
messageBody = document.querySelector("[data-user-message-body='true']");
expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true");
expect(messageBody?.className).toContain("max-h-44");
expect(messageBody?.getAttribute("data-user-message-fade")).toBe("true");
expect((messageBody as HTMLDivElement | null)?.style.maskImage).toContain("linear-gradient");
} finally {
await screen.unmount();
}
});
it("starts the newest long user prompt collapsed", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[buildUserTimelineEntry(buildLongUserMessageText("latest long prompt"))]}
/>,
);
try {
await expect.element(page.getByRole("button", { name: "Show full message" })).toBeVisible();
const messageBody = document.querySelector("[data-user-message-body='true']");
expect(messageBody?.getAttribute("data-user-message-collapsed")).toBe("true");
} finally {
await screen.unmount();
}
});
it("renders user messages as markdown with chat-style line breaks", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
buildUserTimelineEntry(
"## Plan\nuse **bold** and [a link](https://example.com)\nsecond line",
),
]}
/>,
);
try {
await expect.element(page.getByRole("heading", { level: 2, name: "Plan" })).toBeVisible();
await expect
.element(page.getByRole("link", { name: "a link" }))
.toHaveAttribute("href", "https://example.com");
const messageBody = document.querySelector("[data-user-message-body='true']");
expect(messageBody?.querySelector("strong")?.textContent).toBe("bold");
// remark-breaks: the single newline between the inline runs is a <br>.
expect(messageBody?.querySelectorAll("p br").length).toBe(1);
} finally {
await screen.unmount();
}
});
it("renders markdown file tags in user and assistant messages", async () => {
const fileLink = "[package.json](path/to/package.json)";
const screen = await render(
<MessagesTimeline
{...buildProps()}
markdownCwd="/repo/project"
timelineEntries={[
buildUserTimelineEntry(`Review ${fileLink}`),
buildAssistantTimelineEntry(`I reviewed ${fileLink}`),
]}
/>,
);
try {
const userFileLink = document.querySelector(
'[data-message-role="user"] .chat-markdown-file-link',
);
const assistantFileLink = document.querySelector(
'[data-message-role="assistant"] .chat-markdown-file-link',
);
expect(userFileLink?.textContent).toContain("package.json");
expect(userFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json");
expect(assistantFileLink?.textContent).toContain("package.json");
expect(assistantFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json");
} finally {
await screen.unmount();
}
});
it("uses the file path without line suffix for markdown file tag icons", async () => {
const fileLink = "[package.json](path/to/package.json:25)";
const screen = await render(
<MessagesTimeline
{...buildProps()}
markdownCwd="/repo/project"
timelineEntries={[buildAssistantTimelineEntry(`Updated ${fileLink}`)]}
/>,
);
try {
const assistantFileLink = document.querySelector(
'[data-message-role="assistant"] .chat-markdown-file-link',
);
const icon = assistantFileLink?.querySelector("svg[data-pierre-icon]");
expect(assistantFileLink?.textContent).toContain("package.json");
expect(assistantFileLink?.textContent).toContain("L25");
expect(assistantFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json:25");
expect(icon?.getAttribute("data-pierre-icon")).toBe("t3-file-icon-package-json");
} finally {
await screen.unmount();
}
});
it("folds settled-turn work behind a Worked-for row and expands it on click", async () => {
const screen = await render(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "entry-commentary",
kind: "message" as const,
createdAt: "2026-04-13T12:00:00.000Z",
message: {
id: "message-commentary" as never,
role: "assistant" as const,
text: "Let me look around first.",
turnId: "turn-1" as never,
createdAt: "2026-04-13T12:00:00.000Z",
completedAt: "2026-04-13T12:00:02.000Z",
streaming: false,
},
},
{
id: "entry-work",
kind: "work" as const,
createdAt: "2026-04-13T12:00:05.000Z",
entry: {
id: "work-1",
createdAt: "2026-04-13T12:00:05.000Z",
turnId: "turn-1" as never,
label: "read files",
detail: "Inspecting repository state",
tone: "tool" as const,
},
},
{
id: "entry-final",
kind: "message" as const,
createdAt: "2026-04-13T12:00:20.000Z",
message: {
id: "message-final" as never,
role: "assistant" as const,
text: "All done.",
turnId: "turn-1" as never,
createdAt: "2026-04-13T12:00:20.000Z",
completedAt: "2026-04-13T12:00:30.000Z",
streaming: false,
},
},
]}
/>,
);
try {
const foldButton = page.getByRole("button", { name: "Worked for 30s" });
await expect.element(foldButton).toBeVisible();
await expect.element(foldButton).toHaveAttribute("aria-expanded", "false");
expect(document.body.textContent).toContain("All done.");
expect(document.body.textContent).not.toContain("Let me look around first.");
expect(document.body.textContent).not.toContain("Inspecting repository state");
await foldButton.click();
await expect.element(foldButton).toHaveAttribute("aria-expanded", "true");
expect(document.body.textContent).toContain("Let me look around first.");
expect(document.body.textContent).toContain("Inspecting repository state");
await foldButton.click();
await expect.element(foldButton).toHaveAttribute("aria-expanded", "false");
expect(document.body.textContent).not.toContain("Inspecting repository state");
} finally {
await screen.unmount();
}
});
});