返回 DeepSeek-Reasonix
remoteRuntimeReconciliationCases.tsx
根目录 / desktop / frontend / src / __tests__ / helpers / remoteRuntimeReconciliationCases.tsx
1 import React, { act } from "react";
2 import { createRoot } from "react-dom/client";
3 import { RemoteSessionSurface } from "../../components/RemoteSessionSurface";
4 import { LocaleProvider } from "../../lib/i18n";
5 import { useRemoteSession, type RemoteSessionApi } from "../../lib/useRemoteSession";
6 import type { AppBindings } from "../../lib/bridge";
7 import type { TabMeta } from "../../lib/types";
8
9 export async function runRemoteRuntimeCases({ commands, emitRemote: __emitMockRemoteTab, remoteTab, ok, tape, flush, setSnapshotHistory }: {
10 commands: AppBindings;
11 emitRemote: (tabId: string, channel: "state" | "event", payload: unknown) => void;
12 remoteTab: TabMeta;
13 ok: (value: boolean, label: string) => void;
14 tape: string[];
15 flush: () => Promise<void>;
16 setSnapshotHistory: (history: unknown[]) => void;
17 }) {
18 // Only the ordered Follow stream settles transcript activity. Ancillary
19 // status cannot replace its content or settle a newer turn.
20 const { runtimeStateStore } = await import("../../lib/runtimeStateStore");
21 let runtimeProbe: RemoteSessionApi;
22 function RuntimeProbe() {
23 runtimeProbe = useRemoteSession("tab-runtime", "ready", "/runtime-session");
24 return <RemoteSessionSurface tab={{ ...remoteTab, id: "tab-runtime" }} session={runtimeProbe} />;
25 }
26 const runtimeNode = document.createElement("div");
27 document.body.append(runtimeNode);
28 const runtimeRoot = createRoot(runtimeNode);
29 await act(async () => { runtimeRoot.render(<LocaleProvider><RuntimeProbe /></LocaleProvider>); await flush(); });
30 let runtimeRevision = 0;
31 async function publishRuntime(phase: "idle" | "executing" | "finishing", turnId = "lost-turn", freshness: "synced" | "unknown" = "synced", sessionPath = "/runtime-session") {
32 await act(async () => {
33 runtimeStateStore.commit({ epoch: "runtime-app", revision: ++runtimeRevision, topics: [], sessions: [{
34 tabId: "tab-runtime", scope: "project", workspaceRoot: "~/app", topicId: "", sessionPath,
35 sessionGeneration: 1, open: true, remote: true, freshness,
36 state: { schemaVersion: 1, runtimeEpoch: "runtime-controller", revision: runtimeRevision,
37 phase, running: phase !== "idle", turnId, turnStatus: phase === "idle" ? "completed" : "in_progress",
38 turnEventSeq: runtimeRevision, pendingPrompt: false, cancelRequested: false,
39 cancellable: phase === "executing", backgroundJobs: 0, activity: "" },
40 }] });
41 await flush();
42 });
43 }
44 await act(async () => {
45 __emitMockRemoteTab("tab-runtime", "event", { kind: "turn_started", turnId: "lost-turn" });
46 __emitMockRemoteTab("tab-runtime", "event", { kind: "text", messageId: "runtime-answer", text: "partial runtime answer" });
47 await flush();
48 });
49 const beforeRuntimeHistory = tape.filter(entry => entry === "snapshot:tab-runtime").length;
50 await publishRuntime("idle", "lost-turn", "unknown");
51 ok(runtimeProbe!.transcript.running, "unknown runtime does not settle a stream");
52 await publishRuntime("finishing");
53 ok(runtimeProbe!.transcript.running, "finishing does not settle the transcript before completion");
54 await publishRuntime("idle", "previous-turn");
55 ok(runtimeProbe!.transcript.running, "idle evidence for a previous turn cannot settle the current turn");
56 await publishRuntime("idle", "lost-turn", "synced", "/other-session");
57 ok(runtimeProbe!.transcript.running, "another selected session cannot settle the current transcript");
58 await act(async () => {
59 __emitMockRemoteTab("tab-runtime", "event", { kind: "retrying", retryAttempt: 2, retryMax: 4 });
60 await flush();
61 });
62 ok(runtimeProbe!.transcript.retry !== undefined, "lost-completion fixture includes an active retry");
63 setSnapshotHistory([{ role: "assistant", content: "obsolete metadata history" }]);
64 await publishRuntime("idle");
65 ok(runtimeProbe!.transcript.running && runtimeProbe!.transcript.live?.text === "partial runtime answer",
66 "ancillary idle cannot settle or erase an active Follow stream");
67 ok(tape.filter(entry => entry === "snapshot:tab-runtime").length === beforeRuntimeHistory,
68 "runtime metadata never initiates a completion history rebase");
69 await act(async () => {
70 __emitMockRemoteTab("tab-runtime", "event", { kind: "message", messageId: "runtime-answer", text: "complete durable runtime answer" });
71 __emitMockRemoteTab("tab-runtime", "event", { kind: "turn_done", turnId: "lost-turn" });
72 await flush();
73 });
74 ok(!runtimeProbe!.transcript.running && !runtimeProbe!.transcript.turnActive && runtimeProbe!.transcript.retry === undefined,
75 "Follow completion settles activity and retry together");
76 ok(runtimeNode.textContent?.includes("complete durable runtime answer") === true,
77 "committed final content remains visible after Follow completion");
78 await act(async () => {
79 __emitMockRemoteTab("tab-runtime", "event", { kind: "turn_started", turnId: "next-turn" });
80 __emitMockRemoteTab("tab-runtime", "event", { kind: "text", text: "next live answer" });
81 await flush();
82 });
83 await publishRuntime("idle", "lost-turn");
84 ok(runtimeProbe!.transcript.running && runtimeProbe!.transcript.live?.text === "next live answer",
85 "old idle cannot settle the next Follow turn");
86 const beforeStatus = tape.filter(entry => entry === "snapshot:tab-runtime").length;
87 await act(async () => { await commands.RemoteTabStatus("tab-runtime"); await flush(); });
88 ok(tape.filter(entry => entry === "snapshot:tab-runtime").length === beforeStatus
89 && !runtimeNode.textContent?.includes("obsolete metadata history"),
90 "late status cannot replace the transcript with unrelated history");
91 await act(async () => runtimeRoot.unmount());
92 runtimeNode.remove();
93 }
94
94 lines Plain Text