Files
DeskClaw/tests/unit/host-events.test.ts
2026-03-25 21:11:20 +08:00

77 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const addEventListenerMock = vi.fn();
const removeEventListenerMock = vi.fn();
const eventSourceMock = {
addEventListener: addEventListenerMock,
removeEventListener: removeEventListenerMock,
} as unknown as EventSource;
const createHostEventSourceMock = vi.fn(() => eventSourceMock);
vi.mock('@/lib/host-api', () => ({
createHostEventSource: () => createHostEventSourceMock(),
}));
describe('host-events', () => {
beforeEach(() => {
vi.resetAllMocks();
window.localStorage.clear();
});
it('subscribes through IPC for mapped host events', async () => {
const onMock = vi.mocked(window.electron.ipcRenderer.on);
const captured: Array<(...args: unknown[]) => void> = [];
const cleanupSpy = vi.fn();
onMock.mockImplementation((_, cb: (...args: unknown[]) => void) => {
captured.push(cb);
return cleanupSpy;
});
const { subscribeHostEvent } = await import('@/lib/host-events');
const handler = vi.fn();
const unsubscribe = subscribeHostEvent('gateway:status', handler);
expect(onMock).toHaveBeenCalledWith('gateway:status-changed', expect.any(Function));
expect(createHostEventSourceMock).not.toHaveBeenCalled();
captured[0]({ state: 'running' });
expect(handler).toHaveBeenCalledWith({ state: 'running' });
// unsubscribe should use the cleanup returned by ipc.on() — NOT ipc.off()
// which would pass the wrong function reference (see preload wrapper mismatch)
unsubscribe();
expect(cleanupSpy).toHaveBeenCalledTimes(1);
});
it('does not use SSE fallback by default for unknown events', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const { subscribeHostEvent } = await import('@/lib/host-events');
const unsubscribe = subscribeHostEvent('unknown:event', vi.fn());
expect(createHostEventSourceMock).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith(
'[host-events] no IPC mapping for event "unknown:event", SSE fallback disabled',
);
unsubscribe();
warnSpy.mockRestore();
});
it('uses SSE fallback only when explicitly enabled', async () => {
window.localStorage.setItem('clawx:allow-sse-fallback', '1');
const { subscribeHostEvent } = await import('@/lib/host-events');
const handler = vi.fn();
const unsubscribe = subscribeHostEvent('unknown:event', handler);
expect(createHostEventSourceMock).toHaveBeenCalledTimes(1);
expect(addEventListenerMock).toHaveBeenCalledWith('unknown:event', expect.any(Function));
const listener = addEventListenerMock.mock.calls[0][1] as (event: Event) => void;
listener({ data: JSON.stringify({ x: 1 }) } as unknown as Event);
expect(handler).toHaveBeenCalledWith({ x: 1 });
unsubscribe();
expect(removeEventListenerMock).toHaveBeenCalledWith('unknown:event', expect.any(Function));
});
});