返回 DeepSeek-Reasonix
chat-view-history-head.test.ts
根目录 / desktop / frontend / src / __tests__ / chat-view-history-head.test.ts
1 import assert from "node:assert/strict";
2 import { ChatSource, type ChatInput } from "../lib/chatViewSource";
3 import type { Item } from "../lib/useController";
4
5 const tool = (id: string): Item => ({ kind: "tool", id, name: "read_file", args: "", readOnly: true, status: "done", output: "ok" });
6 const answer = (id: string, streaming = false): Item => ({ kind: "assistant", id, text: "done", reasoning: "", streaming });
7 const base: ChatInput = { items: [], running: false, hydrating: false, hasOlder: true, loadingOlder: false };
8 const process = (source: ChatSource, key: string) => {
9 const node = source.getNodeSnapshot(`${key}:process`);
10 assert.equal(node?.kind, "process");
11 return node as Extract<NonNullable<typeof node>, { kind: "process" }>;
12 };
13
14 const head: Item[] = [tool("t1"), tool("t2"), answer("a1")];
15 const settled = new ChatSource("head-settled");
16 settled.update({ ...base, items: head });
17 await Promise.resolve();
18 assert.ok(process(settled, "history-head").foldable, "a head group with tools can fold");
19 assert.ok(process(settled, "history-head").collapsed, "a settled head group starts folded");
20 settled.toggleProcess("history-head");
21 assert.equal(process(settled, "history-head").collapsed, false, "the head group folds and unfolds by hand");
22 settled.toggleProcess("history-head");
23 settled.dispose();
24
25 const running = new ChatSource("head-running");
26 running.update({ ...base, items: [tool("t1"), answer("a1", true)], running: true });
27 await Promise.resolve();
28 const live = process(running, "history-head");
29 assert.ok(!live.foldable && !live.collapsed, "a head group still running stays open");
30 running.dispose();
31
32 const chosen = new ChatSource("head-choice");
33 chosen.update({ ...base, items: head });
34 await Promise.resolve();
35 chosen.toggleProcess("history-head");
36 const user: Item = { kind: "user", id: "u0", text: "long task", checkpointTurn: 1 };
37 chosen.update({ ...base, items: [user, ...head], hasOlder: false });
38 await Promise.resolve();
39 assert.equal(chosen.getNodeSnapshot("history-head:process"), undefined, "the head key is gone once the user message arrives");
40 assert.equal(process(chosen, "u0").collapsed, false, "the manual choice follows the turn when its user message loads");
41 chosen.dispose();
42
43 const folded = new ChatSource("head-folded");
44 folded.update({ ...base, items: head });
45 await Promise.resolve();
46 folded.update({ ...base, items: [user, ...head], hasOlder: false });
47 await Promise.resolve();
48 assert.ok(process(folded, "u0").collapsed, "an untouched head stays folded after the user message loads");
49 folded.dispose();
50
51 const unrelated = new ChatSource("head-unrelated");
52 unrelated.update({ ...base, items: head });
53 await Promise.resolve();
54 unrelated.toggleProcess("history-head");
55 const other: Item[] = [{ kind: "user", id: "u9", text: "other", checkpointTurn: 9 }, tool("x1"), answer("ax")];
56 unrelated.update({ ...base, items: other });
57 await Promise.resolve();
58 assert.ok(process(unrelated, "u9").collapsed, "a choice made on the head does not leak onto a turn that does not contain it");
59 unrelated.dispose();
60
61 console.log("chat view: history head fold, running head and choice migration passed");
62
62 lines TYPESCRIPT