diff --git a/frontend/src/composables/__tests__/useClipboard.spec.ts b/frontend/src/composables/__tests__/useClipboard.spec.ts index 3d1ffb05f9..f287d6a36f 100644 --- a/frontend/src/composables/__tests__/useClipboard.spec.ts +++ b/frontend/src/composables/__tests__/useClipboard.spec.ts @@ -126,6 +126,21 @@ describe('useClipboard', () => { expect(document.execCommand).toHaveBeenCalledWith('copy') }) + it.each([false, true])('reports a thrown fallback error (secure context: %s)', async (secure) => { + Object.defineProperty(window, 'isSecureContext', { value: secure, writable: true }) + vi.mocked(navigator.clipboard.writeText).mockRejectedValue(new Error('Clipboard denied')) + Object.defineProperty(document, 'execCommand', { + configurable: true, + value: vi.fn(() => { throw new DOMException('Copy denied', 'NotAllowedError') }) + }) + const { copyToClipboard, copied } = useClipboard() + await expect(copyToClipboard('text')).resolves.toBe(false) + expect(copied.value).toBe(false) + expect(mockShowError).toHaveBeenCalledWith('common.copyFailed') + expect(mockShowSuccess).not.toHaveBeenCalled() + expect(document.querySelector('textarea')).toBeNull() + }) + it('所有复制方式均失败时调用 showError', async () => { const writeTextMock = navigator.clipboard.writeText as any writeTextMock.mockRejectedValue(new Error('fail')) diff --git a/frontend/src/composables/useClipboard.ts b/frontend/src/composables/useClipboard.ts index b430c7cec4..d17598cc6f 100644 --- a/frontend/src/composables/useClipboard.ts +++ b/frontend/src/composables/useClipboard.ts @@ -26,6 +26,8 @@ function fallbackCopy(text: string): boolean { textarea.setSelectionRange(0, textarea.value.length) try { return document.execCommand('copy') + } catch { + return false } finally { document.body.removeChild(textarea) }