返回 DeepSeek-Reasonix
workspace-refresh-store.test.ts
根目录 / desktop / frontend / src / __tests__ / workspace-refresh-store.test.ts
1 import assert from "node:assert/strict";
2 import {
3 acceptWorkspaceRefreshForTests,
4 activateWorkspaceRefreshScopeForTests,
5 reconcileWorkspaceRefreshForTests,
6 resetWorkspaceRefreshStoreForTests,
7 workspaceRefreshSnapshotForTests,
8 } from "../lib/workspaceRefreshStore";
9 import { createWorkspaceRefreshScheduler, type WorkspaceRefreshTimer } from "../lib/workspaceRefreshScheduler";
10 import { workspaceRefreshActions } from "../lib/workspaceRefreshInvalidation";
11 import type { WireWorkspaceChanged, WorkspaceRevisions } from "../lib/types";
12
13 const revisions = (content: number): WorkspaceRevisions => ({ content, tree: content, workingTree: content, gitMeta: 0, session: content });
14 const event = (content: number): WireWorkspaceChanged => ({
15 revisions: revisions(content), changes: [], allPaths: true, source: "filesystem", watchState: "active",
16 });
17
18 function workspaceScopesKeepIndependentRevisionBaselines() {
19 resetWorkspaceRefreshStoreForTests();
20 activateWorkspaceRefreshScopeForTests("tab", "root-a");
21 acceptWorkspaceRefreshForTests("tab", event(100));
22 assert.equal(workspaceRefreshSnapshotForTests("tab", "root-a").revisions.content, 100);
23
24 activateWorkspaceRefreshScopeForTests("tab", "root-b");
25 acceptWorkspaceRefreshForTests("tab", event(1));
26 assert.equal(workspaceRefreshSnapshotForTests("tab", "root-b").revisions.content, 1,
27 "a new root must not inherit the old root's monotonic baseline");
28 assert.equal(workspaceRefreshSnapshotForTests("tab", "root-a").revisions.content, 100);
29 }
30
31 async function reconciliationForcesVisibleResourcesWithoutMovingBackwards() {
32 resetWorkspaceRefreshStoreForTests();
33 activateWorkspaceRefreshScopeForTests("tab", "root");
34 acceptWorkspaceRefreshForTests("tab", event(5));
35 const initialSequence = workspaceRefreshSnapshotForTests("tab", "root").sequence;
36 reconcileWorkspaceRefreshForTests("tab", "root", { revisions: revisions(5), watchState: "active" });
37 assert.equal(workspaceRefreshSnapshotForTests("tab", "root").sequence, initialSequence,
38 "ordinary revision reconciliation must not reload an unchanged active workspace");
39 reconcileWorkspaceRefreshForTests("tab", "root", { revisions: revisions(5), watchState: "active" }, true);
40 const forced = workspaceRefreshSnapshotForTests("tab", "root");
41 assert.equal(forced.sequence, initialSequence + 1, "focus reconciliation must refresh unwatched external resources even at the same revision");
42 assert.equal(forced.allPaths, true);
43 assert.equal(forced.source, "reconcile");
44
45 acceptWorkspaceRefreshForTests("tab", event(6));
46 reconcileWorkspaceRefreshForTests("tab", "root", { revisions: revisions(5), watchState: "active" }, true);
47 const current = workspaceRefreshSnapshotForTests("tab", "root");
48 assert.equal(current.revisions.content, 6, "an older focus response must not overwrite a newer workspace event");
49 assert.equal(current.source, "reconcile", "the focus fallback must still invalidate visible resources after a newer event");
50 assert.equal(current.allPaths, true);
51 }
52
53 function focusReconciliationInvalidatesVisibleResourcesAtEqualRevisions() {
54 const current = revisions(5);
55 const focused: WireWorkspaceChanged = {
56 revisions: current, changes: [], allPaths: true, source: "reconcile", watchState: "active",
57 };
58 assert.deepEqual(workspaceRefreshActions(current, { ...focused, sequence: 2 }, false), {
59 forceVisible: true, content: true, tree: true, workingTree: true, gitMeta: false,
60 });
61 assert.equal(workspaceRefreshActions(current, { ...focused, sequence: 3 }, true).gitMeta, true,
62 "focus fallback refreshes history only while that resource is visible");
63
64 const ordinary: WireWorkspaceChanged = {
65 revisions: current, changes: [], allPaths: true, source: "filesystem", watchState: "active",
66 };
67 assert.deepEqual(workspaceRefreshActions(current, { ...ordinary, sequence: 4 }, true), {
68 forceVisible: false, content: false, tree: false, workingTree: false, gitMeta: false,
69 });
70 }
71
72 class FakeTimer implements WorkspaceRefreshTimer {
73 callbacks: Array<() => void> = [];
74 schedule(callback: () => void): unknown { this.callbacks.push(callback); return callback; }
75 cancel(handle: unknown): void { this.callbacks = this.callbacks.filter((callback) => callback !== handle); }
76 fire(): void { const callback = this.callbacks.shift(); assert.ok(callback); callback(); }
77 }
78
79 async function refreshSchedulerUsesTrailingQuietWindowAndBoundsConcurrency() {
80 const timer = new FakeTimer();
81 const scheduler = createWorkspaceRefreshScheduler(300, timer);
82 const runs: string[] = [];
83 let releaseFirst!: () => void;
84 const first = new Promise<void>((resolve) => { releaseFirst = resolve; });
85 let confirmTrailing!: () => void;
86 const trailingStarted = new Promise<void>((resolve) => { confirmTrailing = resolve; });
87
88 scheduler.trigger(() => { runs.push("superseded"); });
89 scheduler.trigger(async () => { runs.push("first"); await first; });
90 assert.equal(timer.callbacks.length, 1, "retriggering must reset the quiet window");
91 timer.fire();
92 await Promise.resolve();
93 assert.deepEqual(runs, ["first"]);
94
95 scheduler.trigger(() => { runs.push("trailing"); confirmTrailing(); });
96 timer.fire();
97 await Promise.resolve();
98 assert.deepEqual(runs, ["first"], "a refresh must not overlap the in-flight load");
99 releaseFirst();
100 await trailingStarted;
101 assert.deepEqual(runs, ["first", "trailing"]);
102 }
103
104 await workspaceScopesKeepIndependentRevisionBaselines();
105 await reconciliationForcesVisibleResourcesWithoutMovingBackwards();
106 focusReconciliationInvalidatesVisibleResourcesAtEqualRevisions();
107 await refreshSchedulerUsesTrailingQuietWindowAndBoundsConcurrency();
108 console.log("ok workspace refresh scope isolation and scheduling");
109
109 lines TYPESCRIPT