Treat desktop settings evaluation as part of daemon startup so restored app chrome cannot render between bootstrap phases.
347 lines
11 KiB
TypeScript
347 lines
11 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
resolveStartupBlocker,
|
|
resolveStartupNavigationReady,
|
|
resolveHostIndexRoute,
|
|
resolveStartupRoute,
|
|
shouldRunStartupGiveUpTimer,
|
|
startHostRuntimeBootstrap,
|
|
} from "./host-runtime-bootstrap";
|
|
import type {
|
|
DaemonStartCondition,
|
|
StartDaemonIfEnabledInput,
|
|
} from "@/runtime/daemon-start-service";
|
|
|
|
describe("startHostRuntimeBootstrap", () => {
|
|
it("boots the host registry and starts the managed-daemon decision as one operation", () => {
|
|
const events: string[] = [];
|
|
const shouldStartDaemon = async () => true;
|
|
const store = {
|
|
boot: () => {
|
|
events.push("boot");
|
|
},
|
|
};
|
|
let receivedCondition: DaemonStartCondition | null = null;
|
|
const daemonStartService = {
|
|
startIfEnabled: async (input: StartDaemonIfEnabledInput) => {
|
|
receivedCondition = input.shouldStart;
|
|
events.push("daemon-start-decision");
|
|
return { ok: true as const };
|
|
},
|
|
};
|
|
|
|
startHostRuntimeBootstrap({
|
|
store,
|
|
daemonStartService,
|
|
shouldStartDaemon,
|
|
});
|
|
|
|
expect(events).toEqual(["boot", "daemon-start-decision"]);
|
|
expect(receivedCondition).toBe(shouldStartDaemon);
|
|
});
|
|
});
|
|
|
|
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");
|
|
});
|
|
});
|