源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import {
|
||||
commitRouteWorkspaceSelection,
|
||||
createLatestWorkspaceCommitter,
|
||||
createRouteWorkspaceSelectionCommitter,
|
||||
createRouteRefreshLifecycle,
|
||||
planRouteConnectionGap,
|
||||
planRouteWorkspaceLoads,
|
||||
} from "../src/react-app/shell/route-refresh-control";
|
||||
|
||||
describe("createLatestWorkspaceCommitter", () => {
|
||||
test("coalesces an in-flight switching burst to the last route", async () => {
|
||||
const committed: string[] = [];
|
||||
let releaseFirst: () => void = () => undefined;
|
||||
const firstBlocked = new Promise<void>((resolve) => {
|
||||
releaseFirst = resolve;
|
||||
});
|
||||
const committer = createLatestWorkspaceCommitter(async (workspaceId) => {
|
||||
committed.push(workspaceId);
|
||||
if (workspaceId === "ws_1") await firstBlocked;
|
||||
});
|
||||
|
||||
committer.request("ws_1");
|
||||
committer.request("ws_2");
|
||||
committer.request("ws_3");
|
||||
expect(committed).toEqual(["ws_1"]);
|
||||
|
||||
releaseFirst();
|
||||
await committer.settled();
|
||||
expect(committed).toEqual(["ws_1", "ws_3"]);
|
||||
});
|
||||
|
||||
test("does not repeat the active commit when the final route returns to it", async () => {
|
||||
const committed: string[] = [];
|
||||
let releaseFirst: () => void = () => undefined;
|
||||
const firstBlocked = new Promise<void>((resolve) => {
|
||||
releaseFirst = resolve;
|
||||
});
|
||||
const committer = createLatestWorkspaceCommitter(async (workspaceId) => {
|
||||
committed.push(workspaceId);
|
||||
if (committed.length === 1) await firstBlocked;
|
||||
});
|
||||
|
||||
committer.request("ws_1");
|
||||
committer.request("ws_2");
|
||||
committer.request("ws_1");
|
||||
releaseFirst();
|
||||
await committer.settled();
|
||||
|
||||
expect(committed).toEqual(["ws_1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("route workspace selection handoff", () => {
|
||||
test("keeps the Settings destination behind an in-flight session commit", async () => {
|
||||
const committed: string[] = [];
|
||||
let releaseSessionCommit: () => void = () => undefined;
|
||||
const sessionCommitBlocked = new Promise<void>((resolve) => {
|
||||
releaseSessionCommit = resolve;
|
||||
});
|
||||
const committer = createRouteWorkspaceSelectionCommitter();
|
||||
|
||||
committer.request("ws_1", async (workspaceId) => {
|
||||
committed.push(`session:${workspaceId}:start`);
|
||||
await sessionCommitBlocked;
|
||||
committed.push(`session:${workspaceId}:end`);
|
||||
});
|
||||
committer.request("ws_2", async (workspaceId) => {
|
||||
committed.push(`session:${workspaceId}`);
|
||||
});
|
||||
committer.request("ws_2", async (workspaceId) => {
|
||||
committed.push(`settings:${workspaceId}`);
|
||||
});
|
||||
|
||||
expect(committed).toEqual(["session:ws_1:start"]);
|
||||
releaseSessionCommit();
|
||||
await committer.settled();
|
||||
|
||||
expect(committed).toEqual([
|
||||
"session:ws_1:start",
|
||||
"session:ws_1:end",
|
||||
"settings:ws_2",
|
||||
]);
|
||||
});
|
||||
|
||||
test("retries the same destination with the newer route callback after a failed commit", async () => {
|
||||
const committed: string[] = [];
|
||||
let releaseFailedCommit: () => void = () => undefined;
|
||||
const failedCommitBlocked = new Promise<void>((resolve) => {
|
||||
releaseFailedCommit = resolve;
|
||||
});
|
||||
const committer = createRouteWorkspaceSelectionCommitter();
|
||||
|
||||
committer.request("ws_2", async () => {
|
||||
committed.push("session:ws_2");
|
||||
await failedCommitBlocked;
|
||||
throw new Error("stale connection");
|
||||
});
|
||||
committer.request("ws_2", async () => {
|
||||
committed.push("settings:ws_2");
|
||||
});
|
||||
|
||||
releaseFailedCommit();
|
||||
await committer.settled();
|
||||
expect(committed).toEqual(["session:ws_2", "settings:ws_2"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("commitRouteWorkspaceSelection", () => {
|
||||
test("serializes desktop mutations before server activation", async () => {
|
||||
const calls: string[] = [];
|
||||
await commitRouteWorkspaceSelection({
|
||||
workspaceId: " ws_2 ",
|
||||
desktopRuntime: true,
|
||||
setDesktopSelected: async (workspaceId) => { calls.push(`selected:${workspaceId}`); },
|
||||
setDesktopRuntimeActive: async (workspaceId) => { calls.push(`runtime:${workspaceId}`); },
|
||||
activateWorkspace: async (workspaceId) => { calls.push(`server:${workspaceId}`); },
|
||||
});
|
||||
expect(calls).toEqual(["selected:ws_2", "runtime:ws_2", "server:ws_2"]);
|
||||
});
|
||||
|
||||
test("continues after a desktop persistence failure", async () => {
|
||||
const calls: string[] = [];
|
||||
await commitRouteWorkspaceSelection({
|
||||
workspaceId: "ws_2",
|
||||
desktopRuntime: true,
|
||||
setDesktopSelected: async () => { throw new Error("write failed"); },
|
||||
setDesktopRuntimeActive: async (workspaceId) => { calls.push(`runtime:${workspaceId}`); },
|
||||
activateWorkspace: async (workspaceId) => { calls.push(`server:${workspaceId}`); },
|
||||
});
|
||||
expect(calls).toEqual(["runtime:ws_2", "server:ws_2"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("planRouteWorkspaceLoads", () => {
|
||||
test("loads every unloaded workspace with the selected workspace first", () => {
|
||||
expect(planRouteWorkspaceLoads(
|
||||
["ws_1", "ws_2", "ws_3", "ws_4"],
|
||||
"ws_3",
|
||||
new Set(["ws_1"]),
|
||||
)).toEqual(["ws_3", "ws_2", "ws_4"]);
|
||||
});
|
||||
|
||||
test("still loads other unloaded workspaces when the selected one is loaded", () => {
|
||||
expect(planRouteWorkspaceLoads(["ws_1", "ws_2"], "ws_2", new Set(["ws_2"]))).toEqual(["ws_1"]);
|
||||
});
|
||||
|
||||
test("returns nothing when every workspace session index is loaded", () => {
|
||||
expect(planRouteWorkspaceLoads(["ws_1", "ws_2"], "ws_1", new Set(["ws_1", "ws_2"]))).toEqual([]);
|
||||
});
|
||||
|
||||
test("ignores a selected workspace that is not in the list", () => {
|
||||
expect(planRouteWorkspaceLoads(["ws_1"], "ws_missing", new Set())).toEqual(["ws_1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createRouteRefreshLifecycle", () => {
|
||||
test("dedupes refreshes while one is in flight", () => {
|
||||
const lifecycle = createRouteRefreshLifecycle();
|
||||
|
||||
const first = lifecycle.begin();
|
||||
expect(first).not.toBeNull();
|
||||
expect(lifecycle.begin()).toBeNull();
|
||||
|
||||
expect(first?.finish()).toBe(true);
|
||||
expect(lifecycle.isInFlight()).toBe(false);
|
||||
expect(lifecycle.begin()).not.toBeNull();
|
||||
});
|
||||
|
||||
test("supersede cancels the in-flight attempt instead of racing it", () => {
|
||||
const lifecycle = createRouteRefreshLifecycle();
|
||||
|
||||
const stale = lifecycle.begin();
|
||||
const fresh = lifecycle.begin({ supersede: true });
|
||||
|
||||
expect(stale?.isCurrent()).toBe(false);
|
||||
expect(fresh?.isCurrent()).toBe(true);
|
||||
|
||||
// The superseded attempt no longer owns the in-flight slot, so its
|
||||
// completion cannot re-open the route mid-refresh.
|
||||
expect(stale?.finish()).toBe(false);
|
||||
expect(lifecycle.isInFlight()).toBe(true);
|
||||
expect(fresh?.finish()).toBe(true);
|
||||
expect(lifecycle.isInFlight()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("planRouteConnectionGap", () => {
|
||||
test("desktop gaps retain route state and hold the boot overlay", () => {
|
||||
expect(planRouteConnectionGap({ desktopRuntime: true })).toEqual({
|
||||
retainExistingState: true,
|
||||
markRouteReady: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("web treats a missing connection as a real disconnected state", () => {
|
||||
expect(planRouteConnectionGap({ desktopRuntime: false })).toEqual({
|
||||
retainExistingState: false,
|
||||
markRouteReady: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Mirrors refreshRouteState's control flow: begin → resolve connection →
|
||||
// staleness check → gap plan or apply → finish, with route readiness reported
|
||||
// only by the attempt that still owns the refresh. On a desktop gap the
|
||||
// connection is quarantined (no request may carry the previous bearer token
|
||||
// to the freed loopback port) while session display state is retained.
|
||||
type SimulatedRouteState = {
|
||||
baseUrl: string;
|
||||
token: string;
|
||||
sessions: string[];
|
||||
routeReady: boolean;
|
||||
};
|
||||
|
||||
function createSimulatedRoute(initial: SimulatedRouteState) {
|
||||
const lifecycle = createRouteRefreshLifecycle();
|
||||
const state = { ...initial, sessions: [...initial.sessions] };
|
||||
|
||||
const refresh = async (
|
||||
resolveConnection: () => Promise<{ baseUrl: string; token: string } | null>,
|
||||
options?: { supersede?: boolean },
|
||||
) => {
|
||||
const attempt = lifecycle.begin(options);
|
||||
if (!attempt) return;
|
||||
let routeReadyAfterRefresh = true;
|
||||
try {
|
||||
const resolution = await resolveConnection();
|
||||
if (!attempt.isCurrent()) return;
|
||||
if (!resolution) {
|
||||
const gapPlan = planRouteConnectionGap({ desktopRuntime: true });
|
||||
routeReadyAfterRefresh = gapPlan.markRouteReady;
|
||||
// The live connection is quarantined in every gap.
|
||||
state.baseUrl = "";
|
||||
state.token = "";
|
||||
if (!gapPlan.retainExistingState) {
|
||||
state.sessions = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
state.baseUrl = resolution.baseUrl;
|
||||
state.token = resolution.token;
|
||||
} finally {
|
||||
attempt.finish();
|
||||
if (attempt.isCurrent() && routeReadyAfterRefresh) {
|
||||
state.routeReady = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { state, refresh };
|
||||
}
|
||||
|
||||
describe("desktop restart refresh sequence", () => {
|
||||
test("an empty resolution quarantines the connection, retains sessions, and a later one recovers", async () => {
|
||||
const route = createSimulatedRoute({
|
||||
baseUrl: "http://127.0.0.1:4100",
|
||||
token: "tok_before_restart",
|
||||
sessions: ["task-1", "task-2"],
|
||||
routeReady: false,
|
||||
});
|
||||
|
||||
// Local server is restarting: the first resolution has no base URL/token.
|
||||
await route.refresh(async () => null);
|
||||
|
||||
// No live endpoint or bearer token survives the gap — the freed loopback
|
||||
// port must never receive the previous credentials…
|
||||
expect(route.state.baseUrl).toBe("");
|
||||
expect(route.state.token).toBe("");
|
||||
// …while the session display state is retained for the user.
|
||||
expect(route.state.sessions).toEqual(["task-1", "task-2"]);
|
||||
// The boot overlay must not be released by the gap refresh.
|
||||
expect(route.state.routeReady).toBe(false);
|
||||
|
||||
// The restarted server published fresh connection info.
|
||||
await route.refresh(async () => ({ baseUrl: "http://127.0.0.1:4187", token: "tok_after_restart" }));
|
||||
|
||||
expect(route.state.baseUrl).toBe("http://127.0.0.1:4187");
|
||||
expect(route.state.token).toBe("tok_after_restart");
|
||||
expect(route.state.sessions).toEqual(["task-1", "task-2"]);
|
||||
expect(route.state.routeReady).toBe(true);
|
||||
});
|
||||
|
||||
test("a stale slow refresh cannot overwrite the recovered connection", async () => {
|
||||
const route = createSimulatedRoute({
|
||||
baseUrl: "",
|
||||
token: "",
|
||||
sessions: ["task-1"],
|
||||
routeReady: false,
|
||||
});
|
||||
|
||||
let releaseStaleResolution: (value: null) => void = () => {};
|
||||
const staleResolution = new Promise<null>((resolve) => {
|
||||
releaseStaleResolution = resolve;
|
||||
});
|
||||
|
||||
// A refresh starts against the restarting server and hangs on resolution.
|
||||
const staleRun = route.refresh(() => staleResolution);
|
||||
// The republished server settings supersede it with a fresh refresh.
|
||||
await route.refresh(
|
||||
async () => ({ baseUrl: "http://127.0.0.1:4187", token: "tok_recovered" }),
|
||||
{ supersede: true },
|
||||
);
|
||||
|
||||
expect(route.state.baseUrl).toBe("http://127.0.0.1:4187");
|
||||
expect(route.state.routeReady).toBe(true);
|
||||
|
||||
// The stale attempt finally resolves empty — after the recovery. Its
|
||||
// completion must change nothing.
|
||||
releaseStaleResolution(null);
|
||||
await staleRun;
|
||||
|
||||
expect(route.state.baseUrl).toBe("http://127.0.0.1:4187");
|
||||
expect(route.state.token).toBe("tok_recovered");
|
||||
expect(route.state.sessions).toEqual(["task-1"]);
|
||||
expect(route.state.routeReady).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user