mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
test(core): add missing copyTextToClipboard unit tests to restore 100% TS coverage
The `copy.ts` utility was added in #39246 without a corresponding test file, dropping the `core-packages-ts` codecov target below 100%. Adds 8 tests covering all branches: Clipboard API (Safari + non-Safari), ClipboardItem fallback, execCommand fallback paths, removeRange vs removeAllRanges, and null-selection guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { copyTextToClipboard } from '@superset-ui/core';
|
||||
|
||||
const SAFARI_UA =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15';
|
||||
const CHROME_UA =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
|
||||
const makeGetText = (text: string) => () => Promise.resolve(text);
|
||||
|
||||
test('uses Clipboard API writeText on non-Safari browsers', async () => {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: CHROME_UA,
|
||||
configurable: true,
|
||||
});
|
||||
const writeText = jest.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: { writeText },
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
await copyTextToClipboard(makeGetText('hello'));
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith('hello');
|
||||
});
|
||||
|
||||
test('uses ClipboardItem API on Safari browsers', async () => {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: SAFARI_UA,
|
||||
configurable: true,
|
||||
});
|
||||
const write = jest.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: { write },
|
||||
configurable: true,
|
||||
});
|
||||
const MockClipboardItem = jest.fn().mockImplementation(data => ({ data }));
|
||||
(global as any).ClipboardItem = MockClipboardItem;
|
||||
|
||||
await copyTextToClipboard(makeGetText('safari text'));
|
||||
|
||||
expect(MockClipboardItem).toHaveBeenCalled();
|
||||
expect(write).toHaveBeenCalledWith([expect.anything()]);
|
||||
|
||||
delete (global as any).ClipboardItem;
|
||||
});
|
||||
|
||||
test('falls back to writeText on Safari when ClipboardItem write fails', async () => {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: SAFARI_UA,
|
||||
configurable: true,
|
||||
});
|
||||
const writeText = jest.fn().mockResolvedValue(undefined);
|
||||
const write = jest.fn().mockRejectedValue(new Error('not supported'));
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: { write, writeText },
|
||||
configurable: true,
|
||||
});
|
||||
const MockClipboardItem = jest.fn().mockImplementation(data => ({ data }));
|
||||
(global as any).ClipboardItem = MockClipboardItem;
|
||||
|
||||
await copyTextToClipboard(makeGetText('fallback text'));
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith('fallback text');
|
||||
|
||||
delete (global as any).ClipboardItem;
|
||||
});
|
||||
|
||||
function mockExecCommand(impl: (cmd: string) => boolean) {
|
||||
Object.defineProperty(document, 'execCommand', {
|
||||
value: jest.fn().mockImplementation(impl),
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function setupFallbackMocks(options: {
|
||||
selection: Partial<Selection> | null;
|
||||
}) {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: CHROME_UA,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: {
|
||||
writeText: jest.fn().mockRejectedValue(new Error('not allowed')),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const mockRange = { selectNode: jest.fn() };
|
||||
const mockSpan = {
|
||||
style: {} as CSSStyleDeclaration,
|
||||
textContent: '',
|
||||
} as unknown as HTMLSpanElement;
|
||||
|
||||
jest
|
||||
.spyOn(document, 'getSelection')
|
||||
.mockReturnValue(options.selection as Selection | null);
|
||||
jest.spyOn(document, 'createRange').mockReturnValue(mockRange as unknown as Range);
|
||||
jest.spyOn(document, 'createElement').mockReturnValue(mockSpan);
|
||||
jest.spyOn(document.body, 'appendChild').mockImplementation(() => mockSpan);
|
||||
jest.spyOn(document.body, 'removeChild').mockImplementation(() => mockSpan);
|
||||
|
||||
return { mockRange, mockSpan };
|
||||
}
|
||||
|
||||
test('falls back to execCommand copy when Clipboard API is unavailable', async () => {
|
||||
const removeRange = jest.fn();
|
||||
const { mockRange } = setupFallbackMocks({
|
||||
selection: {
|
||||
removeAllRanges: jest.fn(),
|
||||
addRange: jest.fn(),
|
||||
removeRange,
|
||||
},
|
||||
});
|
||||
mockExecCommand(cmd => cmd === 'copy');
|
||||
|
||||
await copyTextToClipboard(makeGetText('exec text'));
|
||||
|
||||
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||
expect(removeRange).toHaveBeenCalledWith(mockRange);
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('falls back to removeAllRanges when removeRange is not available', async () => {
|
||||
const removeAllRanges = jest.fn();
|
||||
setupFallbackMocks({
|
||||
selection: {
|
||||
removeAllRanges,
|
||||
addRange: jest.fn(),
|
||||
removeRange: undefined,
|
||||
},
|
||||
});
|
||||
mockExecCommand(cmd => cmd === 'copy');
|
||||
|
||||
await copyTextToClipboard(makeGetText('no removeRange'));
|
||||
|
||||
expect(removeAllRanges).toHaveBeenCalled();
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('rejects when execCommand returns false', async () => {
|
||||
setupFallbackMocks({
|
||||
selection: {
|
||||
removeAllRanges: jest.fn(),
|
||||
addRange: jest.fn(),
|
||||
removeRange: jest.fn(),
|
||||
},
|
||||
});
|
||||
mockExecCommand(() => false);
|
||||
|
||||
await expect(copyTextToClipboard(makeGetText('fail'))).rejects.toBeUndefined();
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('rejects when execCommand throws', async () => {
|
||||
setupFallbackMocks({
|
||||
selection: {
|
||||
removeAllRanges: jest.fn(),
|
||||
addRange: jest.fn(),
|
||||
removeRange: jest.fn(),
|
||||
},
|
||||
});
|
||||
Object.defineProperty(document, 'execCommand', {
|
||||
value: jest.fn().mockImplementation(() => {
|
||||
throw new Error('execCommand error');
|
||||
}),
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
await expect(copyTextToClipboard(makeGetText('throw'))).rejects.toBeUndefined();
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('resolves without copying when getSelection returns null', async () => {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
value: CHROME_UA,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: {
|
||||
writeText: jest.fn().mockRejectedValue(new Error('not allowed')),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
jest.spyOn(document, 'getSelection').mockReturnValue(null);
|
||||
|
||||
await expect(copyTextToClipboard(makeGetText('no selection'))).resolves.toBeUndefined();
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
Reference in New Issue
Block a user