diff --git a/apps/studio/lib/helpers.test.ts b/apps/studio/lib/helpers.test.ts index c3cd19dff73..defae2e2b43 100644 --- a/apps/studio/lib/helpers.test.ts +++ b/apps/studio/lib/helpers.test.ts @@ -1,3 +1,4 @@ +import { toast } from 'sonner' import { copyToClipboard } from 'ui' import { v4 as _uuidV4 } from 'uuid' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -31,6 +32,10 @@ import { uuidv4, } from './helpers' +vi.mock('sonner', () => ({ + toast: { error: vi.fn(), success: vi.fn() }, +})) + vi.mock('uuid', () => ({ v4: vi.fn(() => 'mocked-uuid'), })) @@ -218,6 +223,30 @@ describe('copyToClipboard', () => { await copyToClipboard('hello') expect(writeTextMock).toHaveBeenCalledWith('hello') }) + + it('resolves and reports when clipboard.write is denied', async () => { + writeMock.mockRejectedValue( + new DOMException("Failed to execute 'write' on 'Clipboard': Write permission denied.") + ) + const callback = vi.fn() + + const promise = copyToClipboard('hello', callback) + vi.runAllTimers() + + await expect(promise).resolves.toBeUndefined() + expect(callback).not.toHaveBeenCalled() + expect(toast.error).toHaveBeenCalledWith('Unable to copy to clipboard') + }) + + it('resolves and reports when writeText is denied', async () => { + writeTextMock.mockRejectedValue(new DOMException('Write permission denied.')) + vi.stubGlobal('navigator', { clipboard: { writeText: writeTextMock } }) + const callback = vi.fn() + + await expect(copyToClipboard('hello', callback)).resolves.toBeUndefined() + expect(callback).not.toHaveBeenCalled() + expect(toast.error).toHaveBeenCalledWith('Unable to copy to clipboard') + }) }) describe('detectBrowser', () => { diff --git a/packages/ui/src/lib/utils/clipboard.ts b/packages/ui/src/lib/utils/clipboard.ts index 58b80a4e382..8fe3b5e1154 100644 --- a/packages/ui/src/lib/utils/clipboard.ts +++ b/packages/ui/src/lib/utils/clipboard.ts @@ -30,20 +30,9 @@ export const copyToClipboard = async (str: ClipboardText, callback = noop) => { 'text/plain': Promise.resolve(str).then((text) => new Blob([text], { type: 'text/plain' })), }) - let resolve = () => {} - let reject = () => {} - const promise = new Promise((res, rej) => { - resolve = res - reject = rej - }) - // Safari also seems to require that the promise resolve soon after the - // clipboard write call, adding a setTimeout with 0 delay seems to work. - // Returning the promise to ensure the caller can await the clipboard - // copy operation intuitively. - setTimeout(() => { - navigator.clipboard.write([text]).then(callback).then(resolve).catch(reject) - }, 0) - return promise + await navigator.clipboard.write([text]) + callback() + return } // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,