返回 DeepSeek-Reasonix
deferred-history-close-race.tsx
根目录 / desktop / frontend / src / __tests__ / deferred-history-close-race.tsx
1 import { act } from "react";
2 import { getTranscriptStore } from "../lib/transcriptStore";
3 import type { HistorySlice } from "../lib/types";
4 import { historySliceFromMessages } from "./mockHistorySlice";
5
6 type DeferredHistory = {
7 promise: Promise<HistorySlice>;
8 resolve: (value: HistorySlice) => void;
9 };
10
11 type OlderHistoryState = {
12 items: Array<{ kind: string; text?: string }>;
13 historyOlderLoading: boolean;
14 historyOlderError?: string;
15 };
16
17 export async function verifyStaleHistoryFingerprint({
18 olderPage,
19 loadOlderHistory,
20 historyCalls,
21 waitFor,
22 flushPromises,
23 equal,
24 getState,
25 }: {
26 olderPage: DeferredHistory;
27 loadOlderHistory: () => Promise<boolean> | undefined;
28 historyCalls: () => number;
29 waitFor: (label: string, predicate: () => boolean) => Promise<void>;
30 flushPromises: () => Promise<void>;
31 equal: (actual: unknown, expected: unknown, label: string) => void;
32 getState: () => OlderHistoryState | undefined;
33 }) {
34 let olderLoad: Promise<boolean> | undefined;
35 await act(async () => {
36 olderLoad = loadOlderHistory();
37 await flushPromises();
38 });
39 await waitFor("tab-l older page request", () => historyCalls() === 2);
40 olderPage.resolve({
41 ...historySliceFromMessages(
42 "tab-l",
43 [{ role: "user", content: "stale older L" }],
44 { cursor: "", turns: 12 },
45 { revision: 1, digest: "digest-l-v1" },
46 ),
47 hasOlder: true,
48 stale: true,
49 nextCursor: btoa(JSON.stringify({ v: 1, before: 2 })),
50 });
51 await act(async () => {
52 await olderLoad;
53 await flushPromises();
54 });
55 const state = getState();
56 equal(state?.items.some((item) => item.kind === "user" && item.text === "stale older L") ?? false, false, "stale older page is discarded after session fingerprint changes");
57 equal(state?.historyOlderLoading, false, "stale older page releases its loading state");
58 equal(state?.historyOlderError, "history snapshot expired", "stale older page enters the explicit retry state instead of silently auto-retrying");
59 }
60
61 export async function verifyDeferredHistoryCloseRace({
62 olderPage,
63 loadOlderHistory,
64 closeTab,
65 historyCalls,
66 waitFor,
67 flushPromises,
68 equal,
69 sessionPath,
70 }: {
71 olderPage: DeferredHistory;
72 loadOlderHistory: () => Promise<boolean> | undefined;
73 closeTab: () => Promise<boolean>;
74 historyCalls: () => number;
75 waitFor: (label: string, predicate: () => boolean) => Promise<void>;
76 flushPromises: () => Promise<void>;
77 equal: (actual: unknown, expected: unknown, label: string) => void;
78 sessionPath: string;
79 }) {
80 let closingOlderLoad: Promise<boolean> | undefined;
81 await act(async () => {
82 closingOlderLoad = loadOlderHistory();
83 await flushPromises();
84 });
85 await waitFor("tab-l closing older page request", () => historyCalls() === 3);
86
87 const transcriptStore = getTranscriptStore();
88 const originalSetPinned = transcriptStore.setPinned.bind(transcriptStore);
89 let releasedTabStateCommits = 0;
90 transcriptStore.setPinned = (tabId: string, pinned: boolean) => {
91 if (tabId === "tab-l") releasedTabStateCommits += 1;
92 originalSetPinned(tabId, pinned);
93 };
94 let tabClosed = false;
95 await act(async () => {
96 tabClosed = await closeTab();
97 await flushPromises();
98 });
99 equal(tabClosed, true, "tab-l closes while its older page is deferred");
100
101 olderPage.resolve(historySliceFromMessages(
102 "tab-l",
103 [{ role: "user", content: "late page after close" }],
104 { cursor: "", turns: 12 },
105 { revision: 2, digest: "digest-l-v2" },
106 ));
107 await act(async () => {
108 await closingOlderLoad;
109 await flushPromises();
110 });
111 transcriptStore.setPinned = originalSetPinned;
112 equal(releasedTabStateCommits, 0, "late older-page completion cannot recreate the released tab state");
113 equal(transcriptStore.isResident("tab-l", sessionPath), false, "late older-page completion cannot restore the evicted transcript");
114 }
115
115 lines Plain Text