FN-6366: fix desktop Vitest constructor mocks

Stabilize the desktop Vitest suite under Vitest 4.1.8 while keeping dashboard builds complete.

- Convert Electron constructor mocks to constructable function implementations for BrowserWindow, Tray, Notification, and LocalRuntimeManager.\n- Assert local runtime initialization uses the mocked home directory in desktop main-process tests.\n- Build dashboard runtime plugin packages before the desktop dashboard build.

Files changed:\n packages/desktop/scripts/workspace-tools.ts        | 15 ++++++++++++++\n packages/desktop/src/__tests__/deep-link.test.ts   |  4 +++-\n .../desktop/src/__tests__/main-integration.test.ts | 23 ++++++++++++++--------\n .../desktop/src/__tests__/main-local-mode.test.ts  | 19 ++++++++++++++----\n .../desktop/src/__tests__/main.integration.test.ts | 21 ++++++++++++--------\n packages/desktop/src/__tests__/main.test.ts        | 13 +++++++++---\n packages/desktop/src/__tests__/native.test.ts      |  6 ++++--\n 7 files changed, 75 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-6366

Fusion-Task-Lineage: 4e4ccdbc-845f-47d9-a565-cc5d659cf089
This commit is contained in:
gsxdsm
2026-06-13 09:01:39 -07:00
parent 3a0e3d558f
commit 63f1e097a2
7 changed files with 75 additions and 26 deletions

View File

@@ -45,8 +45,23 @@ export async function buildCore(): Promise<void> {
await runWorkspaceBin("tsc", [], resolve(workspaceRoot, "packages", "core"));
}
async function buildPackage(relativePath: string): Promise<void> {
await runWorkspaceBin("tsc", [], resolve(workspaceRoot, relativePath));
}
export async function buildDashboardRuntimePlugins(): Promise<void> {
await buildPackage("packages/plugin-sdk");
await Promise.all([
buildPackage("plugins/fusion-plugin-dependency-graph"),
buildPackage("plugins/fusion-plugin-hermes-runtime"),
buildPackage("plugins/fusion-plugin-openclaw-runtime"),
buildPackage("plugins/fusion-plugin-paperclip-runtime"),
]);
}
export async function buildDashboard(): Promise<void> {
const dashboardRoot = resolve(workspaceRoot, "packages", "dashboard");
await buildDashboardRuntimePlugins();
await runWorkspaceBin("vite", ["build"], dashboardRoot);
await runWorkspaceBin("tsc", [], dashboardRoot);
}

View File

