Files
multica/packages/views/common/task-transcript/build-timeline.test.ts
2026-05-28 12:34:51 +08:00

80 lines
2.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { TaskMessagePayload } from "@multica/core/types/events";
import { appendTimelineItem, buildTimeline, coalesceTimelineItems, type TimelineItem } from "./build-timeline";
function message(seq: number, type: TaskMessagePayload["type"], content?: string): TaskMessagePayload {
return {
task_id: "task-1",
issue_id: "issue-1",
seq,
type,
content,
};
}
describe("task transcript timeline", () => {
it("merges adjacent text and thinking fragments split by streaming flushes", () => {
const items = buildTimeline([
message(2, "text", "world"),
message(1, "text", "hello "),
message(3, "thinking", "step "),
message(4, "thinking", "one"),
]);
expect(items).toEqual([
expect.objectContaining({ seq: 1, type: "text", content: "hello world" }),
expect.objectContaining({ seq: 3, type: "thinking", content: "step one" }),
]);
});
it("does not merge across tool or error boundaries", () => {
const items = coalesceTimelineItems([
{ seq: 1, type: "text", content: "before" },
{ seq: 2, type: "tool_use", tool: "bash" },
{ seq: 3, type: "text", content: "after" },
{ seq: 4, type: "error", content: "failed" },
{ seq: 5, type: "text", content: "done" },
]);
expect(items.map((item) => item.content ?? item.tool)).toEqual([
"before",
"bash",
"after",
"failed",
"done",
]);
});
it("coalesces newly appended live text with the previous text item", () => {
const existing: TimelineItem[] = [{ seq: 1, type: "text", content: "hello" }];
const items = appendTimelineItem(existing, { seq: 2, type: "text", content: " world" });
expect(items).toEqual([
expect.objectContaining({ seq: 1, type: "text", content: "hello world" }),
]);
});
it("coalesces out-of-order raw text by sequence", () => {
const existing: TimelineItem[] = [
{ seq: 1, type: "text", content: "A" },
{ seq: 3, type: "text", content: "C" },
];
const items = appendTimelineItem(existing, { seq: 2, type: "text", content: "B" });
expect(items).toEqual([
expect.objectContaining({ seq: 1, type: "text", content: "ABC" }),
]);
});
it("redacts secrets after adjacent chunks are coalesced", () => {
const items = buildTimeline([
message(1, "text", "Authorization: Bearer abc123xyz."),
message(2, "text", "def456"),
]);
expect(items[0]?.content).toBe("Authorization: Bearer [REDACTED]");
expect(items[0]?.content).not.toContain("abc123xyz");
expect(items[0]?.content).not.toContain("def456");
});
});