Files
deepseek-harness/packages/host/directory-picker-native/tests/win32-dialog.spec.ts
Huanqi Cao 089f4dfad8 feat(picker): open the Win32 folder dialog in-process over koffi
The modern IFileOpenDialog becomes the primary win32 tier: a koffi-driven
COM conversation on a worker_threads worker (the modal Show never blocks
the host event loop), per-monitor-v2 DPI via SetThreadDpiAwarenessContext,
and abort service by re-posting WM_CLOSE to the dialog thread's windows,
with terminate+unref as the last resort (Node cannot interrupt a thread
blocked in native code, and such a worker must never hold the process open).

The PowerShell chain stays as the fallback tier with its trigger widened
from ENOENT to any pwsh failure, closing the review-flagged PowerShell 6
regression (no WinForms: exit 1, not ENOENT, so 5.1 never ran).

Layering keeps per-file coverage honest on every host: pure sequencing and
the driver test against fakes anywhere; the bindings run against a mocked
koffi COM world (the session-persistence-jsonl technique); POSIX hosts
drive the real spawn plumbing to its koffi-load rejection; win32 hosts run
a real open-and-abort-close smoke. The smoke joins processBoundTests: a
worker blocked in a native modal wedges the threads pool's teardown, while
a fork contains it. The worker bundles as its own CJS tsdown entry
(workflow-workerthread's pattern; no TLA), and the host module is imported
statically so the node-half bundle stays chunk-free.

Built-plane and real-COM behavior verified on native Windows: standalone
probes for the source worker, the built CJS worker, and the driver's abort
path all open and close the real dialog.

Agent Notes: new implemented/feature/2026-08-02-win32-in-process-folder-dialog
(bilingual) owns the decision; the DPI note is re-scoped to the fallback tier
it now describes and its AutoUpgradeEnabled attribution corrected (.NET Core
3.0 rewrote FolderBrowserDialog; the opt-out arrived in .NET 6).
2026-08-05 00:31:43 +08:00

137 lines
6.0 KiB
TypeScript

/**
* Driver tests: the worker message protocol mapped onto the promise, the
* WM_CLOSE abort service (including the show-race retry and the terminate
* last resort) against fakes, plus the real spawn plumbing — POSIX hosts
* prove the default path rejects cleanly (koffi cannot load ole32 there),
* and win32 hosts briefly open and auto-abort a real dialog.
*/
import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import { pickWin32Directory, type Win32DialogInternals, type Win32DialogWorkerLike } from '../src/win32-dialog.ts'
import type { Win32DialogWorkerMessage } from '../src/win32-dialog-worker.ts'
class FakeWorker extends EventEmitter implements Win32DialogWorkerLike {
terminate = vi.fn(async () => 0)
post(message: Win32DialogWorkerMessage): void {
this.emit('message', message)
}
}
interface Harness {
worker: FakeWorker
internals: Win32DialogInternals
close: ReturnType<typeof vi.fn>
}
function harness(overrides: Partial<Win32DialogInternals> = {}): Harness {
const worker = new FakeWorker()
const close = vi.fn(async () => undefined)
return {
worker,
close,
internals: { spawnWorker: () => worker, closeThreadWindows: close, closeRetryMs: 1, ...overrides },
}
}
const live = (): AbortSignal => new AbortController().signal
describe('pickWin32Directory', () => {
it('resolves the selected path and the cancellation null', async () => {
const first = harness()
const picked = pickWin32Directory(live(), first.internals)
first.worker.post({ kind: 'showing', threadId: 7 })
first.worker.post({ kind: 'done', path: 'C:\\picked' })
await expect(picked).resolves.toBe('C:\\picked')
expect(first.close).not.toHaveBeenCalled()
const second = harness()
const cancelled = pickWin32Directory(live(), second.internals)
second.worker.post({ kind: 'done', path: null })
await expect(cancelled).resolves.toBeNull()
})
it('rejects on a reported dialog failure, a worker crash, and a silent exit', async () => {
const reported = harness()
const failing = pickWin32Directory(live(), reported.internals)
reported.worker.post({ kind: 'error', message: 'CoCreateInstance failed' })
await expect(failing).rejects.toThrow('win32 folder dialog failed: CoCreateInstance failed')
const crashed = harness()
const crashing = pickWin32Directory(live(), crashed.internals)
crashed.worker.emit('error', new Error('worker blew up'))
await expect(crashing).rejects.toThrow('worker blew up')
const silent = harness()
const exiting = pickWin32Directory(live(), silent.internals)
silent.worker.emit('exit', 0)
await expect(exiting).rejects.toThrow('exited before reporting a result')
})
it('settles once: a late exit after the result is inert', async () => {
const { worker, internals } = harness()
const picked = pickWin32Directory(live(), internals)
worker.post({ kind: 'done', path: 'C:\\once' })
worker.emit('exit', 0)
await expect(picked).resolves.toBe('C:\\once')
})
it('throws immediately on an already-aborted signal without spawning', async () => {
const spawnWorker = vi.fn()
const controller = new AbortController()
controller.abort()
await expect(pickWin32Directory(controller.signal, { spawnWorker, closeThreadWindows: async () => undefined }))
.rejects.toThrow('native directory picker aborted')
expect(spawnWorker).not.toHaveBeenCalled()
})
it('services an abort by closing the dialog thread windows until the worker reports', async () => {
const { worker, internals, close } = harness()
const controller = new AbortController()
const picked = pickWin32Directory(controller.signal, internals)
worker.post({ kind: 'showing', threadId: 99 })
controller.abort()
await vi.waitFor(() =>{ expect(close).toHaveBeenCalledWith(99) })
worker.post({ kind: 'done', path: null })
await expect(picked).rejects.toThrow('native directory picker aborted')
})
it('starts the close service on the showing notice when the abort came first', async () => {
const closeFailures = vi.fn(async () => { throw new Error('window not there yet') })
const { worker, internals } = harness({ closeThreadWindows: closeFailures })
const controller = new AbortController()
const picked = pickWin32Directory(controller.signal, internals)
controller.abort()
expect(closeFailures).not.toHaveBeenCalled()
worker.post({ kind: 'showing', threadId: 12 })
await vi.waitFor(() =>{ expect(closeFailures.mock.calls.length).toBeGreaterThan(1) })
worker.post({ kind: 'done', path: null })
await expect(picked).rejects.toThrow('native directory picker aborted')
})
it('terminates an unresponsive worker after the close budget', async () => {
const { worker, internals, close } = harness()
const controller = new AbortController()
const picked = pickWin32Directory(controller.signal, internals)
worker.post({ kind: 'showing', threadId: 5 })
controller.abort()
await expect(picked).rejects.toThrow('dialog unresponsive; worker terminated')
expect(worker.terminate).toHaveBeenCalledOnce()
expect(close.mock.calls.length).toBeGreaterThan(10)
})
// POSIX hosts exercise the REAL default plumbing end to end: the tsx-bootstrapped
// worker spawns, loads koffi, fails to load ole32.dll, and reports the error.
it.skipIf(process.platform === 'win32')('rejects through the real worker where the Win32 surface is unavailable', async () => {
await expect(pickWin32Directory(live())).rejects.toThrow('win32 folder dialog failed')
}, 30_000)
// win32 hosts run the true COM smoke instead: a real dialog opens briefly
// and the abort service closes it (the same lever a disconnecting client pulls).
it.skipIf(process.platform !== 'win32')('opens and abort-closes a real dialog', async () => {
const controller = new AbortController()
setTimeout(() =>{ controller.abort() }, 400)
await expect(pickWin32Directory(controller.signal)).rejects.toThrow('native directory picker aborted')
}, 30_000)
})