1041 lines · 30.2 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test";import {  computeStableMessagesTimelineRows,  computeMessageDurationStart,  deriveMessagesTimelineRows,  normalizeCompactToolLabel,  resolveAssistantMessageCopyState,} from "./MessagesTimeline.logic"; describe("computeMessageDurationStart", () => {  it("returns message createdAt when there is no preceding user message", () => {    const result = computeMessageDurationStart([      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:05Z",        updatedAt: "2026-01-01T00:00:10Z",        streaming: false,      },    ]);    expect(result).toEqual(new Map([["a1", "2026-01-01T00:00:05Z"]]));  });   it("uses the user message createdAt for the first assistant response", () => {    const result = computeMessageDurationStart([      {        id: "u1",        role: "user",        createdAt: "2026-01-01T00:00:00Z",        updatedAt: "2026-01-01T00:00:00Z",        streaming: false,      },      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:30Z",        updatedAt: "2026-01-01T00:00:30Z",        streaming: false,      },    ]);     expect(result).toEqual(      new Map([        ["u1", "2026-01-01T00:00:00Z"],        ["a1", "2026-01-01T00:00:00Z"],      ]),    );  });   it("uses the previous completed assistant updatedAt for subsequent assistant responses", () => {    const result = computeMessageDurationStart([      {        id: "u1",        role: "user",        createdAt: "2026-01-01T00:00:00Z",        updatedAt: "2026-01-01T00:00:00Z",        streaming: false,      },      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:30Z",        updatedAt: "2026-01-01T00:00:30Z",        streaming: false,      },      {        id: "a2",        role: "assistant",        createdAt: "2026-01-01T00:00:55Z",        updatedAt: "2026-01-01T00:00:55Z",        streaming: false,      },    ]);     expect(result).toEqual(      new Map([        ["u1", "2026-01-01T00:00:00Z"],        ["a1", "2026-01-01T00:00:00Z"],        ["a2", "2026-01-01T00:00:30Z"],      ]),    );  });   it("does not advance the boundary for a streaming message", () => {    const result = computeMessageDurationStart([      {        id: "u1",        role: "user",        createdAt: "2026-01-01T00:00:00Z",        updatedAt: "2026-01-01T00:00:00Z",        streaming: false,      },      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:30Z",        updatedAt: "2026-01-01T00:00:40Z",        streaming: true,      },      {        id: "a2",        role: "assistant",        createdAt: "2026-01-01T00:00:55Z",        updatedAt: "2026-01-01T00:00:55Z",        streaming: false,      },    ]);     expect(result).toEqual(      new Map([        ["u1", "2026-01-01T00:00:00Z"],        ["a1", "2026-01-01T00:00:00Z"],        ["a2", "2026-01-01T00:00:00Z"],      ]),    );  });   it("resets the boundary on a new user message", () => {    const result = computeMessageDurationStart([      {        id: "u1",        role: "user",        createdAt: "2026-01-01T00:00:00Z",        updatedAt: "2026-01-01T00:00:00Z",        streaming: false,      },      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:30Z",        updatedAt: "2026-01-01T00:00:30Z",        streaming: false,      },      {        id: "u2",        role: "user",        createdAt: "2026-01-01T00:01:00Z",        updatedAt: "2026-01-01T00:01:00Z",        streaming: false,      },      {        id: "a2",        role: "assistant",        createdAt: "2026-01-01T00:01:20Z",        updatedAt: "2026-01-01T00:01:20Z",        streaming: false,      },    ]);     expect(result).toEqual(      new Map([        ["u1", "2026-01-01T00:00:00Z"],        ["a1", "2026-01-01T00:00:00Z"],        ["u2", "2026-01-01T00:01:00Z"],        ["a2", "2026-01-01T00:01:00Z"],      ]),    );  });   it("handles system messages without affecting the boundary", () => {    const result = computeMessageDurationStart([      {        id: "u1",        role: "user",        createdAt: "2026-01-01T00:00:00Z",        updatedAt: "2026-01-01T00:00:00Z",        streaming: false,      },      {        id: "s1",        role: "system",        createdAt: "2026-01-01T00:00:01Z",        updatedAt: "2026-01-01T00:00:01Z",        streaming: false,      },      {        id: "a1",        role: "assistant",        createdAt: "2026-01-01T00:00:30Z",        updatedAt: "2026-01-01T00:00:30Z",        streaming: false,      },    ]);     expect(result).toEqual(      new Map([        ["u1", "2026-01-01T00:00:00Z"],        ["s1", "2026-01-01T00:00:00Z"],        ["a1", "2026-01-01T00:00:00Z"],      ]),    );  });   it("returns empty map for empty input", () => {    expect(computeMessageDurationStart([])).toEqual(new Map());  });}); describe("normalizeCompactToolLabel", () => {  it("removes trailing completion wording from command labels", () => {    expect(normalizeCompactToolLabel("Ran command complete")).toBe("Ran command");  });   it("removes trailing completion wording from other labels", () => {    expect(normalizeCompactToolLabel("Read file completed")).toBe("Read file");  });}); describe("resolveAssistantMessageCopyState", () => {  it("returns enabled copy state for completed assistant messages", () => {    expect(      resolveAssistantMessageCopyState({        showCopyButton: true,        text: "Ship it",        streaming: false,      }),    ).toEqual({      text: "Ship it",      visible: true,    });  });   it("hides copy while an assistant message is still streaming", () => {    expect(      resolveAssistantMessageCopyState({        showCopyButton: true,        text: "Still streaming",        streaming: true,      }),    ).toEqual({      text: "Still streaming",      visible: false,    });  });   it("hides copy for empty completed assistant messages", () => {    expect(      resolveAssistantMessageCopyState({        showCopyButton: true,        text: "   ",        streaming: false,      }),    ).toEqual({      text: null,      visible: false,    });  });   it("hides copy for non-terminal assistant messages", () => {    expect(      resolveAssistantMessageCopyState({        showCopyButton: false,        text: "Interim thought",        streaming: false,      }),    ).toEqual({      text: "Interim thought",      visible: false,    });  });}); describe("deriveMessagesTimelineRows", () => {  it("only enables assistant copy for the terminal assistant message in a turn", () => {    const rows = deriveMessagesTimelineRows({      timelineEntries: [        {          id: "user-1-entry",          kind: "message",          createdAt: "2026-01-01T00:00:00Z",          message: {            id: "user-1" as never,            role: "user",            text: "Write a poem",            turnId: null,            createdAt: "2026-01-01T00:00:00Z",            updatedAt: "2026-01-01T00:00:00Z",            streaming: false,          },        },        {          id: "assistant-thought-entry",          kind: "message",          createdAt: "2026-01-01T00:00:10Z",          message: {            id: "assistant-thought" as never,            role: "assistant",            text: "I should ground this first.",            turnId: "turn-1" as never,            createdAt: "2026-01-01T00:00:10Z",            updatedAt: "2026-01-01T00:00:11Z",            streaming: false,          },        },        {          id: "assistant-final-entry",          kind: "message",          createdAt: "2026-01-01T00:00:20Z",          message: {            id: "assistant-final" as never,            role: "assistant",            text: "Here is the poem.",            turnId: "turn-1" as never,            createdAt: "2026-01-01T00:00:20Z",            updatedAt: "2026-01-01T00:00:30Z",            streaming: false,          },        },      ],      expandedTurnIds: new Set(["turn-1" as never]),      isWorking: false,      activeTurnStartedAt: null,      turnDiffSummaryByAssistantMessageId: new Map(),      revertTurnCountByUserMessageId: new Map(),    });     const assistantRows = rows.filter(      (row): row is Extract<(typeof rows)[number], { kind: "message" }> =>        row.kind === "message" && row.message.role === "assistant",    );     expect(assistantRows).toHaveLength(2);    expect(assistantRows[0]?.showAssistantCopyButton).toBe(false);    expect(assistantRows[1]?.showAssistantCopyButton).toBe(true);  });   it("marks only the active assistant turn as streaming for copy controls", () => {    const rows = deriveMessagesTimelineRows({      timelineEntries: [        {          id: "assistant-one-entry",          kind: "message",          createdAt: "2026-01-01T00:00:10Z",          message: {            id: "assistant-one" as never,            role: "assistant",            text: "Earlier response.",            turnId: "turn-1" as never,            createdAt: "2026-01-01T00:00:10Z",            updatedAt: "2026-01-01T00:00:11Z",            streaming: false,          },        },        {          id: "assistant-two-entry",          kind: "message",          createdAt: "2026-01-01T00:00:20Z",          message: {            id: "assistant-two" as never,            role: "assistant",            text: "Active response.",            turnId: "turn-2" as never,            createdAt: "2026-01-01T00:00:20Z",            updatedAt: "2026-01-01T00:00:30Z",            streaming: false,          },        },      ],      latestTurn: {        turnId: "turn-2" as never,        state: "running",        startedAt: "2026-01-01T00:00:19Z",        completedAt: null,      },      isWorking: false,      activeTurnStartedAt: null,      turnDiffSummaryByAssistantMessageId: new Map(),      revertTurnCountByUserMessageId: new Map(),    });     const assistantRows = rows.filter(      (row): row is Extract<(typeof rows)[number], { kind: "message" }> =>        row.kind === "message" && row.message.role === "assistant",    );     expect(assistantRows[0]?.assistantCopyStreaming).toBe(false);    expect(assistantRows[1]?.assistantCopyStreaming).toBe(true);  });   it("projects assistant diff summaries and user revert counts onto the affected rows", () => {    const assistantTurnDiffSummary = {      turnId: "turn-1" as never,      completedAt: "2026-01-01T00:00:30Z",      assistantMessageId: "assistant-1" as never,      checkpointTurnCount: 2,      checkpointRef: "checkpoint-1" as never,      status: "ready" as const,      files: [{ path: "src/index.ts", kind: "modified", additions: 3, deletions: 1 }],    };     const rows = deriveMessagesTimelineRows({      timelineEntries: [        {          id: "user-entry",          kind: "message",          createdAt: "2026-01-01T00:00:00Z",          message: {            id: "user-1" as never,            role: "user",            text: "Do the thing",            turnId: null,            createdAt: "2026-01-01T00:00:00Z",            updatedAt: "2026-01-01T00:00:00Z",            streaming: false,          },        },        {          id: "assistant-entry",          kind: "message",          createdAt: "2026-01-01T00:00:20Z",          message: {            id: "assistant-1" as never,            role: "assistant",            text: "Done",            turnId: "turn-1" as never,            createdAt: "2026-01-01T00:00:20Z",            updatedAt: "2026-01-01T00:00:30Z",            streaming: false,          },        },      ],      isWorking: false,      activeTurnStartedAt: null,      turnDiffSummaryByAssistantMessageId: new Map([        ["assistant-1" as never, assistantTurnDiffSummary],      ]),      revertTurnCountByUserMessageId: new Map([["user-1" as never, 1]]),    });     const userRow = rows.find(      (row): row is Extract<(typeof rows)[number], { kind: "message" }> =>        row.kind === "message" && row.message.role === "user",    );    const assistantRow = rows.find(      (row): row is Extract<(typeof rows)[number], { kind: "message" }> =>        row.kind === "message" && row.message.role === "assistant",    );     expect(userRow?.revertTurnCount).toBe(1);    expect(assistantRow?.assistantTurnDiffSummary).toBe(assistantTurnDiffSummary);  });   it("folds settled-turn commentary and work behind a Worked-for row", () => {    const timelineEntries = [      {        id: "user-entry",        kind: "message" as const,        createdAt: "2026-01-01T00:00:00Z",        message: {          id: "user-1" as never,          role: "user" as const,          text: "Build it",          turnId: null,          createdAt: "2026-01-01T00:00:00Z",          updatedAt: "2026-01-01T00:00:00Z",          streaming: false,        },      },      {        id: "assistant-thought-entry",        kind: "message" as const,        createdAt: "2026-01-01T00:00:05Z",        message: {          id: "assistant-thought" as never,          role: "assistant" as const,          text: "Looking around first.",          turnId: "turn-1" as never,          createdAt: "2026-01-01T00:00:05Z",          updatedAt: "2026-01-01T00:00:06Z",          streaming: false,        },      },      {        id: "work-entry-1",        kind: "work" as const,        createdAt: "2026-01-01T00:00:08Z",        entry: {          id: "work-1",          createdAt: "2026-01-01T00:00:08Z",          turnId: "turn-1" as never,          label: "Ran command",          tone: "tool" as const,        },      },      {        id: "assistant-final-entry",        kind: "message" as const,        createdAt: "2026-01-01T00:00:20Z",        message: {          id: "assistant-final" as never,          role: "assistant" as const,          text: "Done",          turnId: "turn-1" as never,          createdAt: "2026-01-01T00:00:20Z",          updatedAt: "2026-01-01T00:00:22Z",          streaming: false,        },      },    ]; 

Lines 1–500 of 1041.