返回 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 const host = installDesktopHostStub({
21 ListTrashEntries: async () => ({items: rows, generation}),
22 ReadSessionHistory: async () => new Promise(resolve => { latePreview = () => resolve({messages:[{role:"user", content:"late private preview"}]}); }),
23 ApplySessionLifecycle: async (request: SessionLifecycleRequest) => {
24 requests.push(structuredClone(request));
25 if (networkFailure) throw new Error("network outcome unknown");
26 return {operationId: request.operationId, generation, committed: true, items: request.targets.map(target => ({target, committed:true, retryable:false}))};
27 },
28 });
29 const root = createRoot(document.getElementById("root")!);
30 const button = (text: string) => Array.from(document.querySelectorAll<HTMLButtonElement>("button")).find(node => node.textContent?.trim() === text)!;
31 await act(async () => root.render(<LocaleProvider><ArchivedSessionsList active={true} onOpenSession={async()=>{}} /></LocaleProvider>));
32 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
33 assert.ok(document.querySelector('[role="dialog"]'));
34 // Another client restores and rearchives this same session while confirmation is open.
35 generation = 9;
36 await act(async () => host.emit("project-tree:changed"));
37 await act(async () => button("Permanently delete").click());
38 assert.equal(requests[0].expectedGeneration, 7, "confirmation retains original generation");
39
40 // A background refresh must not remove an unknown-result request or its retry.
41 networkFailure = true;
42 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
43 await act(async () => button("Permanently delete").click());
44 const unknown = structuredClone(requests.at(-1));
45 generation = 12;
46 await act(async () => host.emit("project-tree:changed"));
47 assert.ok(button("Retry failed items"), "refresh preserves retry control");
48 await act(async () => button("Retry failed items").click());
49 assert.deepEqual(requests.at(-1), unknown, "retry preserves the complete original request");
50
51 // Invalidate an outstanding preview when its row becomes unavailable.
52 await act(async () => (document.querySelector('.archived-sessions__open') as HTMLButtonElement).click());
53 rows = [{...row("victim"), canPreview:false, canRestore:false}];
54 await act(async () => host.emit("project-tree:changed"));
55 assert.equal(document.querySelector('.archived-sessions__preview'), null);
56 await act(async () => latePreview?.());
57 assert.ok(!document.body.textContent?.includes("late private preview"));
58
59 // Cancelling and leaving the page never submits the captured request.
60 const beforeCancel = requests.length;
61 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
62 await act(async () => button("Cancel").click());
63 assert.equal(requests.length, beforeCancel);
64 // Replacing an open confirmation cancels its old request; only the replacement submits.
65 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
66 generation = 15;
67 await act(async () => host.emit("project-tree:changed"));
68 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
69 assert.equal(requests.length, beforeCancel);
70 await act(async () => button("Permanently delete").click());
71 assert.equal(requests.length, beforeCancel + 1);
72 assert.equal(requests.at(-1)?.expectedGeneration, 15);
73 await act(async () => (document.querySelector('.archived-sessions__delete') as HTMLButtonElement).click());
74 await act(async () => root.render(<LocaleProvider><ArchivedSessionsList active={false} onOpenSession={async()=>{}} /></LocaleProvider>));
75 assert.equal(document.querySelector('[role="dialog"]'), null);
76 assert.equal(requests.length, beforeCancel + 1);
77 await act(async () => root.unmount());
78 dom.window.close();
79 console.log("PASS frozen confirmation, unchanged unknown-result retry, refresh invalidation and cancelled confirmation");
80
80 lines Plain Text