返回 DeepSeek-Reasonix
todoSessionSwitchScenario.ts
根目录 / desktop / frontend / src / test-support / todoSessionSwitchScenario.ts
1 import { act } from "react";
2 import type { useController } from "../lib/useController";
3 import { runtimeStateStore, type RuntimeState } from "../lib/runtimeStateStore";
4 import type { Meta } from "../lib/types";
5 import type { DesktopHostStub } from "../__tests__/desktopHostStub";
6
7 type Controller = ReturnType<typeof useController>;
8
9 export async function runTodoSessionSwitchScenario(options: {
10 controller: () => Controller | undefined;
11 desktopStub: DesktopHostStub;
12 currentSessionPath: () => string;
13 meta: (overrides: Partial<Meta>) => Meta;
14 equal: (actual: unknown, expected: unknown, label: string) => void;
15 }): Promise<void> {
16 const { controller, desktopStub, currentSessionPath, meta, equal } = options;
17 const pausedTodos = [{ content: "Finish session A", status: "in_progress" }];
18 const sessionA = "/sessions/todo-a.jsonl", sessionB = "/sessions/todo-b.jsonl";
19 desktopStub.commands.MetaForTab = async () => meta({
20 sessionPath: currentSessionPath(), sessionGeneration: 1,
21 canonicalTodos: currentSessionPath() === sessionA ? pausedTodos : [],
22 });
23 let revision = 0;
24 const publish = (sessionPath: string, todos: typeof pausedTodos) => {
25 const state: RuntimeState = {
26 schemaVersion: 1, runtimeEpoch: "todo-runtime", activityRevision: 1, revision: ++revision,
27 phase: "idle", running: false, turnId: "paused-turn", turnStatus: "interrupted", turnEventSeq: 1,
28 pendingPrompt: false, cancelRequested: false, cancellable: false, backgroundJobs: 0, activity: "", todos,
29 };
30 runtimeStateStore.commit({ epoch: "todo-app", revision, topics: [], sessions: [{
31 tabId: "tab-switch", scope: "project", workspaceRoot: "/repo", topicId: "topic-a",
32 sessionPath, sessionGeneration: 1, open: true, remote: false, freshness: "synced", state,
33 }] });
34 };
35 await act(async () => { const navigation = controller()?.resumeSession(sessionA, "tab-switch"); await navigation?.surfaceReady; publish(sessionA, pausedTodos); });
36 equal(controller()?.state.meta?.canonicalTodos?.[0]?.content, "Finish session A", "paused A owns its unfinished todo");
37 await act(async () => { const navigation = controller()?.resumeSession(sessionB, "tab-switch"); await navigation?.surfaceReady; });
38 equal(controller()?.state.meta?.canonicalTodos?.length, 0, "B stays empty while A's runtime snapshot is still current in the feed");
39 await act(async () => { publish(sessionB, []); const navigation = controller()?.resumeSession(sessionA, "tab-switch"); await navigation?.surfaceReady; });
40 equal(controller()?.state.meta?.canonicalTodos?.[0]?.content, "Finish session A", "returning to A cannot be cleared by B's delayed empty snapshot");
41 await act(async () => { publish(sessionB, []); });
42 equal(controller()?.state.meta?.canonicalTodos?.[0]?.content, "Finish session A", "late B publication after the return to A cannot erase A's todo");
43 await act(async () => { publish(sessionA, [{ content: "Finish session A", status: "completed" }]); });
44 equal(controller()?.state.meta?.canonicalTodos?.[0]?.status, "completed", "a matching runtime snapshot still publishes live todo progress");
45 await act(async () => { publish(sessionA, []); });
46 equal(controller()?.state.meta?.canonicalTodos?.length, 0, "an authoritative empty list for the same session still clears todos");
47 }
48
48 lines TYPESCRIPT