返回 DeepSeek-Reasonix
trash-confirmation-generation.test.tsx
根目录 / desktop / frontend / src / __tests__ / trash-confirmation-generation.test.tsx
1 import { registerHooks } from "node:module";
2 registerHooks({ resolve(specifier, context, nextResolve) { return specifier.endsWith(".svg") ? nextResolve("./asset-stub-for-tests.ts", { ...context, parentURL: import.meta.url }) : nextResolve(specifier, context); } });
3 import assert from "node:assert/strict";
4 import { managementDom } from "../test-support/managementDom";
5 import type { SessionLifecycleRequest } from "../generated/desktopContract.generated";
6 import { installDesktopHostStub } from "./desktopHostStub";
7 const dom = managementDom();
8 const { default: React, act } = await import("react");
9 const { createRoot } = await import("react-dom/client");
10 const { LocaleProvider } = await import("../lib/i18n");
11 const { ArchivedSessionsList } = await import("../components/ArchivedSessionsList");
12 type Row = { id: string; ref: { hostId: string; sessionId: string }; title: string; workspaceId: string; workspaceTitle: string; archivedAt: number; health: string; canPreview: boolean; canRestore: boolean; canPurge: boolean };
13 const row = (id: string): Row => ({ id, ref: { hostId: "local", sessionId: id }, title: id, workspaceId: "global", workspaceTitle: "Global", archivedAt: 0, health: "ready", canPreview: true, canRestore: true, canPurge: true });
14
15 let generation = 7;
16 let rows = [row("victim")];
17 const requests: SessionLifecycleRequest[] = [];
18 let networkFailure = false;
19 let latePreview: (() => void) | undefined;
20 let holdPreview = false;
21 const host = installDesktopHostStub({
22 ListTrashEntries: async () => ({items: rows, generation}),
23 ReadSessionHistory: async () => holdPreview
24 ? new Promise(resolve => { latePreview = () => resolve({messages:[{role:"user", content:"late private preview"}]}); })
25 : { messages: [{role:"user", content:"preview"}] },
26 ApplySessionLifecycle: async (request: SessionLifecycleRequest) => {
27 requests.push(structuredClone(request));
28 if (networkFailure) throw new Error("network outcome unknown");
29 return {operationId: request.operationId, generation, committed: true, items: request.targets.map(target => ({target, committed:true, retryable:false}))};
30 },
31 });
32 const root = createRoot(document.getElementById("root")!);
33 const button = (text: string) => Array.from(document.querySelectorAll<HTMLButtonElement>("button")).find(node => node.textContent?.trim() === text)!;
34 const startDelete = async () => {
35 await act(async () => (document.querySelector('.archived-sessions__open') as HTMLButtonElement).click());
36 await act(async () => (document.querySelector('.archived-sessions__session-menu') as HTMLButtonElement).click());
37 await act(async () => (document.querySelector('[role="menuitem"]') as HTMLButtonElement).click());
38 };
39 await act(async () => root.render(<LocaleProvider><ArchivedSessionsList active={true} onOpenSession={async()=>{}} /></LocaleProvider>));
40 await startDelete();
41 assert.ok(document.querySelector('[role="dialog"]'));
42 // Another client restores and rearchives this same session while confirmation is open.
43 generation = 9;
44 await act(async () => host.emit("project-tree:changed"));
45 await act(async () => button("Permanently delete").click());
46 assert.equal(requests[0].expectedGeneration, 7, "confirmation retains original generation");
47
48 // A background refresh must not remove an unknown-result request or its retry.
49 networkFailure = true;
50 await startDelete();
51 await act(async () => button("Permanently delete").click());
52 const unknown = structuredClone(requests.at(-1));
53 generation = 12;
54 await act(async () => host.emit("project-tree:changed"));
55 assert.ok(button("Retry failed items"), "refresh preserves retry control");
56 await act(async () => button("Retry failed items").click());
57 assert.deepEqual(requests.at(-1), unknown, "retry preserves the complete original request");
58
59 // Invalidate an outstanding preview when its row becomes unavailable.
60 holdPreview = true;
61 act(() => (document.querySelector('.archived-sessions__open') as HTMLButtonElement).click());
62 rows = [{...row("victim"), canPreview:false, canRestore:false}];
63 await act(async () => host.emit("project-tree:changed"));
64 assert.equal(document.querySelector('.archived-sessions__preview-heading'), null, "unavailable preview content is cleared");
65 latePreview?.();
66 await Promise.resolve();
67 assert.ok(!document.body.textContent?.includes("late private preview"));
68
69 // Cancelling and leaving the page never submits the captured request.
70 const beforeCancel = requests.length;
71 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
72 await act(async () => button("Cancel").click());
73 assert.equal(requests.length, beforeCancel);
74 // Replacing an open confirmation cancels its old request; only the replacement submits.
75 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
76 generation = 15;
77 await act(async () => host.emit("project-tree:changed"));
78 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
79 assert.equal(requests.length, beforeCancel);
80 await act(async () => button("Permanently delete").click());
81 assert.equal(requests.length, beforeCancel + 1);
82 assert.equal(requests.at(-1)?.expectedGeneration, 15);
83 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
84 await act(async () => root.render(<LocaleProvider><ArchivedSessionsList active={false} onOpenSession={async()=>{}} /></LocaleProvider>));
85 assert.equal(document.querySelector('[role="dialog"]'), null);
86 assert.equal(requests.length, beforeCancel + 1);
87 await act(async () => root.unmount());
88 dom.window.close();
89 console.log("PASS frozen confirmation, unchanged unknown-result retry, refresh invalidation and cancelled confirmation");
90
90 lines Plain Text