|
|
@@ -38,6 +38,10 @@ interface ComWorld {
|
|
|
registered: number
|
|
|
unregistered: number
|
|
|
uninitialized: number
|
|
|
+ /** The synthesized keybd_event calls, in order. */
|
|
|
+ keyEvents: { vk: number; flags: number }[]
|
|
|
+ /** Cross-cutting call trace shared by keybd_event and the dialog Show slot. */
|
|
|
+ nativeOrder: string[]
|
|
|
}
|
|
|
|
|
|
function comWorld(overrides: Partial<ComWorld> = {}): ComWorld {
|
|
|
@@ -48,6 +52,7 @@ function comWorld(overrides: Partial<ComWorld> = {}): ComWorld {
|
|
|
titles: [], options: [], dpiContexts: [], freed: [], released: [], posted: [],
|
|
|
str16PointerSizes: [],
|
|
|
registered: 0, unregistered: 0, uninitialized: 0,
|
|
|
+ keyEvents: [], nativeOrder: [],
|
|
|
...overrides,
|
|
|
}
|
|
|
}
|
|
|
@@ -77,7 +82,7 @@ function installFakeKoffi(world: ComWorld, options: {
|
|
|
switch (slot) {
|
|
|
case 9: world.options.push(args[0] as number); return 0
|
|
|
case 17: world.titles.push(args[0] as string); return 0
|
|
|
- case 3: return world.showHr
|
|
|
+ case 3: world.nativeOrder.push('show'); return world.showHr
|
|
|
case 20: {
|
|
|
if (world.getResultHr < 0) return world.getResultHr
|
|
|
;(args[0] as unknown[])[0] = itemPtr
|
|
|
@@ -116,6 +121,10 @@ function installFakeKoffi(world: ComWorld, options: {
|
|
|
}
|
|
|
case 'CoTaskMemFree': return (ptr: unknown) => { world.freed.push(ptr) }
|
|
|
case 'GetCurrentThreadId': return () => 31337
|
|
|
+ case 'keybd_event': return (vk: number, _scan: number, flags: number, _extra: unknown) => {
|
|
|
+ world.keyEvents.push({ vk, flags })
|
|
|
+ world.nativeOrder.push(flags === 0 ? 'alt-down' : 'alt-up')
|
|
|
+ }
|
|
|
case 'SetThreadDpiAwarenessContext': {
|
|
|
if (!world.hasThreadDpi) throw new Error(`${dll}: SetThreadDpiAwarenessContext not found`)
|
|
|
return (context: unknown) => {
|
|
|
@@ -188,6 +197,14 @@ describe('loadWin32DialogBindings over the fake COM world', () => {
|
|
|
expect(world.titles).toEqual(['选择工作区目录'])
|
|
|
expect(world.options).toHaveLength(1)
|
|
|
expect(showing).toHaveBeenCalledWith(31337)
|
|
|
+ // One synthesized Alt press (down, then up) immediately precedes Show, so
|
|
|
+ // the dialog's activation attempt finds this process as the recent-input
|
|
|
+ // owner.
|
|
|
+ expect(world.keyEvents).toEqual([
|
|
|
+ { vk: 0x12, flags: 0 },
|
|
|
+ { vk: 0x12, flags: 2 },
|
|
|
+ ])
|
|
|
+ expect(world.nativeOrder.slice(-3)).toEqual(['alt-down', 'alt-up', 'show'])
|
|
|
expect(world.freed).toHaveLength(1)
|
|
|
expect(world.str16PointerSizes).toEqual([FAKE_POINTER_SIZE])
|
|
|
expect(world.released).toEqual(['item', 'dialog'])
|
|
|
@@ -244,6 +261,7 @@ describe('loadWin32DialogBindings over the fake COM world', () => {
|
|
|
const { loadWin32DialogBindings } = await loadBindingsModule()
|
|
|
const bindings = await loadWin32DialogBindings()
|
|
|
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBeNull()
|
|
|
+ expect(world.keyEvents).toHaveLength(2)
|
|
|
expect(world.released).toEqual(['dialog'])
|
|
|
expect(world.uninitialized).toBe(1)
|
|
|
})
|
|
|
@@ -357,6 +375,7 @@ describe('the worker entry over a mocked process boundary', () => {
|
|
|
coInitializeSta: () => 0,
|
|
|
coUninitialize: () => undefined,
|
|
|
currentThreadId: () => 11,
|
|
|
+ pressAltForForeground: () => undefined,
|
|
|
createFolderDialog: () => ({
|
|
|
setOptions: () => 0,
|
|
|
setTitle: () => 0,
|