Files
deepseek-harness/packages/host/directory-picker-native/tests/win32-dialog-logic.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

91 lines
3.5 KiB
TypeScript

/**
* The COM conversation's sequencing against fake bindings: outcome mapping
* (selection / cancellation / HRESULT failures at every step) and the
* release-on-every-path guarantee, all platform-independent.
*/
import { describe, expect, it, vi } from 'vitest'
import {
FOS_FORCEFILESYSTEM, FOS_NOCHANGEDIR, FOS_PICKFOLDERS, HRESULT_CANCELLED,
runFolderDialog, type Win32DialogBindings, type Win32FolderDialog,
} from '../src/win32-dialog-logic.ts'
const E_FAIL = 0x80004005 | 0
interface FakeWorld {
bindings: Win32DialogBindings
dpi: ReturnType<typeof vi.fn>
createDialog: ReturnType<typeof vi.fn>
dialog: {
setOptions: ReturnType<typeof vi.fn>
setTitle: ReturnType<typeof vi.fn>
show: ReturnType<typeof vi.fn>
resultPath: ReturnType<typeof vi.fn>
release: ReturnType<typeof vi.fn>
}
}
function world(overrides: Partial<Win32FolderDialog> = {}, coInit = 0): FakeWorld {
const dialog = {
setOptions: vi.fn(() => 0),
setTitle: vi.fn(() => 0),
show: vi.fn(() => 0),
resultPath: vi.fn(() => ({ hr: 0, path: 'C:\\picked\\目录' })),
release: vi.fn(),
...overrides,
}
const dpi = vi.fn()
const createDialog = vi.fn(() => dialog)
const bindings: Win32DialogBindings = {
setThreadDpiAwareness: dpi,
coInitializeSta: vi.fn(() => coInit),
createFolderDialog: createDialog,
currentThreadId: vi.fn(() => 4242),
}
return { bindings, dpi, createDialog, dialog: dialog as FakeWorld['dialog'] }
}
describe('runFolderDialog', () => {
it('sequences DPI, STA, options, title, show, and result extraction', () => {
const { bindings, dpi, dialog } = world()
const showing = vi.fn()
expect(runFolderDialog(bindings, 'Pick', showing)).toBe('C:\\picked\\目录')
expect(dpi).toHaveBeenCalledOnce()
expect(dialog.setOptions).toHaveBeenCalledWith(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR)
expect(dialog.setTitle).toHaveBeenCalledWith('Pick')
expect(showing).toHaveBeenCalledWith(4242)
expect(showing.mock.invocationCallOrder[0]).toBeLessThan(dialog.show.mock.invocationCallOrder[0] as number)
expect(dialog.release).toHaveBeenCalledOnce()
})
it('maps the cancelled HRESULT to null and still releases the dialog', () => {
const { bindings, dialog } = world({ show: vi.fn(() => HRESULT_CANCELLED) })
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBeNull()
expect(dialog.resultPath).not.toHaveBeenCalled()
expect(dialog.release).toHaveBeenCalledOnce()
})
it('accepts the S_FALSE re-entry HRESULT from CoInitializeEx', () => {
const { bindings } = world({}, 1)
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBe('C:\\picked\\目录')
})
it('throws on a failing CoInitializeEx without creating a dialog', () => {
const { bindings, createDialog } = world({}, E_FAIL)
expect(() => runFolderDialog(bindings, 'Pick', vi.fn())).toThrow('CoInitializeEx failed: HRESULT 0x80004005')
expect(createDialog).not.toHaveBeenCalled()
})
it.each([
['SetOptions', { setOptions: vi.fn(() => E_FAIL) }],
['SetTitle', { setTitle: vi.fn(() => E_FAIL) }],
['Show', { show: vi.fn(() => E_FAIL) }],
['GetResult', { resultPath: vi.fn(() => ({ hr: E_FAIL })) }],
] satisfies [string, Partial<Win32FolderDialog>][])('releases the dialog when %s fails', (what, overrides) => {
const { bindings, dialog } = world(overrides)
expect(() => runFolderDialog(bindings, 'Pick', vi.fn())).toThrow(`${what} failed: HRESULT 0x80004005`)
expect(dialog.release).toHaveBeenCalledOnce()
void bindings
})
})