Files
fusion/packages/desktop/src/__tests__/local-server.test.ts
Fusion 025eb7b3ae feat(FN-3398): update desktop README with multi-project setup and troublesh
The merge lands Step 7 of FN-3398, adding documentation and delivery artifacts across the mobile and desktop packages with updated READMEs, mobile-specific docs, and architecture references.

Fusion-Task-Id: FN-3398
2026-05-04 22:17:10 -07:00

112 lines
3.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => {
type Handler = (...args: unknown[]) => void;
class SimpleEmitter {
private listeners = new Map<string, Handler[]>();
on(event: string, handler: Handler) {
const current = this.listeners.get(event) ?? [];
current.push(handler);
this.listeners.set(event, current);
return this;
}
once(event: string, handler: Handler) {
const wrapped: Handler = (...args) => {
this.removeListener(event, wrapped);
handler(...args);
};
return this.on(event, wrapped);
}
removeListener(event: string, handler: Handler) {
const current = this.listeners.get(event) ?? [];
this.listeners.set(event, current.filter((item) => item !== handler));
return this;
}
emit(event: string, ...args: unknown[]) {
const current = this.listeners.get(event) ?? [];
for (const handler of current) {
handler(...args);
}
}
}
const store = {
init: vi.fn(async () => undefined),
watch: vi.fn(async () => undefined),
close: vi.fn(),
};
class TaskStore {
constructor(_rootDir: string) {}
init = store.init;
watch = store.watch;
close = store.close;
}
const server = Object.assign(new SimpleEmitter(), {
address: vi.fn(() => ({ port: 4545 })),
close: vi.fn((cb: () => void) => cb()),
});
const listen = vi.fn(() => {
queueMicrotask(() => server.emit("listening"));
return server;
});
const createServer = vi.fn(() => ({ listen }));
return { TaskStore, createServer, store, listen };
});
vi.mock("@fusion/core", () => ({ TaskStore: mocks.TaskStore }));
vi.mock("@fusion/dashboard", () => ({ createServer: mocks.createServer }));
describe("DesktopLocalServerManager", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("starts local runtime and exposes port", async () => {
const { DesktopLocalServerManager } = await import("../local-server.ts");
const manager = new DesktopLocalServerManager("/repo");
const runtime = await manager.start();
expect(runtime.port).toBe(4545);
expect(manager.getPort()).toBe(4545);
expect(manager.getState().status).toBe("ready");
});
it("stops local runtime and resets state", async () => {
const { DesktopLocalServerManager } = await import("../local-server.ts");
const manager = new DesktopLocalServerManager("/repo");
await manager.start();
await manager.stop();
expect(mocks.store.close).toHaveBeenCalled();
expect(manager.getState().status).toBe("idle");
expect(manager.getPort()).toBeUndefined();
});
it("sets error state when startup fails", async () => {
mocks.store.init.mockRejectedValueOnce(new Error("init failed"));
const { DesktopLocalServerManager } = await import("../local-server.ts");
const manager = new DesktopLocalServerManager("/repo");
await expect(manager.start()).rejects.toThrow("init failed");
expect(manager.getState()).toMatchObject({ status: "error", error: "init failed" });
});
it("returns existing runtime when start is called twice", async () => {
const { DesktopLocalServerManager } = await import("../local-server.ts");
const manager = new DesktopLocalServerManager("/repo");
const first = await manager.start();
const second = await manager.start();
expect(first).toBe(second);
expect(mocks.listen).toHaveBeenCalledTimes(1);
});
});