import { EnvironmentId, MessageId } from "@t3tools/contracts";import { createRef } from "react";import { renderToStaticMarkup } from "react-dom/server";import { beforeAll, describe, expect, it, vi } from "vitest";import type { LegendListRef } from "@legendapp/list/react"; vi.mock("@legendapp/list/react", async () => { const React = await import("react"); const LegendList = React.forwardRef(function MockLegendList( 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.ForwardedRef<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 };}); 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"); function buildProps() { return { isWorking: false, activeTurnInProgress: false, activeTurnId: null, activeTurnStartedAt: null, listRef: createRef<LegendListRef | null>(), 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: () => {}, };} describe("MessagesTimeline", () => { it("renders inline terminal labels with the composer chip UI", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( <MessagesTimeline {...buildProps()} timelineEntries={[ { id: "entry-1", kind: "message", createdAt: "2026-03-17T19:12:28.000Z", message: { id: MessageId.make("message-2"), role: "user", text: [ "yoo what's @terminal-1:1-5 mean", "", "<terminal_context>", "- Terminal 1 lines 1-5:", " 1 | julius@mac effect-http-ws-cli % bun i", " 2 | bun install v1.3.9 (cf6cdbbb)", "</terminal_context>", ].join("\n"), createdAt: "2026-03-17T19:12:28.000Z", streaming: false, }, }, ]} />, ); expect(markup).toContain("Terminal 1 lines 1-5"); expect(markup).toContain("lucide-terminal"); expect(markup).toContain("yoo what's "); }, 20_000); it("renders context compaction entries in the normal work log", async () => { const { MessagesTimeline } = await import("./MessagesTimeline"); const markup = renderToStaticMarkup( <MessagesTimeline {...buildProps()} timelineEntries={[ { id: "entry-1", kind: "work", createdAt: "2026-03-17T19:12:28.000Z", entry: { id: "work-1", createdAt: "2026-03-17T19:12:28.000Z", label: "Context compacted", tone: "info", }, }, ]} />, ); 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( <MessagesTimeline {...buildProps()} timelineEntries={[ { id: "entry-1", kind: "work", createdAt: "2026-03-17T19:12:28.000Z", entry: { id: "work-1", createdAt: "2026-03-17T19:12:28.000Z", label: "Updated files", tone: "tool", changedFiles: ["C:/Users/mike/dev-stuff/t3code/apps/web/src/session-logic.ts"], }, }, ]} workspaceRoot="C:/Users/mike/dev-stuff/t3code" />, ); 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"); });});
import { EnvironmentId, MessageId } from "@t3tools/contracts";
import { createRef } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { LegendListRef } from "@legendapp/list/react";
vi.mock("@legendapp/list/react", async () => {
const React = await import("react");
const LegendList = React.forwardRef(function MockLegendList(
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.ForwardedRef<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 };
});
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");
function buildProps() {
return {
isWorking: false,
activeTurnInProgress: false,
activeTurnId: null,
activeTurnStartedAt: null,
listRef: createRef<LegendListRef | null>(),
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: () => {},
};
}
describe("MessagesTimeline", () => {
it("renders inline terminal labels with the composer chip UI", async () => {
const { MessagesTimeline } = await import("./MessagesTimeline");
const markup = renderToStaticMarkup(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "entry-1",
kind: "message",
createdAt: "2026-03-17T19:12:28.000Z",
message: {
id: MessageId.make("message-2"),
role: "user",
text: [
"yoo what's @terminal-1:1-5 mean",
"",
"<terminal_context>",
"- Terminal 1 lines 1-5:",
" 1 | julius@mac effect-http-ws-cli % bun i",
" 2 | bun install v1.3.9 (cf6cdbbb)",
"</terminal_context>",
].join("\n"),
createdAt: "2026-03-17T19:12:28.000Z",
streaming: false,
},
},
]}
/>,
);
expect(markup).toContain("Terminal 1 lines 1-5");
expect(markup).toContain("lucide-terminal");
expect(markup).toContain("yoo what's ");
}, 20_000);
it("renders context compaction entries in the normal work log", async () => {
const { MessagesTimeline } = await import("./MessagesTimeline");
const markup = renderToStaticMarkup(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "entry-1",
kind: "work",
createdAt: "2026-03-17T19:12:28.000Z",
entry: {
id: "work-1",
createdAt: "2026-03-17T19:12:28.000Z",
label: "Context compacted",
tone: "info",
},
},
]}
/>,
);
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(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "entry-1",
kind: "work",
createdAt: "2026-03-17T19:12:28.000Z",
entry: {
id: "work-1",
createdAt: "2026-03-17T19:12:28.000Z",
label: "Updated files",
tone: "tool",
changedFiles: ["C:/Users/mike/dev-stuff/t3code/apps/web/src/session-logic.ts"],
},
},
]}
workspaceRoot="C:/Users/mike/dev-stuff/t3code"
/>,
);
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");
});
});