-
Notifications
You must be signed in to change notification settings - Fork 262
fix: quit handler cleanup ordering and timeout test coverage #689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -299,8 +299,13 @@ describe('app-lifecycle/quit-handler', () => { | |
| expect(mockHistoryManager.stopWatching).toHaveBeenCalled(); | ||
| expect(deps.stopCliWatcher).toHaveBeenCalled(); | ||
| expect(deps.stopSessionCleanup).toHaveBeenCalled(); | ||
| expect(mockPowerManager.clearAllReasons).toHaveBeenCalled(); | ||
| expect(mockProcessManager.killAll).toHaveBeenCalled(); | ||
| // clearAllReasons must be called AFTER killAll to prevent late process | ||
| // output from re-arming the sleep blocker | ||
| expect(mockPowerManager.clearAllReasons).toHaveBeenCalled(); | ||
| const killOrder = mockProcessManager.killAll.mock.invocationCallOrder[0]; | ||
| const clearOrder = mockPowerManager.clearAllReasons.mock.invocationCallOrder[0]; | ||
| expect(killOrder).toBeLessThan(clearOrder); | ||
| expect(mockTunnelManager.stop).toHaveBeenCalled(); | ||
| expect(mockWebServer.stop).toHaveBeenCalled(); | ||
| expect(deps.closeStatsDB).toHaveBeenCalled(); | ||
|
|
@@ -366,6 +371,77 @@ describe('app-lifecycle/quit-handler', () => { | |
| expect(() => beforeQuitHandler!(mockEvent)).not.toThrow(); | ||
| }); | ||
|
|
||
| it('should force-quit after safety timeout if renderer never responds', async () => { | ||
| vi.useFakeTimers(); | ||
|
|
||
| const { createQuitHandler } = await import('../../../main/app-lifecycle/quit-handler'); | ||
|
|
||
| const quitHandler = createQuitHandler(deps as Parameters<typeof createQuitHandler>[0]); | ||
| quitHandler.setup(); | ||
|
|
||
| const mockEvent = { preventDefault: vi.fn() }; | ||
| beforeQuitHandler!(mockEvent); | ||
|
|
||
| // Renderer was asked for confirmation | ||
| expect(mockMainWindow.webContents.send).toHaveBeenCalledWith('app:requestQuitConfirmation'); | ||
| expect(mockQuit).not.toHaveBeenCalled(); | ||
|
|
||
| // Advance past the 5s timeout without renderer responding | ||
| vi.advanceTimersByTime(5000); | ||
|
|
||
| expect(mockQuit).toHaveBeenCalled(); | ||
| expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('timed out'), 'Window'); | ||
|
|
||
| vi.useRealTimers(); | ||
| }); | ||
|
Comment on lines
+375
to
+396
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The same pattern appears in all three new tests (lines 374–395, 398–421, 423–443). Consider centralising the cleanup in an afterEach(() => {
vi.useRealTimers(); // idempotent when timers are already real
vi.restoreAllMocks();
});Then the individual |
||
|
|
||
| it('should clear safety timeout when renderer confirms quit', async () => { | ||
| vi.useFakeTimers(); | ||
|
|
||
| const { createQuitHandler } = await import('../../../main/app-lifecycle/quit-handler'); | ||
|
|
||
| const quitHandler = createQuitHandler(deps as Parameters<typeof createQuitHandler>[0]); | ||
| quitHandler.setup(); | ||
|
|
||
| const mockEvent = { preventDefault: vi.fn() }; | ||
| beforeQuitHandler!(mockEvent); | ||
|
|
||
| // Renderer confirms before timeout | ||
| const confirmHandler = ipcHandlers.get('app:quitConfirmed')!; | ||
| confirmHandler(); | ||
|
|
||
| // mockQuit called once from confirmHandler | ||
| expect(mockQuit).toHaveBeenCalledTimes(1); | ||
|
|
||
| // Advance past timeout — should NOT trigger a second quit | ||
| vi.advanceTimersByTime(5000); | ||
| expect(mockQuit).toHaveBeenCalledTimes(1); | ||
|
|
||
| vi.useRealTimers(); | ||
| }); | ||
|
|
||
| it('should clear safety timeout when renderer cancels quit', async () => { | ||
| vi.useFakeTimers(); | ||
|
|
||
| const { createQuitHandler } = await import('../../../main/app-lifecycle/quit-handler'); | ||
|
|
||
| const quitHandler = createQuitHandler(deps as Parameters<typeof createQuitHandler>[0]); | ||
| quitHandler.setup(); | ||
|
|
||
| const mockEvent = { preventDefault: vi.fn() }; | ||
| beforeQuitHandler!(mockEvent); | ||
|
|
||
| // Renderer cancels | ||
| const cancelHandler = ipcHandlers.get('app:quitCancelled')!; | ||
| cancelHandler(); | ||
|
|
||
| // Advance past timeout — should NOT force quit | ||
| vi.advanceTimersByTime(5000); | ||
| expect(mockQuit).not.toHaveBeenCalled(); | ||
|
|
||
| vi.useRealTimers(); | ||
| }); | ||
|
|
||
| it('should work without stopCliWatcher dependency', async () => { | ||
| const depsWithoutCliWatcher = { ...deps }; | ||
| delete depsWithoutCliWatcher.stopCliWatcher; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5000is duplicated in all three new tests (lines 390, 417, 439) butQUIT_CONFIRMATION_TIMEOUT_MSis not exported fromquit-handler.ts. If the timeout is ever adjusted, the tests will silently diverge.Exporting the constant makes the coupling explicit: