import { EnvironmentId, MessageId } from "@t3tools/contracts"; import { createRef, type ReactNode, type Ref } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { beforeAll, describe, expect, it, vi } from "vite-plus/test"; import type { LegendListRef } from "@legendapp/list/react"; vi.mock("@legendapp/list/react", async () => { const legendListTestId = "legend-list"; const LegendList = (props: { data: Array<{ id: string }>; keyExtractor: (item: { id: string }) => string; renderItem: (args: { item: { id: string } }) => ReactNode; ListHeaderComponent?: ReactNode; ListFooterComponent?: ReactNode; ref?: Ref; }) => (
{props.ListHeaderComponent} {props.data.map((item) => (
{props.renderItem({ item })}
))} {props.ListFooterComponent}
); return { LegendList }; }); function MockFileDiff(props: { fileDiff: { name?: string | null; prevName?: string | null }; renderCustomHeader?: (fileDiff: { name?: string | null; prevName?: string | null; }) => React.ReactNode; }) { return (
{props.renderCustomHeader?.(props.fileDiff)} {props.fileDiff.name ?? props.fileDiff.prevName ?? "diff"}
); } vi.mock("@pierre/diffs/react", () => { return { FileDiff: MockFileDiff }; }); function matchMedia() { return { matches: false, addEventListener: () => {}, removeEventListener: () => {}, }; } beforeAll(() => { const classList = { add: () => {}, remove: () => {}, toggle: () => {}, contains: () => false, }; vi.stubGlobal("localStorage", { getItem: () => null, setItem: () => {}, removeItem: () => {}, clear: () => {}, }); vi.stubGlobal("window", { matchMedia, addEventListener: () => {}, removeEventListener: () => {}, requestAnimationFrame: (callback: FrameRequestCallback) => { callback(0); return 0; }, cancelAnimationFrame: () => {}, desktopBridge: undefined, }); vi.stubGlobal("document", { documentElement: { classList, offsetHeight: 0, }, }); }); const ACTIVE_THREAD_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); const MESSAGE_CREATED_AT = "2026-03-17T19:12:28.000Z"; function buildProps() { return { isWorking: false, activeTurnInProgress: false, activeTurnId: null, activeTurnStartedAt: null, listRef: createRef(), completionDividerBeforeEntryId: null, completionSummary: null, turnDiffSummaryByAssistantMessageId: new Map(), routeThreadKey: "environment-local:thread-1", onOpenTurnDiff: () => {}, revertTurnCountByUserMessageId: new Map(), onRevertUserMessage: () => {}, isRevertingCheckpoint: false, onImageExpand: () => {}, activeThreadEnvironmentId: ACTIVE_THREAD_ENVIRONMENT_ID, markdownCwd: undefined, resolvedTheme: "light" as const, timestampFormat: "locale" as const, workspaceRoot: undefined, onIsAtEndChange: () => {}, }; } 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: MessageId.make("message-1"), role: "user" as const, text, createdAt: MESSAGE_CREATED_AT, streaming: false, }, }; } describe("MessagesTimeline", () => { it("renders collapse controls for long user messages", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( , ); expect(markup).toContain("Show full message"); expect(markup).toContain('data-user-message-collapsed="true"'); expect(markup).toContain('data-user-message-fade="true"'); expect(markup).toContain('data-user-message-footer="true"'); }); it("does not render collapse controls for short user messages", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( , ); expect(markup).not.toContain("Show full message"); expect(markup).toContain('data-user-message-collapsible="false"'); }); it("renders inline terminal labels with the composer chip UI", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( ", "- Terminal 1 lines 1-5:", " 1 | julius@mac effect-http-ws-cli % bun i", " 2 | bun install v1.3.9 (cf6cdbbb)", "", ].join("\n"), ), ]} />, ); expect(markup).toContain("Terminal 1 lines 1-5"); expect(markup).toContain("lucide-terminal"); expect(markup).toContain("yoo what's "); expect(markup).toContain("Show full message"); }, 20_000); it("keeps the copy button for collapsed long user messages", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( , ); expect(markup).toContain('aria-label="Copy link"'); expect(markup).toContain('data-user-message-collapsed="true"'); expect(markup).toContain('data-user-message-footer="true"'); }); it("renders context compaction entries in the normal work log", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( , ); expect(markup).toContain("Context compacted"); expect(markup).toContain("Work log"); }); it("formats changed file paths from the workspace root", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( , ); expect(markup).toContain("t3code/apps/web/src/session-logic.ts"); expect(markup).not.toContain("C:/Users/mike/dev-stuff/t3code/apps/web/src/session-logic.ts"); }); it("renders review comment contexts as structured cards instead of raw tags", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( ', "Wadduo", "```diff", "@@ -0,0 +47,2 @@", '+ it("keeps valid zero-usage snapshots", () => {', "+ expect(snapshot).not.toBeNull();", "```", "", ].join("\n"), createdAt: "2026-03-17T19:12:28.000Z", streaming: false, }, }, ]} />, ); expect(markup).toContain("contextWindow.test.ts"); expect(markup).toContain("Wadduo"); expect(markup).toContain('data-testid="file-diff"'); expect(markup).not.toContain(">Review comment<"); expect(markup).not.toContain("<review_comment"); expect(markup).not.toContain("</review_comment>"); }); });