fix(workspace-cleanup): stop progress after renderer teardown

This commit is contained in:
Brennan Benson
2026-08-13 04:08:59 -07:00
parent 60614cc6fe
commit 414044a218
2 changed files with 43 additions and 5 deletions
@@ -2,10 +2,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ipcMain } from 'electron'
import type { Store } from '../persistence'
const { persistScanResultMock, readScanSnapshotMock } = vi.hoisted(() => ({
persistScanResultMock: vi.fn(),
readScanSnapshotMock: vi.fn()
}))
const { persistScanResultMock, readScanSnapshotMock, scanWorkspaceCleanupMock } = vi.hoisted(
() => ({
persistScanResultMock: vi.fn(),
readScanSnapshotMock: vi.fn(),
scanWorkspaceCleanupMock: vi.fn()
})
)
const { beginPruneBatchMock, finishPruneBatchMock, recordPruneMock } = vi.hoisted(() => ({
beginPruneBatchMock: vi.fn(),
@@ -25,6 +28,10 @@ vi.mock('../workspace-cleanup-scan-snapshot', () => ({
readWorkspaceCleanupScanSnapshot: readScanSnapshotMock
}))
vi.mock('./workspace-cleanup-scan', () => ({
scanWorkspaceCleanup: scanWorkspaceCleanupMock
}))
vi.mock('../workspace-cleanup-removal-snapshot-prune', () => ({
beginWorkspaceCleanupRemovalSnapshotPruneBatch: beginPruneBatchMock,
finishWorkspaceCleanupRemovalSnapshotPruneBatch: finishPruneBatchMock,
@@ -50,6 +57,18 @@ describe('workspace cleanup snapshot IPC', () => {
vi.mocked(ipcMain.handle).mockClear()
persistScanResultMock.mockReset().mockResolvedValue(undefined)
readScanSnapshotMock.mockReset()
scanWorkspaceCleanupMock.mockReset().mockImplementation(async (_store, args, options) => {
const result = { scannedAt: NOW, candidates: [], errors: [] }
options.onProgress?.({
scanId: args.scanId,
scannedAt: NOW,
scannedWorktreeCount: 0,
totalWorktreeCount: 0,
candidates: [],
errors: []
})
return result
})
beginPruneBatchMock.mockReset()
finishPruneBatchMock.mockReset().mockResolvedValue(undefined)
recordPruneMock.mockReset()
@@ -78,6 +97,21 @@ describe('workspace cleanup snapshot IPC', () => {
expect(persistScanResultMock).not.toHaveBeenCalled()
})
it('stops streaming progress after the invoking renderer is destroyed', async () => {
registerWorkspaceCleanupHandlers(makeEmptyStore())
const handler = vi
.mocked(ipcMain.handle)
.mock.calls.find(([channel]) => channel === 'workspaceCleanup:scan')?.[1]
const send = vi.fn(() => {
throw new Error('Object has been destroyed')
})
await expect(
handler?.({ sender: { isDestroyed: () => true, send } } as never, { scanId: 'scan-1' })
).resolves.toEqual({ scannedAt: NOW, candidates: [], errors: [] })
expect(send).not.toHaveBeenCalled()
})
it('serves the cached scan snapshot through getCachedScan', async () => {
const snapshot = { scannedAt: NOW, candidates: [], errors: [] }
readScanSnapshotMock.mockResolvedValue(snapshot)
+5 -1
View File
@@ -53,7 +53,11 @@ export function registerWorkspaceCleanupHandlers(
const scanArgs = args ?? {}
const result = await scanWorkspaceCleanup(store, scanArgs, {
onProgress: scanArgs.scanId
? (progress) => event.sender.send('workspaceCleanup:scanProgress', progress)
? (progress) => {
if (!event.sender.isDestroyed()) {
event.sender.send('workspaceCleanup:scanProgress', progress)
}
}
: undefined
})
// Focused scans are live-only; persisting each rewrites and fsyncs the fleet snapshot.