返回 DeepSeek-Reasonix
submission-output-order.test.ts
根目录 / desktop / frontend / src / __tests__ / submission-output-order.test.ts
1 import assert from "node:assert/strict";
2 import test from "node:test";
3 import { initialState, reducer, type Item, type State } from "../lib/useController";
4
5 const user = (id: string, turnId = "turn"): Item => ({ kind: "user", id, messageId: id, turnId, text: "question" });
6 const answer = (id: string): Item => ({ kind: "assistant", id, turnId: "turn", text: id, reasoning: "", streaming: false });
7 const tool = (id: string): Extract<Item, { kind: "tool" }> => ({ kind: "tool", id, turnId: "turn", name: "read_file", args: "{}", readOnly: true, status: "done", output: "contents" });
8 const install = (state: State, items: Item[]) => reducer(state, {
9 type: "transcript_records", confirmedUsers: [], projection: {
10 items, removeIds: [], startTurn: 0, endTurn: 1, totalTurns: 1,
11 hasOlder: false, hasNewer: false, revision: 1, revisionKnown: true, digest: "cut",
12 },
13 });
14 const stream = (state: State, id: string) => reducer(state, {
15 type: "event", e: { kind: "text", messageId: id, turnId: "turn", text: "next answer" },
16 });
17 const ids = (state: State) => state.items.map(item => item.id);
18 const empty = (): State => ({ ...initialState, transcriptProtocol: 2, activeTurnId: "turn" });
19
20 test("a later sampling answer stays after the earlier answer and tool on repeated projection patches", () => {
21 const formal = [user("user"), answer("first"), tool("call")];
22 let state = stream(install(empty(), formal), "next");
23 const liveAnswer = state.items[state.items.length - 1];
24 for (let revision = 0; revision < 3; revision++) {
25 state = install(state, [...formal.slice(0, -1), { ...tool("call"), output: `contents ${revision}` }]);
26 assert.deepEqual(ids(state), ["user", "first", "call", "m:next"]);
27 assert.equal(state.items[state.items.length - 1], liveAnswer, "patching an earlier tool must retain the live host");
28 }
29 });
30
31 test("a newly formal predecessor preserves the order of remaining live tools and answers", () => {
32 const formal = [user("user"), answer("first"), tool("call")];
33 let state = stream(install(empty(), formal), "second");
34 state = reducer(state, { type: "event", e: { kind: "tool_dispatch", turnId: "turn", messageId: "second",
35 tool: { id: "second-call", name: "read_file", args: "{}", readOnly: true } } });
36 state = stream(state, "third");
37 const expected = ["user", "first", "call", "m:second", "second-call", "m:third"];
38 assert.deepEqual(ids(state), expected);
39 state = install(state, [...formal, answer("m:second")]);
40 assert.deepEqual(ids(state), expected);
41 state = install(state, [...formal, answer("m:second"), tool("second-call")]);
42 assert.deepEqual(ids(state), expected);
43 });
44
45 test("a delayed user stays before its formal tool and live answer without reversing the output", () => {
46 let state = stream(install(empty(), [user("previous", "previous-turn"), tool("call")]), "next");
47 state = install(state, [user("previous", "previous-turn"), tool("call"), user("user")]);
48 assert.deepEqual(ids(state), ["previous", "user", "call", "m:next"]);
49 });
50
51 test("reclaiming a formal predecessor preserves the surviving predecessor of a live row", () => {
52 let state = stream(install(empty(), [user("user"), answer("first"), tool("call")]), "next");
53 state = install(state, [user("user"), answer("first")]);
54 assert.deepEqual(ids(state), ["user", "first", "m:next"]);
55 });
56
57 for (const mode of ["event", "stream_batch"] as const) test(`${mode} preserves the reader window and the complete offscreen stream`, () => {
58 let state: State = { ...empty(), historyHasNewer: true, historyHasOlder: true,
59 items: [user("old-reader", "old-turn")] };
60 const resident = state.items;
61 for (const text of ["first", "second"]) {
62 state = reducer(state, mode === "event"
63 ? { type: "event", e: { kind: "text", messageId: "active", turnId: "turn", text } }
64 : { type: "stream_batch", segments: [{ kind: "text", delta: text }] });
65 assert.equal(state.historyHasNewer, true);
66 assert.equal(state.items, resident);
67 assert.equal(state.offscreenItems?.length, 1);
68 assert.equal(state.offscreenItems[0].turnId, "turn");
69 }
70 assert.equal(state.live?.text, "firstsecond");
71 const liveHost = state.offscreenItems![0];
72 state = reducer(state, { type: "history_append", items: [], startTurn: 0, endTurn: 1, totalTurns: 1, hasOlder: true, hasNewer: false });
73 assert.equal(state.items.find(item => item.id === liveHost.id), liveHost);
74 assert.equal(state.offscreenItems, undefined);
75 assert.equal(state.live?.text, "firstsecond");
76 });
77
77 lines TYPESCRIPT