@@ -33,7 +33,9 @@ const mocks = vi.hoisted(() => {
vi.mock("electron", () => ({
app: mocks.app,
BrowserWindow: vi.fn(() => mocks.browserWindow),
BrowserWindow: vi.fn(function () {
return mocks.browserWindow;
}),
}));
async function importDeepLinkModule() {

View File

@@ -44,6 +44,7 @@ const mocks = vi.hoisted(() => {
const app = {
whenReady: vi.fn(() => Promise.resolve()),
getPath: vi.fn((name: string) => (name === "home" ? "/mock/home" : "/mock/other")),
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
appEvents.set(event, handler);
}),
@@ -51,14 +52,14 @@ const mocks = vi.hoisted(() => {
isQuitting: false,
};
const BrowserWindow = vi.fn((options: Record<string, unknown>) => {
const BrowserWindow = vi.fn(function (options: Record<string, unknown>) {
callLog.push("createMainWindow");
const instance = createWindowMock();
windowInstances.push({ instance, options });
return instance;
});
const Tray = vi.fn(() => {
const Tray = vi.fn(function () {
const tray = createTrayMock();
trayInstances.push(tray);
return tray;
@@ -117,6 +118,14 @@ const mocks = vi.hoisted(() => {
const getStatus = vi.fn(() => ({ source: "none", state: "stopped" }));
const saveWindowState = vi.fn();
const LocalRuntimeManager = vi.fn(function () {
return {
startLocal,
stopLocal,
getStatus,
getServerPort: vi.fn(() => 0),
};
});
const DEFAULT_WINDOW_STATE = {
width: 1280,
@@ -149,6 +158,7 @@ const mocks = vi.hoisted(() => {
startLocal,
stopLocal,
getStatus,
LocalRuntimeManager,
DEFAULT_WINDOW_STATE,
};
});
@@ -195,12 +205,7 @@ vi.mock("../native.js", () => ({
}));
vi.mock("../local-runtime.js", () => ({
LocalRuntimeManager: vi.fn(() => ({
startLocal: mocks.startLocal,
stopLocal: mocks.stopLocal,
getStatus: mocks.getStatus,
getServerPort: vi.fn(() => 0),
})),
LocalRuntimeManager: mocks.LocalRuntimeManager,
}));
// Mock renderer module
@@ -268,6 +273,8 @@ describe("main integration", () => {
"setupAutoUpdater",
"startUpdateCheckInterval",
]);
expect(mocks.LocalRuntimeManager).toHaveBeenCalledWith({ rootDir: "/mock/home" });
expect(mocks.app.getPath).toHaveBeenCalledWith("home");
});
it("createMainWindow uses restored window state", async () => {

View File

@@ -4,6 +4,7 @@ const mocks = vi.hoisted(() => {
const appHandlers = new Map<string, (...args: unknown[]) => void>();
const app = {
whenReady: vi.fn(async () => undefined),
getPath: vi.fn((name: string) => (name === "home" ? "/mock/home" : "/mock/other")),
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
appHandlers.set(event, handler);
return app;
@@ -26,8 +27,12 @@ const mocks = vi.hoisted(() => {
webContents: { send: vi.fn() },
};
const BrowserWindow = vi.fn(() => browserWindow);
const Tray = vi.fn(() => ({ destroy: vi.fn() }));
const BrowserWindow = vi.fn(function () {
return browserWindow;
});
const Tray = vi.fn(function () {
return { destroy: vi.fn() };
});
const localRuntimeManager = {
startLocal: vi.fn(async () => ({ source: "embedded-local", state: "running", port: 4041 })),
@@ -40,7 +45,11 @@ const mocks = vi.hoisted(() => {
getAllDisplays: vi.fn(() => [{ workArea: { x: 0, y: 0, width: 1920, height: 1080 } }]),
};
return { app, appHandlers, BrowserWindow, Tray, browserWindow, localRuntimeManager, screen };
const LocalRuntimeManager = vi.fn(function () {
return localRuntimeManager;
});
return { app, appHandlers, BrowserWindow, Tray, browserWindow, localRuntimeManager, LocalRuntimeManager, screen };
});
vi.mock("electron", () => ({
@@ -66,7 +75,7 @@ vi.mock("../native.js", () => ({
clampWindowStateToVisibleDisplay: vi.fn((state) => state),
}));
vi.mock("../deep-link.js", () => ({ registerDeepLinkProtocol: vi.fn(), setupDeepLinkHandler: vi.fn() }));
vi.mock("../local-runtime.js", () => ({ LocalRuntimeManager: vi.fn(() => mocks.localRuntimeManager) }));
vi.mock("../local-runtime.js", () => ({ LocalRuntimeManager: mocks.LocalRuntimeManager }));
describe("main local mode", () => {
beforeEach(() => {
@@ -81,6 +90,8 @@ describe("main local mode", () => {
const { initializeApp } = await import("../main.ts");
await initializeApp();
expect(mocks.LocalRuntimeManager).toHaveBeenCalledWith({ rootDir: "/mock/home" });
expect(mocks.app.getPath).toHaveBeenCalledWith("home");
expect(mocks.localRuntimeManager.startLocal).toHaveBeenCalled();
delete process.env.FUSION_DESKTOP_MODE;
});

View File

@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => {
const app = {
whenReady: vi.fn(() => Promise.resolve()),
getPath: vi.fn((name: string) => (name === "home" ? "/mock/home" : "/mock/other")),
on: vi.fn(),
quit: vi.fn(),
};
@@ -20,14 +21,18 @@ const mocks = vi.hoisted(() => {
return {
app,
BrowserWindow: vi.fn(() => browserWindow),
Tray: vi.fn(() => ({
destroy: vi.fn(),
setImage: vi.fn(),
setContextMenu: vi.fn(),
setToolTip: vi.fn(),
on: vi.fn(),
})),
BrowserWindow: vi.fn(function () {
return browserWindow;
}),
Tray: vi.fn(function () {
return {
destroy: vi.fn(),
setImage: vi.fn(),
setContextMenu: vi.fn(),
setToolTip: vi.fn(),
on: vi.fn(),
};
}),
nativeImage: {
createEmpty: vi.fn(() => ({ id: "empty-image" })),
},

View File

@@ -34,7 +34,9 @@ const mocks = vi.hoisted(() => {
maximize: vi.fn(),
};
const BrowserWindow = vi.fn(() => browserWindowInstance) as unknown as {
const BrowserWindow = vi.fn(function () {
return browserWindowInstance;
}) as unknown as {
(...args: unknown[]): typeof browserWindowInstance;
getAllWindows: () => unknown[];
};
@@ -60,7 +62,9 @@ const mocks = vi.hoisted(() => {
on: vi.fn(),
};
const Tray = vi.fn(() => trayInstance);
const Tray = vi.fn(function () {
return trayInstance;
});
const Menu = {
buildFromTemplate: vi.fn(() => ({ id: "mock-menu" })),
setApplicationMenu: vi.fn(),
@@ -133,7 +137,9 @@ const mainDeps = vi.hoisted(() => {
loadDesktopLaunchMode,
saveDesktopLaunchMode,
saveWindowState: vi.fn(),
LocalRuntimeManager: vi.fn(() => ({ startLocal, stopLocal, getStatus, getServerPort })),
LocalRuntimeManager: vi.fn(function () {
return { startLocal, stopLocal, getStatus, getServerPort };
}),
startLocal,
};
});
@@ -301,6 +307,7 @@ describe("main process", () => {
await initializeApp();
expect(mainDeps.LocalRuntimeManager).toHaveBeenCalledWith({ rootDir: "/mock/home" });
expect(mainDeps.startLocal).toHaveBeenCalledTimes(1);
expect(getCurrentDesktopLaunchMode()).toBe("local");
});

View File

@@ -24,7 +24,7 @@ const mocks = vi.hoisted(() => {
options: Record<string, unknown>;
}> = [];
const Notification = vi.fn().mockImplementation((options: Record<string, unknown>) => {
const Notification = vi.fn().mockImplementation(function (options: Record<string, unknown>) {
const listeners = new Map<string, () => void>();
const instance = {
show: vi.fn(),
@@ -92,7 +92,9 @@ vi.mock("electron", () => ({
app: mocks.app,
dialog: mocks.dialog,
Notification: mocks.Notification,
BrowserWindow: vi.fn(() => mocks.browserWindow),
BrowserWindow: vi.fn(function () {
return mocks.browserWindow;
}),
}));
vi.mock("electron-updater", () => ({