返回 DeepSeek-Reasonix
submission-presentation-dom.test.tsx
根目录 / desktop / frontend / src / __tests__ / submission-presentation-dom.test.tsx
1 import assert from "node:assert/strict";
2 import { act } from "react";
3 import { createTranscriptHarness } from "./transcript-dom-harness";
4 import type { Item } from "../lib/useController";
5 import type { LocalSubmission } from "../lib/localSubmissionState";
6 import { initialState, reducer } from "../lib/useController";
7 import { orderedLocalSubmissions } from "../lib/localSubmissionState";
8
9 const harness = await createTranscriptHarness({ deterministic: true });
10 const pending: LocalSubmission = { submissionId: "send", localId: "optimistic", text: "unique question", createdAt: 1, sequence: 0, status: "sending" };
11 const confirmed: Item = { kind: "user", id: "m:durable", messageId: "durable", submissionId: "send", text: "unique question" };
12 try {
13 await harness.loadModule("/src/components/ChatToolBody.tsx");
14 await harness.render([], { tabId: "identity-dom", localSubmissions: [pending], localSubmissionSendRevision: 1, running: true });
15 await harness.settle();
16 const selector = '[data-chat-kind="user"]';
17 const before = harness.container.querySelector(selector);
18 await harness.render([confirmed], { tabId: "identity-dom", localSubmissions: [], localSubmissionSendRevision: 1, running: true });
19 await harness.settle();
20 assert.equal(harness.container.textContent?.split("unique question").length, 2, "one bubble after durable handoff");
21 assert.ok(before);
22 assert.equal(harness.container.querySelector(selector), before, "mounted user identity remains stable");
23 // React may render only the final state of identity binding + formal install.
24 let batched = reducer({ ...initialState, transcriptProtocol: 2 }, { type: "user", seq: 0, text: "batched question", submissionId: "batched" });
25 const answer: Item = { kind: "assistant", id: "m:answer", text: "answer", reasoning: "process", streaming: false, turnFinal: true };
26 await harness.render([answer], { tabId: "batched-dom", localSubmissions: orderedLocalSubmissions(batched), running: true });
27 await harness.settle();
28 const mounted = ["user", "process", "tail"].map(kind => harness.container.querySelector(`[data-chat-kind="${kind}"]`));
29 batched = reducer(batched, { type: "event", e: { kind: "user_message", source: "executor", messageId: "bound", submissionId: "batched" } });
30 batched = reducer(batched, { type: "transcript_records", confirmedUsers: [{ messageId: "bound" }], projection: {
31 items: [{ kind: "user", id: "m:bound", messageId: "bound", text: "batched question" }, answer], removeIds: [],
32 startTurn: 1, endTurn: 1, totalTurns: 1, hasOlder: false, hasNewer: false, revision: 1, revisionKnown: true, digest: "cut",
33 } });
34 await harness.render(batched.items, { tabId: "batched-dom", localSubmissions: [], visibleSubmissionHandoffs: batched.visibleSubmissionHandoffs, running: true });
35 await harness.settle();
36 for (const [index, kind] of ["user", "process", "tail"].entries()) {
37 assert.ok(mounted[index], `${kind} was mounted`);
38 assert.equal(harness.container.querySelector(`[data-chat-kind="${kind}"]`), mounted[index], `${kind} survives a batched message-ID-only handoff`);
39 }
40 await harness.render([confirmed], { tabId: "identity-dom", localSubmissions: [{ ...pending, localId: "another", submissionId: "send-again", sequence: 1 }], localSubmissionSendRevision: 2, running: true });
41 await harness.settle();
42 assert.equal(harness.container.textContent?.split("unique question").length, 3, "same text sent twice remains visible twice");
43 await harness.render([
44 { ...confirmed, id: "m:first-shared", messageId: "first-shared", submissionId: "shared" },
45 { ...confirmed, id: "m:second-shared", messageId: "second-shared", submissionId: "shared" },
46 ], { tabId: "identity-dom", localSubmissions: [], localSubmissionSendRevision: 2, running: false });
47 await harness.settle();
48 assert.equal(harness.container.querySelectorAll(selector).length, 2, "different durable messages never share a DOM key even when submission ids collide");
49 const oldPage: Item = { kind: "user", id: "m:old-page", messageId: "old-page", text: "old page question" };
50 const pendingLatest = { ...pending, text: "latest pending question", submissionId: "latest-pending", localId: "latest-local" };
51 await harness.render([oldPage], { tabId: "identity-dom", localSubmissions: [pendingLatest], localSubmissionSendRevision: 3,
52 hasNewerHistory: true, running: true });
53 await harness.settle();
54 assert.equal(harness.container.textContent?.includes("latest pending question"), false, "reading an old page does not insert the latest local echo");
55 await harness.render([{ kind: "user", id: "m:latest", messageId: "latest", submissionId: "latest-pending", text: "latest pending question" }],
56 { tabId: "identity-dom", localSubmissions: [], localSubmissionSendRevision: 3, hasNewerHistory: false, running: false });
57 await harness.settle();
58 assert.equal(harness.container.textContent?.split("latest pending question").length, 2, "returning to latest renders one canonical message");
59 for (const state of ["timed_out", "cancelled", "completed"] as const) {
60 const shell: Item = { kind: "tool", id: state, name: "bash", args: '{"command":"echo test"}', output: "test", status: "done",
61 execution: { kind: "shell", supportsAndAnd: true, state, exitCode: 0 } };
62 await harness.render([shell], { tabId: "identity-dom", geometrySessionKey: state, localSubmissions: [pending] });
63 await harness.settle();
64 const row = harness.container.querySelector<HTMLElement>(".chat-tool")!;
65 const dot = row.querySelector(".dsh-StateDot-dot")?.getAttribute("data-state");
66 assert.equal(dot, state === "completed" ? "done" : state === "cancelled" ? "warning" : "error");
67 await act(async () => row.querySelector<HTMLElement>("[data-disclosure-row]")!.click());
68 await harness.settle();
69 assert.equal(row.dataset.state, state === "completed" ? "done" : state === "cancelled" ? "stopped" : "error");
70 assert.ok(row.querySelector("[data-terminal]"), "structured shell detail is mounted");
71 const dots = [...row.querySelectorAll(".dsh-StateDot-dot")];
72 assert.equal(dots.length, 1, "expanded disclosure replaces its dot with a chevron");
73 assert.ok(dots.every(node => node.getAttribute("data-state") === dot));
74 }
75 console.log("DOM: same-frame identity dedup and shared shell presentation passed");
76 } finally { await harness.unmount(); await harness.close(); }
77
77 lines Plain Text