返回 DeepSeek-Reasonix
conversation-projection.test.ts
根目录 / desktop / frontend / src / __tests__ / conversation-projection.test.ts
1 import assert from "node:assert/strict";
2 import { createElement } from "react";
3 import { renderToStaticMarkup } from "react-dom/server";
4 import { WorkspaceDockRegion } from "../app-shell/WorkspaceDockRegion";
5 import type { Translator } from "../lib/i18n";
6 import { projectConversation, projectConversationLayout, projectNavigationSurfaceTarget } from "../app-runtime/conversationProjection";
7 import { initialState } from "../lib/useController";
8 import type { RemoteSessionApi } from "../lib/useRemoteSession";
9 import type { BackgroundRuntimeView } from "../lib/types";
10
11 const local = { ...initialState, running: true, activeTurnId: "local-turn", turnTokens: 999,
12 turnStartAt: 111, sessionTokens: 999, sessionCost: 999, sessionCurrency: "USD",
13 context: { used: 999, window: 999, sessionTokens: 999 }, balance: { available: true, display: "LOCAL" },
14 meta: { ready: true, eventChannel: "local-events", cwd: "local-cwd", label: "local-model", workspaceName: "local-project", gitBranch: "local-branch",
15 imageInputEnabled: true, visionFallbackEnabled: true, pinnedFiles: [{ path: "local-file", sizeBytes: 10, tokenEstimate: 5 }] },
16 } as typeof initialState;
17 const remote: Pick<RemoteSessionApi, "transcript" | "running" | "modelLabel" | "commands" | "composerProfile" | "goalView" | "goalRuntime" | "effort"> = {
18 transcript: { ...initialState, turnTokens: 7, turnStartAt: 222, sessionTokens: 8, sessionCost: 2, sessionCurrency: "CNY" },
19 running: false, modelLabel: "remote-model", commands: [],
20 goalView: { id: "goal-remote", revision: 3, objective: "resume remote work", phase: "active", maxGoalRounds: null,
21 roundsStarted: 7, createdAt: "2026-09-13T10:00:00Z", updatedAt: "2026-09-13T10:10:00Z", activation: "disarmed" },
22 };
23 const tab = { id: "remote", label: "remote-tab", remote: { hostId: "fixture", workspace: "remote-cwd" }, workspaceName: "remote-project" };
24 const background = [{ id: "local-runtime" }] as unknown as BackgroundRuntimeView[];
25 const view = projectConversation({ local, remote, tab, activeTabId: tab.id, backgroundRuntimes: background, connectingLabel: "connecting" });
26 assert.equal(view.runtime, remote.transcript, "projection shares the canonical state and message arrays");
27 assert.equal(view.context.items, remote.transcript.items);
28 assert.equal(view.context.tabId, undefined, "remote context cannot fetch local telemetry");
29 assert.equal(view.composer.modelLabel, "remote-model");
30 assert.equal(view.composer.cwd, "remote-cwd");
31 assert.equal(view.composer.turnTokens, 7);
32 assert.equal(view.composer.turnStartAt, 222);
33 assert.equal(view.composer.goalView?.id, "goal-remote");
34 assert.equal(view.composer.currency, "CNY");
35 assert.equal(view.composer.pinnedFiles, undefined);
36 assert.equal(view.composer.attachmentInputEnabled, false);
37 assert.equal(view.composer.imageInputEnabled, false);
38 assert.equal(view.composer.imageUnderstandingEnabled, false);
39 assert.equal(view.composer.localDurableGuidance, false);
40 assert.equal(view.composer.turnId, undefined);
41 assert.equal(view.composer.context, remote.transcript.context);
42 assert.equal(view.composer.balance, remote.transcript.balance);
43 assert.equal(view.status.context, remote.transcript.context);
44 assert.equal(view.status.balance, remote.transcript.balance);
45 assert.deepEqual(view.status.backgroundRuntimes, []);
46 assert.equal(view.status.gitBranch, undefined);
47 assert.equal(view.status.workspaceName, "remote-project");
48 assert.equal(view.status.cost, 2);
49 assert.equal(view.status.sessionTokens, 8);
50 const localView = projectConversation({ local, activeTabId: "local", backgroundRuntimes: background, connectingLabel: "connecting" });
51 assert.equal(localView.runtime, local);
52 assert.equal(localView.status.backgroundRuntimes, background);
53 assert.equal(localView.composer.attachmentInputEnabled, true);
54 assert.equal(localView.composer.localDurableGuidance, true);
55 assert.equal(localView.context.tabId, "local");
56 const readableBeforeRuntime = projectNavigationSurfaceTarget({
57 activeTabId: "local",
58 sessionKey: "local:session",
59 local: {
60 ...local,
61 meta: { ...local.meta!, ready: false, runtime: { phase: "starting", epoch: "runtime-1" } },
62 backendActivationPending: true,
63 hydrating: false,
64 hydrateError: undefined,
65 },
66 });
67 assert.equal(readableBeforeRuntime.ready, true, "local navigation paint is released by readable history rather than runtime readiness");
68 assert.equal(readableBeforeRuntime.backendActivationPending, false, "local runtime activation does not block transcript paint");
69 const localHistoryPending = projectNavigationSurfaceTarget({
70 activeTabId: "local",
71 sessionKey: "local:session",
72 local: { ...local, backendActivationPending: true, hydrating: true, hydrateError: undefined },
73 });
74 assert.equal(localHistoryPending.ready, false, "local cache miss keeps only the target history skeleton pending");
75 for (const chatVisible of [false, true]) for (const localToolsEnabled of [false, true]) for (const dockMode of ["files", "changed", "remote", "context"]) {
76 const layout = projectConversationLayout({ chatVisible, localToolsEnabled, dockMode, dockRenderable: true,
77 dockGridOpen: true, dockOverlay: true, dockOpen: true, dockMaximized: true, terminalOpen: true });
78 const permitted = chatVisible && (localToolsEnabled || !["files", "changed"].includes(dockMode));
79 assert.equal(layout.dockVisible, permitted);
80 assert.equal(layout.dockGridOpen, permitted);
81 assert.equal(layout.dockOverlay, permitted);
82 assert.equal(layout.terminalOpen, chatVisible && localToolsEnabled);
83 assert.equal(layout.dockMaximized, chatVisible, "automation masks stored maximization without modifying the preference");
84 if (!localToolsEnabled && (dockMode === "files" || dockMode === "changed")) {
85 const noop = () => {};
86 const markup = renderToStaticMarkup(createElement(WorkspaceDockRegion, {
87 visible: layout.dockVisible, overlay: layout.dockOverlay, mode: dockMode,
88 showContext: true, t: ((key: string) => key) as Translator,
89 onPickEntry: noop, remote: { onClose: noop }, context: view.context,
90 workspaceKey: "fixture", workspace: { open: layout.dockVisible, maximized: false, onClose: noop, onToggleMaximized: noop },
91 }));
92 assert.equal(markup, "", "actual dock region never mounts local Files/Changes for a remote source");
93 }
94 }
95 console.log("conversation projection: shared source identity, remote telemetry isolation and local-tool layout policy passed");
96
96 lines TYPESCRIPT