This commit is contained in:
2026-07-12 12:20:23 +08:00
parent 7f2d5579ff
commit f923791d39
252 changed files with 9872 additions and 647 deletions
@@ -6,15 +6,45 @@
* @throws {Error} unable to copy text to clipboard
*/
export const copyTextToClipboard = async data => {
const text =
typeof data === 'object' && data !== null
? JSON.stringify(data, null, 2)
: String(data ?? '');
let clipboardError;
try {
const text =
typeof data === 'object' && data !== null
? JSON.stringify(data, null, 2)
: String(data ?? '');
if (!navigator.clipboard?.writeText) {
throw new Error('Clipboard API is unavailable');
}
await navigator.clipboard.writeText(text);
return;
} catch (error) {
throw new Error(`Unable to copy text to clipboard: ${error.message}`);
clipboardError = error;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.pointerEvents = 'none';
document.body.appendChild(textarea);
try {
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
if (!document.execCommand?.('copy')) {
throw new Error('Fallback copy command failed');
}
} catch {
throw new Error(
`Unable to copy text to clipboard: ${clipboardError.message}`
);
} finally {
textarea.remove();
}
};
@@ -1,15 +1,21 @@
import { copyTextToClipboard, handleOtpPaste } from '../clipboard';
const mockWriteText = vi.fn();
Object.assign(navigator, {
clipboard: {
writeText: mockWriteText,
},
});
const mockExecCommand = vi.fn();
const setClipboard = clipboard => {
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
value: clipboard,
});
};
describe('copyTextToClipboard', () => {
beforeEach(() => {
vi.clearAllMocks();
setClipboard({ writeText: mockWriteText });
document.execCommand = mockExecCommand;
mockExecCommand.mockReturnValue(true);
});
describe('with string input', () => {
@@ -133,23 +139,38 @@ describe('copyTextToClipboard', () => {
it('throws error when clipboard API fails', async () => {
const error = new Error('Clipboard access denied');
mockWriteText.mockRejectedValueOnce(error);
mockExecCommand.mockReturnValueOnce(false);
await expect(copyTextToClipboard('test')).rejects.toThrow(
'Unable to copy text to clipboard: Clipboard access denied'
);
});
it('handles clipboard API not available', async () => {
// Temporarily remove clipboard API
const originalClipboard = navigator.clipboard;
delete navigator.clipboard;
it('falls back when clipboard API is not available', async () => {
setClipboard(undefined);
await copyTextToClipboard('test');
expect(mockExecCommand).toHaveBeenCalledWith('copy');
expect(document.querySelector('textarea')).toBeNull();
});
it('falls back when clipboard API rejects the request', async () => {
mockWriteText.mockRejectedValueOnce(new Error('Clipboard access denied'));
await copyTextToClipboard('test');
expect(mockExecCommand).toHaveBeenCalledWith('copy');
expect(document.querySelector('textarea')).toBeNull();
});
it('throws when neither clipboard method is available', async () => {
setClipboard(undefined);
mockExecCommand.mockReturnValueOnce(false);
await expect(copyTextToClipboard('test')).rejects.toThrow(
'Unable to copy text to clipboard:'
'Unable to copy text to clipboard: Clipboard API is unavailable'
);
// Restore clipboard API
navigator.clipboard = originalClipboard;
});
});