返回 DeepSeek-Reasonix
session-composer-wiring.test.tsx
根目录 / desktop / frontend / src / __tests__ / session-composer-wiring.test.tsx
1 import assert from "node:assert/strict";
2 import React, { act } from "react";
3 import { createRoot } from "react-dom/client";
4 import { JSDOM } from "jsdom";
5 import { installDesktopHostStub } from "./desktopHostStub";
6 import { makeSessionUIMock } from "../lib/sessionUIMock";
7 import { flushAllSessionComposers, resumeSessionComposerEditing, sendPersistedComposer, useSessionComposerPersistence } from "../lib/sessionComposerPersistence";
8
9 const dom = new JSDOM("<div id='root'></div>",{url:"http://localhost"});
10 Object.assign(globalThis,{window:dom.window,document:dom.window.document,IS_REACT_ACT_ENVIRONMENT:true});
11 const backend=makeSessionUIMock(async()=>{});
12 const previews:string[]=[];
13 installDesktopHostStub({...backend,AttachmentDataURLForComposerTarget:async (_target:unknown,path:string)=>{previews.push(path);return "data:image/png;base64,aW1hZ2U=";}});
14 let editor!:ReturnType<typeof useSessionComposerPersistence>;
15 function Probe({id}:{id:string}) {editor=useSessionComposerPersistence({hostId:"local",sessionId:id},id);return null;}
16 const root=createRoot(document.getElementById("root")!);
17 async function paint(id:string){await act(async()=>{root.render(React.createElement(Probe,{id}));await new Promise(resolve=>setTimeout(resolve,0));});}
18 async function flush(){await act(async()=>{await flushAllSessionComposers();resumeSessionComposerEditing();});}
19 try {
20 await paint("wiring");
21 const owner=editor.target!;
22 await act(async()=>owner.onPatch!(owner.draftId,owner.generation,{text:"retained",attachments:[{path:"draft:temporary",draftId:"temporary",recoveryPath:".reasonix/attachments/kept.png",displayName:"kept.png",previewUrl:"blob:ephemeral"}]}));
23 await flush();
24 const saved=JSON.parse((await backend.GetSessionComposerState({hostId:"local",sessionId:"wiring"})).contentJson);
25 assert.equal(saved.attachments[0].path,".reasonix/attachments/kept.png");
26 assert.equal(saved.attachments[0].draftId,undefined);
27 assert.equal(saved.attachments[0].previewUrl,undefined);
28 await backend.SaveSessionComposerState({ref:{hostId:"local",sessionId:"wiring-restored"},expectedRevision:"0",contentJson:JSON.stringify(saved),contentVersion:1});
29 await paint("wiring-restored");
30 assert.equal(editor.target!.initial.attachments[0].previewUrl,"data:image/png;base64,aW1hZ2U=");
31 assert.ok(previews.includes(".reasonix/attachments/kept.png"));
32 await paint("wiring");
33
34 for (const code of ["inbox_capacity_items","inbox_capacity_bytes","inbox_item_too_large","channel_read_only","workspace_starting","workspace_start_failed","image_attachment_unreadable"]) {
35 await act(async()=>{await assert.rejects(sendPersistedComposer("wiring","retained","retained",code,async()=>{throw Error(`reasonix_error:${code}`);}));});
36 assert.equal(editor.blocked,false,`${code} is a definite rejection, not an unknown execution`);
37 assert.equal(editor.target!.initial.text,"retained");
38 }
39 await act(async()=>{await assert.rejects(sendPersistedComposer("wiring","retained","retained","lost-followup",async()=>{throw Error("lost RPC");},undefined,undefined,"guidance"));});
40 assert.equal(editor.blocked,true);
41 await act(async()=>editor.settleSubmission("lost-followup"));
42 assert.equal(editor.blocked,false,"confirmed inbox receipt must settle the saved input as well");
43 assert.equal(editor.target!.initial.text,"");
44 const nextOwner=editor.target!;
45 await act(async()=>nextOwner.onPatch!(nextOwner.draftId,nextOwner.generation,{text:"new input after recovery"}));
46 await act(async()=>editor.settleSubmission("lost-followup"));
47 assert.equal(editor.target!.initial.text,"new input after recovery","a repeated follow-up check cannot clear newer unsaved input");
48 await flush();
49 console.log("PASS persistent composer wiring: durable image source, explicit rejection and follow-up receipt settlement");
50 } finally {await act(async()=>root.unmount());resumeSessionComposerEditing();dom.window.close();}
51
51 lines Plain Text