225 lines
6.9 KiB
TypeScript
225 lines
6.9 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { loadBootPayload, readBootPayload } from "./boot-payload";
|
|
|
|
const JIRA_BUNDLE_URL = "/api/plugins/jira/bundle";
|
|
|
|
describe("readBootPayload", () => {
|
|
it("returns an empty initial state when Go has not injected boot data yet", () => {
|
|
const win = {} as Window;
|
|
|
|
expect(readBootPayload(win)).toEqual({ initialState: {} });
|
|
});
|
|
|
|
it("normalizes the injected Go boot payload shape", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
version: 1,
|
|
route: {
|
|
kind: "spa",
|
|
route: "taskDetail",
|
|
path: "/t/task-1",
|
|
params: { taskId: "task-1" },
|
|
},
|
|
runtime: {
|
|
apiPrefix: "/api/v1",
|
|
webSocketPath: "/ws",
|
|
lspAutoInstallPreferenceLanguages: ["go", "python", "rust", "typescript"],
|
|
},
|
|
initialState: {
|
|
tasks: { activeTaskId: "task-1" },
|
|
},
|
|
interimSettingsInterlockToken: "replayable-per-boot-value",
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win)).toMatchObject({
|
|
version: 1,
|
|
route: {
|
|
kind: "spa",
|
|
route: "taskDetail",
|
|
path: "/t/task-1",
|
|
params: { taskId: "task-1" },
|
|
},
|
|
runtime: {
|
|
apiPrefix: "/api/v1",
|
|
webSocketPath: "/ws",
|
|
lspAutoInstallPreferenceLanguages: ["go", "python", "rust", "typescript"],
|
|
},
|
|
initialState: {
|
|
tasks: { activeTaskId: "task-1" },
|
|
},
|
|
interimSettingsInterlockToken: "replayable-per-boot-value",
|
|
});
|
|
});
|
|
|
|
it("drops invalid route params instead of exposing mixed values", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
route: {
|
|
params: { taskId: "task-1", bad: 3 },
|
|
},
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).route?.params).toBeUndefined();
|
|
});
|
|
|
|
it("enables the runtime debug global when boot payload debug is true", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
runtime: {
|
|
debug: true,
|
|
},
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).runtime?.debug).toBe(true);
|
|
expect(win.__KANDEV_DEBUG).toBe(true);
|
|
});
|
|
|
|
it("reads the browser tab title prefix from the runtime block", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: { runtime: { titlePrefix: "TEST" } },
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).runtime?.titlePrefix).toBe("TEST");
|
|
});
|
|
|
|
it("leaves the title prefix undefined when the runtime block omits it", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: { runtime: { apiPrefix: "/api/v1" } },
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).runtime?.titlePrefix).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("readBootPayload runtime metadata", () => {
|
|
it("reads the native folder picker capability from the runtime block", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
runtime: { nativeFolderPickerAvailable: true, desktopRuntime: true },
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).runtime?.nativeFolderPickerAvailable).toBe(true);
|
|
expect(readBootPayload(win).runtime?.desktopRuntime).toBe(true);
|
|
});
|
|
|
|
it("keeps the backend boot identity from the runtime block", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: { runtime: { bootId: "boot-123" } },
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win)).toMatchObject({ runtime: { bootId: "boot-123" } });
|
|
});
|
|
|
|
it("ignores missing or malformed backend boot identities", () => {
|
|
const missing = {
|
|
__KANDEV_BOOT_PAYLOAD__: { runtime: {} },
|
|
} as unknown as Window;
|
|
const malformed = {
|
|
__KANDEV_BOOT_PAYLOAD__: { runtime: { bootId: 123 } },
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(missing).runtime?.bootId).toBeUndefined();
|
|
expect(readBootPayload(malformed).runtime?.bootId).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("readBootPayload plugins", () => {
|
|
it("parses active plugins from the boot payload", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
plugins: [
|
|
{
|
|
id: "jira",
|
|
name: "Jira",
|
|
bundleUrl: JIRA_BUNDLE_URL,
|
|
styleUrls: ["/api/plugins/jira/style.css"],
|
|
repositoryProviderIds: ["jira"],
|
|
},
|
|
{ id: "hello", name: "Hello", bundleUrl: "/api/plugins/hello/bundle" },
|
|
],
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).plugins).toEqual([
|
|
{
|
|
id: "jira",
|
|
name: "Jira",
|
|
bundleUrl: JIRA_BUNDLE_URL,
|
|
styleUrls: ["/api/plugins/jira/style.css"],
|
|
repositoryProviderIds: ["jira"],
|
|
},
|
|
{ id: "hello", name: "Hello", bundleUrl: "/api/plugins/hello/bundle", styleUrls: undefined },
|
|
]);
|
|
});
|
|
|
|
it("drops plugin entries missing required fields and non-string styleUrls entries", () => {
|
|
const win = {
|
|
__KANDEV_BOOT_PAYLOAD__: {
|
|
plugins: [
|
|
{ id: "no-bundle-url", name: "Missing bundleUrl" },
|
|
{
|
|
id: "jira",
|
|
name: "Jira",
|
|
bundleUrl: JIRA_BUNDLE_URL,
|
|
styleUrls: ["ok.css", 3],
|
|
repositoryProviderIds: ["jira", 3, null, "azure_devops"],
|
|
},
|
|
],
|
|
},
|
|
} as unknown as Window;
|
|
|
|
expect(readBootPayload(win).plugins).toEqual([
|
|
{
|
|
id: "jira",
|
|
name: "Jira",
|
|
bundleUrl: JIRA_BUNDLE_URL,
|
|
styleUrls: ["ok.css"],
|
|
repositoryProviderIds: ["jira", "azure_devops"],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("leaves plugins undefined when the boot payload has no plugins field", () => {
|
|
const win = { __KANDEV_BOOT_PAYLOAD__: { version: 1 } } as unknown as Window;
|
|
|
|
expect(readBootPayload(win).plugins).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("loadBootPayload", () => {
|
|
it("uses the injected Go boot payload without fetching", async () => {
|
|
const win = Object.assign(new Window(), {
|
|
__KANDEV_BOOT_PAYLOAD__: { version: 1, initialState: { features: { office: true } } },
|
|
}) as Window;
|
|
const fetcher = vi.fn();
|
|
|
|
await expect(loadBootPayload(win, fetcher)).resolves.toMatchObject({
|
|
initialState: { features: { office: true } },
|
|
});
|
|
expect(fetcher).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fetches app-state before mount when no boot payload was injected", async () => {
|
|
const win = new Window();
|
|
Object.defineProperty(win, "location", {
|
|
value: { pathname: "/", search: "?workspaceId=ws-1" },
|
|
});
|
|
const fetcher = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ version: 1, initialState: { workflows: { activeId: "wf-1" } } }),
|
|
});
|
|
|
|
await expect(loadBootPayload(win, fetcher)).resolves.toMatchObject({
|
|
initialState: { workflows: { activeId: "wf-1" } },
|
|
});
|
|
expect(fetcher).toHaveBeenCalledWith(
|
|
expect.stringContaining("/api/v1/app-state?path=%2F%3FworkspaceId%3Dws-1"),
|
|
expect.objectContaining({ cache: "no-store", credentials: "include" }),
|
|
);
|
|
});
|
|
});
|