Files
deepseek-harness/apps/desktop/tests/image-drag.spec.ts
T

89 lines
4.7 KiB
TypeScript

import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, expect, it, onTestFinished, vi } from 'vitest'
import type { BrowserWindow, IpcMainInvokeEvent, NativeImage, WebContents, WebFrameMain } from 'electron'
import { DESKTOP_IPC } from '../src/ipc.ts'
const electron = vi.hoisted(() => ({
handle: vi.fn(), removeHandler: vi.fn(), getPath: vi.fn(), getFileIcon: vi.fn(), createFromPath: vi.fn(),
}))
vi.mock('electron', () => ({
app: electron,
ipcMain: electron,
nativeImage: { createFromPath: electron.createFromPath },
}))
const { installDesktopImageDrag } = await import('../src/image-drag.ts')
afterEach(() => { vi.resetAllMocks() })
type Handler = (event: IpcMainInvokeEvent, ...args: unknown[]) => unknown
async function fixture() {
const root = await mkdtemp(join(tmpdir(), 'dsh-native-image-drag-'))
onTestFinished(async () => { await rm(root, { recursive: true, force: true }) })
electron.getPath.mockReturnValue(root)
const icon: NativeImage = Object.assign({} as NativeImage, { isEmpty: () => false, resize: () => icon })
electron.getFileIcon.mockResolvedValue(icon)
const frame = Object.assign({} as WebFrameMain, { url: 'dsh-app://app/' })
const window = Object.assign({} as BrowserWindow, { isDestroyed: () => false,
webContents: Object.assign({} as WebContents, { mainFrame: frame, startDrag: vi.fn() }) })
let current: BrowserWindow | undefined = window
const dispose = installDesktopImageDrag(() => current)
onTestFinished(dispose)
const handlers = new Map<string, Handler>(electron.handle.mock.calls.map(([channel, handler]) => [channel, handler]))
const event = Object.assign({} as IpcMainInvokeEvent, { sender: window.webContents, senderFrame: frame })
const call = (channel: string, ...args: unknown[]) => (handlers.get(channel) as Handler)(event, ...args)
return { root, window, frame, event, icon, call, dispose, detach: () => { current = undefined } }
}
it('stages original bytes before the gesture and synchronously starts an OS copy drag', async () => {
const f = await fixture()
const bytes = new Uint8Array([137, 80, 78, 71])
const handle = await f.call(DESKTOP_IPC.imageDragPrepare, bytes, 'original.png', 'image/png')
expect(f.window.webContents.startDrag).not.toHaveBeenCalled()
expect(f.call(DESKTOP_IPC.imageDragStart, handle)).toBe(true)
const [request] = f.window.webContents.startDrag.mock.calls[0] as [{ file: string; icon: NativeImage }]
expect(request.icon).toBe(f.icon)
expect(new Uint8Array(await readFile(request.file))).toEqual(bytes)
await f.call(DESKTOP_IPC.imageDragRelease, handle)
expect(new Uint8Array(await readFile(request.file))).toEqual(bytes)
expect(f.call(DESKTOP_IPC.imageDragStart, handle)).toBe(false)
await f.dispose()
expect(await readdir(f.root)).toEqual([])
})
it('rejects foreign senders, subframes, revoked handles and native failures', async () => {
const f = await fixture()
expect(f.call(DESKTOP_IPC.imageDragStart, 'foreign')).toBe(false)
const handle = await f.call(DESKTOP_IPC.imageDragPrepare, new Uint8Array([1]), 'x.png', 'image/png')
f.window.webContents.startDrag.mockImplementationOnce(() => { throw new Error('OS rejected drag') })
expect(f.call(DESKTOP_IPC.imageDragStart, handle)).toBe(false)
await vi.waitFor(async () => { expect(await readdir(f.root)).toEqual([]) })
f.frame.url = 'https://other.test/'
expect(() => f.call(DESKTOP_IPC.imageDragStart, handle)).toThrow('unowned renderer')
f.frame.url = 'dsh-app://app/'
Object.assign(f.event, { senderFrame: { url: 'dsh-app://app/' } })
expect(() => f.call(DESKTOP_IPC.imageDragStart, handle)).toThrow('Unowned image drag request')
Object.assign(f.event, { senderFrame: f.frame, sender: {} })
expect(() => f.call(DESKTOP_IPC.imageDragStart, handle)).toThrow('Unowned image drag request')
f.detach()
expect(() => f.call(DESKTOP_IPC.imageDragStart, handle)).toThrow('Unowned image drag request')
})
it('cancels icon preparation on shutdown without waiting for the icon service', async () => {
const f = await fixture()
const pending = Promise.withResolvers<NativeImage>()
electron.getFileIcon.mockReturnValue(pending.promise)
const prepare = f.call(DESKTOP_IPC.imageDragPrepare, new Uint8Array([1]), 'x.svg', 'image/svg+xml')
const rejected = expect(prepare).rejects.toThrow('Unowned image drag request')
await vi.waitFor(() => { expect(electron.getFileIcon).toHaveBeenCalledOnce() })
let stopped = false
const stopping = f.dispose().then(() => { stopped = true })
await rejected
await stopping
expect(stopped).toBe(true)
pending.resolve(f.icon)
await pending.promise
expect(f.window.webContents.startDrag).not.toHaveBeenCalled()
expect(await readdir(f.root)).toEqual([])
})