- Fix CSS URL resolution in memory when loading with file:// protocol - Add DASHBOARD_URL re-export from renderer module for backward compatibility - Update main.test.ts to use correct renderer mock reference (rendererMocks vs mocks) - Document the URL resolution bug fix in memory
226 lines
6.8 KiB
TypeScript
226 lines
6.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
// Mock renderer module - must be hoisted before importing main
|
|
const rendererMocks = vi.hoisted(() => {
|
|
const getRendererUrl = vi.fn(() => "file:///path/to/dist/client/index.html");
|
|
return {
|
|
isDevelopmentMode: vi.fn(() => false),
|
|
getRendererUrl,
|
|
getRendererFilePath: vi.fn(() => "/path/to/dist/client/index.html"),
|
|
isUrlRenderer: vi.fn(() => false),
|
|
IS_DEVELOPMENT: false,
|
|
// DASHBOARD_URL is re-exported as getRendererUrl
|
|
DASHBOARD_URL: getRendererUrl,
|
|
};
|
|
});
|
|
|
|
vi.mock("../renderer.ts", () => rendererMocks);
|
|
|
|
const mocks = vi.hoisted(() => {
|
|
const browserWindowInstance = {
|
|
loadURL: vi.fn(),
|
|
loadFile: vi.fn(),
|
|
on: vi.fn(),
|
|
isVisible: vi.fn(() => true),
|
|
show: vi.fn(),
|
|
focus: vi.fn(),
|
|
hide: vi.fn(),
|
|
};
|
|
|
|
const BrowserWindow = vi.fn(() => browserWindowInstance) as unknown as {
|
|
(...args: unknown[]): typeof browserWindowInstance;
|
|
getAllWindows: () => unknown[];
|
|
};
|
|
BrowserWindow.getAllWindows = vi.fn(() => []);
|
|
|
|
const app = {
|
|
whenReady: vi.fn(() => Promise.resolve()),
|
|
getVersion: vi.fn(() => "0.1.0"),
|
|
quit: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
const ipcMain = {
|
|
handle: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
const trayInstance = {
|
|
setImage: vi.fn(),
|
|
setToolTip: vi.fn(),
|
|
setContextMenu: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
const Tray = vi.fn(() => trayInstance);
|
|
const Menu = {
|
|
buildFromTemplate: vi.fn(() => ({ id: "mock-menu" })),
|
|
setApplicationMenu: vi.fn(),
|
|
};
|
|
const nativeImage = {
|
|
createEmpty: vi.fn(() => ({ id: "mock-image" })),
|
|
createFromPath: vi.fn(() => ({
|
|
resize: vi.fn(() => ({ id: "resized-image" })),
|
|
})),
|
|
};
|
|
|
|
const shell = {
|
|
openExternal: vi.fn(() => Promise.resolve()),
|
|
};
|
|
|
|
const isDevelopmentMode = vi.fn(() => false);
|
|
const getRendererUrl = vi.fn(() => "file:///path/to/dist/client/index.html");
|
|
const getRendererFilePath = vi.fn(() => "/path/to/dist/client/index.html");
|
|
const isUrlRenderer = vi.fn(() => false);
|
|
|
|
return {
|
|
app,
|
|
BrowserWindow,
|
|
ipcMain,
|
|
trayInstance,
|
|
Tray,
|
|
Menu,
|
|
nativeImage,
|
|
shell,
|
|
browserWindowInstance,
|
|
isDevelopmentMode,
|
|
getRendererUrl,
|
|
getRendererFilePath,
|
|
isUrlRenderer,
|
|
};
|
|
});
|
|
|
|
vi.mock("electron", () => ({
|
|
app: mocks.app,
|
|
BrowserWindow: mocks.BrowserWindow,
|
|
ipcMain: mocks.ipcMain,
|
|
Tray: mocks.Tray,
|
|
Menu: mocks.Menu,
|
|
nativeImage: mocks.nativeImage,
|
|
shell: mocks.shell,
|
|
}));
|
|
|
|
async function importMainModule() {
|
|
return import("../main.ts");
|
|
}
|
|
|
|
describe("main process", () => {
|
|
const originalDashboardUrl = process.env.FUSION_DASHBOARD_URL;
|
|
const originalNodeEnv = process.env.NODE_ENV;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.resetModules();
|
|
if (originalDashboardUrl === undefined) {
|
|
delete process.env.FUSION_DASHBOARD_URL;
|
|
} else {
|
|
process.env.FUSION_DASHBOARD_URL = originalDashboardUrl;
|
|
}
|
|
if (originalNodeEnv === undefined) {
|
|
delete process.env.NODE_ENV;
|
|
} else {
|
|
process.env.NODE_ENV = originalNodeEnv;
|
|
}
|
|
// Ensure we're in production mode for these tests
|
|
rendererMocks.isDevelopmentMode.mockReturnValue(false);
|
|
rendererMocks.getRendererUrl.mockReturnValue("file:///path/to/dist/client/index.html");
|
|
rendererMocks.getRendererFilePath.mockReturnValue("/path/to/dist/client/index.html");
|
|
rendererMocks.isUrlRenderer.mockReturnValue(false);
|
|
});
|
|
|
|
it("DASHBOARD_URL defaults to local file URL in production mode", async () => {
|
|
delete process.env.FUSION_DASHBOARD_URL;
|
|
|
|
const { DASHBOARD_URL } = await importMainModule();
|
|
|
|
expect(DASHBOARD_URL()).toMatch(/^file:\/\/.*\/client\/index\.html$/);
|
|
});
|
|
|
|
it("DASHBOARD_URL uses env override in development mode", async () => {
|
|
process.env.FUSION_DASHBOARD_URL = "http://localhost:5050";
|
|
// Mock development mode to use the env var
|
|
rendererMocks.isDevelopmentMode.mockReturnValue(true);
|
|
rendererMocks.getRendererUrl.mockReturnValue("http://localhost:5050");
|
|
rendererMocks.getRendererFilePath.mockReturnValue("");
|
|
rendererMocks.isUrlRenderer.mockReturnValue(true);
|
|
|
|
const { DASHBOARD_URL } = await importMainModule();
|
|
|
|
expect(DASHBOARD_URL()).toBe("http://localhost:5050");
|
|
});
|
|
|
|
it("createMainWindow creates BrowserWindow with secure preferences", async () => {
|
|
const { createMainWindow } = await importMainModule();
|
|
|
|
createMainWindow();
|
|
|
|
expect(mocks.BrowserWindow).toHaveBeenCalledTimes(1);
|
|
const [options] = mocks.BrowserWindow.mock.calls[0] as [
|
|
{
|
|
webPreferences: {
|
|
contextIsolation: boolean;
|
|
nodeIntegration: boolean;
|
|
preload: string;
|
|
};
|
|
},
|
|
];
|
|
|
|
expect(options.webPreferences.contextIsolation).toBe(true);
|
|
expect(options.webPreferences.nodeIntegration).toBe(false);
|
|
expect(options.webPreferences.preload).toContain("preload.js");
|
|
});
|
|
|
|
it("createMainWindow loads the renderer URL in URL mode", async () => {
|
|
rendererMocks.isUrlRenderer.mockReturnValue(true);
|
|
rendererMocks.getRendererUrl.mockReturnValue("http://localhost:3000/index.html");
|
|
rendererMocks.getRendererFilePath.mockReturnValue("");
|
|
|
|
const { createMainWindow } = await importMainModule();
|
|
|
|
createMainWindow();
|
|
|
|
expect(mocks.browserWindowInstance.loadURL).toHaveBeenCalledWith("http://localhost:3000/index.html");
|
|
expect(mocks.browserWindowInstance.loadFile).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("createMainWindow loads the renderer file in file mode (production)", async () => {
|
|
rendererMocks.isUrlRenderer.mockReturnValue(false);
|
|
rendererMocks.getRendererUrl.mockReturnValue("file:///path/to/dist/client/index.html");
|
|
rendererMocks.getRendererFilePath.mockReturnValue("/path/to/dist/client/index.html");
|
|
|
|
const { createMainWindow } = await importMainModule();
|
|
|
|
createMainWindow();
|
|
|
|
expect(mocks.browserWindowInstance.loadFile).toHaveBeenCalledWith("/path/to/dist/client/index.html");
|
|
expect(mocks.browserWindowInstance.loadURL).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("exports initializeApp for lifecycle orchestration", async () => {
|
|
const mainModule = await importMainModule();
|
|
|
|
expect(typeof mainModule.initializeApp).toBe("function");
|
|
});
|
|
|
|
it("createMainWindow registers close and closed handlers", async () => {
|
|
const { createMainWindow } = await importMainModule();
|
|
|
|
createMainWindow();
|
|
|
|
expect(mocks.browserWindowInstance.on).toHaveBeenCalledWith("close", expect.any(Function));
|
|
expect(mocks.browserWindowInstance.on).toHaveBeenCalledWith("closed", expect.any(Function));
|
|
});
|
|
|
|
it("importing main does not auto-start", async () => {
|
|
await importMainModule();
|
|
|
|
expect(mocks.app.whenReady).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("exports run for app entrypoint wiring", async () => {
|
|
const mainModule = await importMainModule();
|
|
|
|
expect(typeof mainModule.run).toBe("function");
|
|
});
|
|
});
|