import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; // Mock Electron modules before importing the module under test const mockGetSystemIdleTime = vi.fn(() => 0); const mockSend = vi.fn(); const mockGetMainWindow = vi.fn(() => ({ isDestroyed: () => false, webContents: { send: mockSend } })); vi.mock('electron', () => ({ powerMonitor: { getSystemIdleTime: mockGetSystemIdleTime } })); vi.mock('../window/create-window', () => ({ getMainWindow: mockGetMainWindow })); import { startIdleMonitor, stopIdleMonitor, getIdleState } from './idle-monitor'; describe('idle-monitor', () => { beforeEach(() => { vi.useFakeTimers(); mockGetSystemIdleTime.mockReturnValue(0); mockSend.mockClear(); }); afterEach(() => { stopIdleMonitor(); vi.useRealTimers(); }); it('returns active when idle time is below threshold', () => { mockGetSystemIdleTime.mockReturnValue(0); expect(getIdleState()).toBe('active'); }); it('returns idle when idle time exceeds 15 minutes', () => { mockGetSystemIdleTime.mockReturnValue(15 * 60); expect(getIdleState()).toBe('idle'); }); it('sends idle-state-changed to renderer when transitioning to idle', () => { startIdleMonitor(); mockGetSystemIdleTime.mockReturnValue(15 * 60); vi.advanceTimersByTime(10_000); expect(mockSend).toHaveBeenCalledWith('idle-state-changed', 'idle'); }); it('sends idle-state-changed to renderer when transitioning back to active', () => { startIdleMonitor(); // Go idle mockGetSystemIdleTime.mockReturnValue(15 * 60); vi.advanceTimersByTime(10_000); mockSend.mockClear(); // Go active mockGetSystemIdleTime.mockReturnValue(5); vi.advanceTimersByTime(10_000); expect(mockSend).toHaveBeenCalledWith('idle-state-changed', 'active'); }); it('does not fire duplicates when state stays the same', () => { startIdleMonitor(); mockGetSystemIdleTime.mockReturnValue(15 * 60); vi.advanceTimersByTime(10_000); vi.advanceTimersByTime(10_000); vi.advanceTimersByTime(10_000); // Only one transition, so only one call const idleCalls = mockSend.mock.calls.filter( ([channel, state]: [string, string]) => channel === 'idle-state-changed' && state === 'idle' ); expect(idleCalls.length).toBe(1); }); it('stops polling after stopIdleMonitor', () => { startIdleMonitor(); mockGetSystemIdleTime.mockReturnValue(15 * 60); vi.advanceTimersByTime(10_000); mockSend.mockClear(); stopIdleMonitor(); mockGetSystemIdleTime.mockReturnValue(0); vi.advanceTimersByTime(10_000); expect(mockSend).not.toHaveBeenCalled(); }); it('does not notify when main window is null', () => { mockGetMainWindow.mockReturnValue(null); startIdleMonitor(); mockGetSystemIdleTime.mockReturnValue(15 * 60); vi.advanceTimersByTime(10_000); expect(mockSend).not.toHaveBeenCalled(); mockGetMainWindow.mockReturnValue({ isDestroyed: () => false, webContents: { send: mockSend } }); }); });