fix: bound code-block clipboard copies

This commit is contained in:
lucaronin
2026-05-23 19:59:13 +02:00
parent 49008eb79c
commit 2df076f834
7 changed files with 324 additions and 143 deletions

View File

@@ -0,0 +1,66 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { invoke } from '@tauri-apps/api/core'
import { writeClipboardText } from './clipboardText'
vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn(),
}))
const originalClipboard = navigator.clipboard
const mockInvoke = vi.mocked(invoke)
function setClipboard(writeText: ReturnType<typeof vi.fn> | undefined) {
Object.defineProperty(window.navigator, 'clipboard', {
configurable: true,
value: writeText ? { writeText } : undefined,
})
}
describe('writeClipboardText', () => {
beforeEach(() => {
mockInvoke.mockReset()
;(globalThis as { isTauri?: boolean }).isTauri = false
Object.defineProperty(window.navigator, 'clipboard', {
configurable: true,
value: originalClipboard,
})
})
it('uses the Web Clipboard API when it is available', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
setClipboard(writeText)
await writeClipboardText('copy me')
expect(writeText).toHaveBeenCalledWith('copy me')
expect(mockInvoke).not.toHaveBeenCalled()
})
it('prefers the Web Clipboard API inside Tauri', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
setClipboard(writeText)
;(globalThis as { isTauri?: boolean }).isTauri = true
await writeClipboardText('copy me')
expect(writeText).toHaveBeenCalledWith('copy me')
expect(mockInvoke).not.toHaveBeenCalled()
})
it('falls back to the native command when Web Clipboard copy fails inside Tauri', async () => {
const writeText = vi.fn().mockRejectedValue(new Error('permission denied'))
setClipboard(writeText)
;(globalThis as { isTauri?: boolean }).isTauri = true
mockInvoke.mockResolvedValue(undefined)
await writeClipboardText('copy me')
expect(mockInvoke).toHaveBeenCalledWith('copy_text_to_clipboard', { text: 'copy me' })
})
it('reports unavailable clipboard support outside Tauri', async () => {
setClipboard(undefined)
await expect(writeClipboardText('copy me')).rejects.toThrow('Clipboard API is unavailable')
})
})

View File

@@ -0,0 +1,38 @@
import { invoke } from '@tauri-apps/api/core'
import { isTauri } from '../mock-tauri'
const NATIVE_COPY_TEXT_COMMAND = 'copy_text_to_clipboard'
type WebClipboardResult =
| { status: 'copied' }
| { status: 'failed'; error: unknown }
| { status: 'unavailable' }
function hasWebClipboard(): boolean {
return typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'
}
async function writeWebClipboardText(text: string): Promise<WebClipboardResult> {
if (!hasWebClipboard()) return { status: 'unavailable' }
try {
await navigator.clipboard.writeText(text)
return { status: 'copied' }
} catch (error) {
return { status: 'failed', error }
}
}
export async function writeClipboardText(text: string): Promise<void> {
const webResult = await writeWebClipboardText(text)
if (webResult.status === 'copied') return
if (isTauri()) {
await invoke(NATIVE_COPY_TEXT_COMMAND, { text })
return
}
if (webResult.status === 'failed') throw webResult.error
throw new Error('Clipboard API is unavailable')
}