返回 DeepSeek-Reasonix
session-draft-presentation.test.tsx
根目录 / desktop / frontend / src / __tests__ / session-draft-presentation.test.tsx
1 import assert from "node:assert/strict";
2 import { register } from "node:module";
3 import React, { act } from "react";
4 import { createRoot } from "react-dom/client";
5 import { JSDOM } from "jsdom";
6
7 import type { SessionDraftSurface } from "../app-runtime/useSessionDraftSurface";
8 import { initialState } from "../lib/useController";
9 import { LocaleProvider, type Translator } from "../lib/i18n";
10 import { creationHeroVisible } from "../app-shell/draftPresentation";
11
12 register(new URL("../../scripts/svg-loader.mjs", import.meta.url));
13 const { ChatPaneRegion } = await import("../app-shell/ChatPaneRegion");
14 const { DraftTopicbarActions } = await import("../app-shell/DraftTopicbarActions");
15
16 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost" });
17 Object.assign(globalThis, {
18 window: dom.window,
19 document: dom.window.document,
20 localStorage: dom.window.localStorage,
21 IS_REACT_ACT_ENVIRONMENT: true,
22 });
23
24 const surface: SessionDraftSurface = {
25 kind: "draft",
26 draft: {
27 id: "draft-project",
28 workspaceId: "workspace-project",
29 scope: "project",
30 workspaceRoot: "/workspace/project",
31 revision: 1,
32 contentJson: "{}",
33 settings: { model: "fixture/model", mode: "normal", toolApprovalMode: "ask", disabledMcp: {}, mcpOrder: [] },
34 status: "active",
35 updatedAt: 1,
36 },
37 content: { text: "", invocations: [], attachments: [], workspaceRefs: [], pastedBlocks: [], openPastedLabels: [], sessionRefs: [], selectedTextRefs: [] },
38 settings: { model: "fixture/model", mode: "normal", toolApprovalMode: "ask", disabledMcp: {}, mcpOrder: [] },
39 commands: [],
40 servers: [],
41 generation: 1,
42 editVersion: 0,
43 pendingTasks: 0,
44 preparingSubmission: false,
45 saveState: "saved",
46 };
47
48 const noop = () => {};
49 for (const emptyHero of [true, false]) {
50 assert.equal(creationHeroVisible(null, emptyHero), emptyHero, "formal sessions keep their own layout");
51 assert.equal(creationHeroVisible(surface, emptyHero), true, "healthy drafts own the welcome layout");
52 for (const failure of [
53 { saveState: "conflict" as const },
54 { saveState: "error" as const },
55 { taskError: "Attachment failed" },
56 ]) {
57 assert.equal(creationHeroVisible({ ...surface, ...failure }, emptyHero), false,
58 "draft recovery is never collapsed by a hidden empty formal session");
59 }
60 }
61 const commands = { onPrompt: noop, onFork: noop, onLoadOlderHistory: async () => false, onLoadNewerHistory: async () => false, onSurfacePaintReady: noop };
62 const transcript = {
63 state: { ...initialState, meta: { ready: true, eventChannel: "fixture" } },
64 items: [], tabId: undefined, geometrySessionKey: "draft", footerHeight: 0,
65 invocationMetadata: undefined, surfaceCommitToken: undefined, liveStore: undefined,
66 transcriptHydrating: false, navigationDataReady: true, readOnly: false, controllerReady: false,
67 hydratePlaceholderActive: false, clearContextPending: false, availability: { kind: "ready", source: "history" } as const,
68 rewind: { stateActive: false, committing: false },
69 };
70 const draftActions = {
71 surface,
72 onUseSaved: noop,
73 onKeepLocal: noop,
74 onRetrySave: noop,
75 onDismissTaskError: noop,
76 onResume: noop,
77 onOpenSession: noop,
78 onCheckSubmission: noop,
79 };
80
81 const root = createRoot(document.getElementById("root")!);
82 try {
83 await act(async () => root.render(<LocaleProvider><ChatPaneRegion
84 transitioning={false} t={((key: string) => key) as Translator} imDetail={null} remote={undefined}
85 draft={draftActions} transcript={transcript} onRetryHistory={async () => {}} commands={commands}
86 /></LocaleProvider>));
87 assert.ok(document.querySelector(".main--draft-landing"), "a healthy draft uses the clean new-session landing surface");
88 assert.equal(document.querySelector(".session-draft-surface"), null, "healthy drafts do not render the management page");
89
90 await act(async () => root.render(<LocaleProvider><ChatPaneRegion
91 transitioning={false} t={((key: string) => key) as Translator} imDetail={null} remote={undefined}
92 draft={{ ...draftActions, surface: { ...surface, saveState: "conflict", conflict: surface.draft } }}
93 transcript={transcript} onRetryHistory={async () => {}} commands={commands}
94 /></LocaleProvider>));
95 assert.ok(document.querySelector(".session-draft-attention[role=alert]"), "a conflict keeps its recovery actions visible");
96 assert.equal(document.querySelectorAll(".session-draft-attention button").length, 2, "both conflict resolutions remain available");
97
98 let discarded = 0;
99 const mcpSelections: boolean[] = [];
100 const server = {
101 name: "fixture-mcp", transport: "stdio", status: "deferred", enabled: true, installed: true,
102 autoStart: true, tools: 0, toolCount: 0, prompts: 0, resources: 0, toolList: [],
103 };
104 await act(async () => root.render(<LocaleProvider><DraftTopicbarActions
105 t={((key: string) => key) as Translator}
106 draft={{ ...surface, servers: [server] }}
107 onSetMCPEnabled={(_server, enabled) => mcpSelections.push(enabled)}
108 onDiscard={() => { discarded++; }}
109 /></LocaleProvider>));
110 assert.equal(document.querySelector(".draft-topicbar-mcp summary")?.textContent, "1/1", "the compact control preserves MCP visibility");
111 await act(async () => document.querySelector<HTMLInputElement>(".draft-topicbar-mcp input")!.click());
112 await act(async () => document.querySelector<HTMLButtonElement>('button[aria-label="draft.discard"]')!.click());
113 assert.deepEqual(mcpSelections, [false], "the compact control preserves per-draft MCP selection");
114 assert.equal(discarded, 1, "the compact control preserves draft discard");
115
116 await act(async () => root.unmount());
117 console.log("PASS session draft presentation: clean landing and exceptional recovery controls");
118 } finally {
119 dom.window.close();
120 }
121
121 lines Plain Text