agent-ecosystem/test/main/ipc/window.test.ts

117 lines
3.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const electronMock = vi.hoisted(() => ({
app: {
quit: vi.fn(),
relaunch: vi.fn(),
},
BrowserWindow: {
fromWebContents: vi.fn(),
getFocusedWindow: vi.fn(),
getAllWindows: vi.fn(),
},
}));
vi.mock('electron', () => electronMock);
vi.mock('@shared/utils/logger', () => ({
createLogger: () => ({
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
}),
}));
import { app, BrowserWindow } from 'electron';
import { registerWindowHandlers, removeWindowHandlers } from '@main/ipc/window';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
type WindowHandler = (event: IpcMainInvokeEvent, ...args: unknown[]) => unknown;
function createMockIpcMain(): IpcMain & {
invoke: (channel: string, event?: Partial<IpcMainInvokeEvent>) => Promise<unknown>;
} {
const handlers = new Map<string, WindowHandler>();
const ipcMain = {
handle: vi.fn((channel: string, handler: WindowHandler) => {
handlers.set(channel, handler);
}),
removeHandler: vi.fn((channel: string) => {
handlers.delete(channel);
}),
invoke: async (channel: string, event: Partial<IpcMainInvokeEvent> = {}) => {
const handler = handlers.get(channel);
if (!handler) throw new Error(`No handler for ${channel}`);
return await Promise.resolve(handler(event as IpcMainInvokeEvent));
},
};
return ipcMain as unknown as IpcMain & {
invoke: (channel: string, event?: Partial<IpcMainInvokeEvent>) => Promise<unknown>;
};
}
function createMockWindow() {
return {
isDestroyed: vi.fn(() => false),
minimize: vi.fn(),
maximize: vi.fn(),
unmaximize: vi.fn(),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
};
}
describe('window IPC handlers', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(BrowserWindow.fromWebContents).mockReturnValue(null);
vi.mocked(BrowserWindow.getFocusedWindow).mockReturnValue(null);
vi.mocked(BrowserWindow.getAllWindows).mockReturnValue([]);
});
it('quits the app when the custom close control is clicked', async () => {
const ipcMain = createMockIpcMain();
registerWindowHandlers(ipcMain);
await ipcMain.invoke('window:close');
expect(app.quit).toHaveBeenCalledTimes(1);
});
it('relaunches through app.quit so shutdown cleanup can run', async () => {
const ipcMain = createMockIpcMain();
registerWindowHandlers(ipcMain);
await ipcMain.invoke('app:relaunch');
expect(app.relaunch).toHaveBeenCalledTimes(1);
expect(app.quit).toHaveBeenCalledTimes(1);
});
it('uses the window that sent the IPC event for window-specific controls', async () => {
const ipcMain = createMockIpcMain();
const senderWindow = createMockWindow();
const focusedWindow = createMockWindow();
const sender = {};
vi.mocked(BrowserWindow.fromWebContents).mockReturnValue(senderWindow as never);
vi.mocked(BrowserWindow.getFocusedWindow).mockReturnValue(focusedWindow as never);
registerWindowHandlers(ipcMain);
await ipcMain.invoke('window:minimize', { sender } as Partial<IpcMainInvokeEvent>);
expect(senderWindow.minimize).toHaveBeenCalledTimes(1);
expect(focusedWindow.minimize).not.toHaveBeenCalled();
});
it('removes registered handlers during shutdown cleanup', () => {
const ipcMain = createMockIpcMain();
registerWindowHandlers(ipcMain);
removeWindowHandlers(ipcMain);
expect(ipcMain.removeHandler).toHaveBeenCalledWith('window:close');
expect(ipcMain.removeHandler).toHaveBeenCalledWith('app:relaunch');
});
});