Files
paseo/packages/app/src/navigation/host-runtime-bootstrap.test.ts
T
Mohamed BoudraandGitHub f94ec0e430 Keep New Workspace on the current project (#1806)
* fix(app): keep New Workspace on the active project

New Workspace preselection was falling back to stale remembered host state when multiple hosts existed. Pass durable active-project context from workspace entry points and centralize initial host selection so stale offline hosts cannot steal the form while startup restore remains last-workspace only.

* fix(app): address New Workspace review feedback

* fix(app): stabilize New Workspace host selection

* fix(app): preserve New Workspace host fallback

* test(app): loosen stale-host project assertion

* fix(app): prefer reachable new workspace hosts

* fix(app): avoid stale offline workspace preselect

* fix(app): prefer online project hosts
2026-06-29 20:44:18 +02:00

425 lines
13 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
resolveStartupBlocker,
resolveStartupNavigationReady,
resolveHostIndexRoute,
resolveStartupRoute,
shouldRunStartupGiveUpTimer,
startHostRuntimeBootstrap,
} from "./host-runtime-bootstrap";
function createFakeStore() {
return { boot: vi.fn() };
}
function createFakeDaemonStartService() {
return {
start: vi.fn(async () => ({ ok: true as const })),
};
}
describe("startHostRuntimeBootstrap", () => {
it("fires boot and daemon-start without awaiting the daemon-start promise", () => {
const events: string[] = [];
const store = {
boot: vi.fn(() => {
events.push("boot");
}),
};
const daemonStartService = {
start: vi.fn(async () => {
events.push("daemon-start");
return { ok: true as const };
}),
};
startHostRuntimeBootstrap({
store,
daemonStartService,
shouldStartDaemon: true,
});
expect(store.boot).toHaveBeenCalledTimes(1);
expect(daemonStartService.start).toHaveBeenCalledTimes(1);
expect(events).toEqual(["boot", "daemon-start"]);
});
it("skips daemon-start when shouldStartDaemon is false", () => {
const store = createFakeStore();
const daemonStartService = createFakeDaemonStartService();
startHostRuntimeBootstrap({
store,
daemonStartService,
shouldStartDaemon: false,
});
expect(store.boot).toHaveBeenCalledTimes(1);
expect(daemonStartService.start).not.toHaveBeenCalled();
});
it("skips daemon-start when the startup gate resolves false", async () => {
const store = createFakeStore();
const daemonStartService = createFakeDaemonStartService();
startHostRuntimeBootstrap({
store,
daemonStartService,
shouldStartDaemon: async () => false,
});
await Promise.resolve();
expect(store.boot).toHaveBeenCalledTimes(1);
expect(daemonStartService.start).not.toHaveBeenCalled();
});
it("surfaces gate rejection to onGateError without starting the daemon", async () => {
const store = createFakeStore();
const daemonStartService = createFakeDaemonStartService();
const onGateError = vi.fn();
startHostRuntimeBootstrap({
store,
daemonStartService,
shouldStartDaemon: async () => {
throw new Error("settings file unreadable");
},
onGateError,
});
await vi.waitFor(() => {
expect(onGateError).toHaveBeenCalledTimes(1);
});
expect(daemonStartService.start).not.toHaveBeenCalled();
expect(onGateError).toHaveBeenCalledWith(expect.stringContaining("settings file unreadable"));
});
it("does not await the daemon-start promise", () => {
const store = createFakeStore();
let resolveStart: ((value: { ok: true }) => void) | undefined;
const daemonStartService = {
start: vi.fn(
() =>
new Promise<{ ok: true }>((resolve) => {
resolveStart = resolve;
}),
),
};
startHostRuntimeBootstrap({
store,
daemonStartService,
shouldStartDaemon: true,
});
expect(store.boot).toHaveBeenCalledTimes(1);
expect(daemonStartService.start).toHaveBeenCalledTimes(1);
resolveStart?.({ ok: true });
});
});
describe("startup blocking policy", () => {
const noBlockerInput = {
isDesktopRuntime: false,
anyOnlineHostServerId: null,
daemonStartIsRunning: false,
daemonStartError: null,
};
it("runs the give-up timer when no startup blocker is active", () => {
const blocker = resolveStartupBlocker(noBlockerInput);
expect(blocker).toEqual({ kind: "none" });
expect(resolveStartupNavigationReady({ startupBlocker: blocker })).toBe(true);
expect(
shouldRunStartupGiveUpTimer({
startupBlocker: blocker,
anyOnlineHostServerId: null,
hasGivenUpWaitingForHost: false,
}),
).toBe(true);
});
it("blocks navigation while desktop is starting the managed daemon", () => {
const blocker = resolveStartupBlocker({
...noBlockerInput,
isDesktopRuntime: true,
daemonStartIsRunning: true,
});
expect(blocker).toEqual({ kind: "managed-daemon-starting" });
expect(resolveStartupNavigationReady({ startupBlocker: blocker })).toBe(false);
expect(
shouldRunStartupGiveUpTimer({
startupBlocker: blocker,
anyOnlineHostServerId: null,
hasGivenUpWaitingForHost: false,
}),
).toBe(false);
});
it("unblocks navigation when any host is online", () => {
const blocker = resolveStartupBlocker({
...noBlockerInput,
isDesktopRuntime: true,
anyOnlineHostServerId: "srv_desktop",
daemonStartIsRunning: true,
});
expect(blocker).toEqual({ kind: "none" });
expect(resolveStartupNavigationReady({ startupBlocker: blocker })).toBe(true);
});
it("keeps desktop daemon startup errors on the startup error surface", () => {
const blocker = resolveStartupBlocker({
...noBlockerInput,
isDesktopRuntime: true,
daemonStartError: "daemon failed to start",
});
expect(blocker).toEqual({
kind: "managed-daemon-error",
message: "daemon failed to start",
});
expect(resolveStartupNavigationReady({ startupBlocker: blocker })).toBe(true);
expect(
shouldRunStartupGiveUpTimer({
startupBlocker: blocker,
anyOnlineHostServerId: null,
hasGivenUpWaitingForHost: false,
}),
).toBe(false);
});
});
describe("resolveStartupRoute", () => {
const baseIndexInput = {
route: { kind: "index" as const, pathname: "/" },
startupBlocker: { kind: "none" as const },
hostRegistryStatus: "ready" as const,
hosts: [],
anyOnlineHostServerId: null,
workspaceSelection: null,
workspaceSelectionStatus: "unknown" as const,
isWorkspaceSelectionLoaded: true,
hasGivenUpWaitingForHost: false,
};
const baseHostInput = {
route: { kind: "host" as const, serverId: "server-saved" },
startupBlocker: { kind: "none" as const },
hostRegistryStatus: "ready" as const,
hosts: [],
};
it("renders non-index routes instead of making an index startup decision", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
route: { kind: "index", pathname: "/settings" },
}),
).toEqual({ kind: "render" });
});
it("keeps startup on the splash while the persisted workspace selection is loading", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
anyOnlineHostServerId: "server-1",
isWorkspaceSelectionLoaded: false,
}),
).toEqual({ kind: "splash" });
});
it("keeps startup on the splash while the host registry is loading", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hostRegistryStatus: "loading",
}),
).toEqual({ kind: "splash" });
});
it("does not treat loading hosts as an empty registry when a workspace is already restored", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hostRegistryStatus: "loading",
workspaceSelection: { serverId: "server-1", workspaceId: "workspace-a" },
hasGivenUpWaitingForHost: true,
}),
).toEqual({ kind: "splash" });
});
it("enters the host boundary for saved workspace restore after the host registry proves the host exists", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hosts: [{ serverId: "server-1" }],
workspaceSelection: { serverId: "server-1", workspaceId: "workspace-a" },
workspaceSelectionStatus: "exists",
}),
).toEqual({ kind: "redirect", href: "/h/server-1" });
});
it("restores the last workspace host even when a different host is already online", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hosts: [{ serverId: "server-offline" }, { serverId: "server-online" }],
anyOnlineHostServerId: "server-online",
workspaceSelection: { serverId: "server-offline", workspaceId: "workspace-a" },
workspaceSelectionStatus: "unknown",
}),
).toEqual({ kind: "redirect", href: "/h/server-offline" });
});
it("does not restore a saved workspace after workspace hydration proves it is missing", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hosts: [{ serverId: "server-1" }],
workspaceSelection: { serverId: "server-1", workspaceId: "workspace-a" },
workspaceSelectionStatus: "missing",
}),
).toEqual({ kind: "redirect", href: "/h/server-1" });
});
it("falls back to a saved host when the restored workspace host is no longer saved", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
workspaceSelection: { serverId: "server-saved", workspaceId: "workspace-a" },
hosts: [{ serverId: "server-next" }],
hasGivenUpWaitingForHost: true,
}),
).toEqual({ kind: "redirect", href: "/h/server-next" });
});
it("redirects to the online host when no saved workspace is selected", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
anyOnlineHostServerId: "srv-desktop",
}),
).toEqual({ kind: "redirect", href: "/h/srv-desktop" });
});
it("keeps a known connecting host in app-owned routing instead of showing welcome", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hosts: [{ serverId: "server-saved" }],
hasGivenUpWaitingForHost: true,
}),
).toEqual({ kind: "redirect", href: "/h/server-saved" });
});
it("shows welcome after root startup gives up and no host exists", () => {
expect(
resolveStartupRoute({
...baseIndexInput,
hasGivenUpWaitingForHost: true,
}),
).toEqual({ kind: "redirect", href: "/welcome" });
});
it("keeps host routes mounted while the host registry is loading", () => {
expect(
resolveStartupRoute({
...baseHostInput,
hostRegistryStatus: "loading",
}),
).toEqual({ kind: "render" });
});
it("keeps host routes mounted while the managed daemon is starting", () => {
expect(
resolveStartupRoute({
...baseHostInput,
startupBlocker: { kind: "managed-daemon-starting" },
}),
).toEqual({ kind: "render" });
});
it("renders a host route once the route host is known", () => {
expect(
resolveStartupRoute({
...baseHostInput,
hosts: [{ serverId: "server-saved" }],
}),
).toEqual({ kind: "render" });
});
it("sends removed host routes to global project selection instead of welcome", () => {
expect(
resolveStartupRoute({
...baseHostInput,
route: { kind: "host", serverId: "server-removed" },
hosts: [{ serverId: "server-next" }],
}),
).toEqual({ kind: "redirect", href: "/open-project" });
});
it("shows welcome from a host route only after the registry proves no hosts exist", () => {
expect(
resolveStartupRoute({
...baseHostInput,
route: { kind: "host", serverId: "server-removed" },
}),
).toEqual({ kind: "redirect", href: "/welcome" });
});
});
describe("resolveHostIndexRoute", () => {
it("restores the remembered workspace when the host index opens for the same host", () => {
expect(
resolveHostIndexRoute({
serverId: "server-saved",
workspaceSelection: { serverId: "server-saved", workspaceId: "workspace-a" },
workspaceSelectionStatus: "exists",
}),
).toEqual("/h/server-saved/workspace/workspace-a");
});
it("keeps restoring a remembered workspace before the host workspace list hydrates", () => {
expect(
resolveHostIndexRoute({
serverId: "server-saved",
workspaceSelection: { serverId: "server-saved", workspaceId: "workspace-a" },
workspaceSelectionStatus: "unknown",
}),
).toEqual("/h/server-saved/workspace/workspace-a");
});
it("opens global project selection when the remembered workspace is proven missing", () => {
expect(
resolveHostIndexRoute({
serverId: "server-saved",
workspaceSelection: { serverId: "server-saved", workspaceId: "workspace-a" },
workspaceSelectionStatus: "missing",
}),
).toEqual("/open-project");
});
it("opens global project selection when the remembered workspace belongs to another host", () => {
expect(
resolveHostIndexRoute({
serverId: "server-saved",
workspaceSelection: { serverId: "server-other", workspaceId: "workspace-a" },
workspaceSelectionStatus: "exists",
}),
).toEqual("/open-project");
});
it("opens global project selection when no workspace is remembered", () => {
expect(
resolveHostIndexRoute({
serverId: "server-saved",
workspaceSelection: null,
workspaceSelectionStatus: "unknown",
}),
).toEqual("/open-project");
});
});