Commit a185d1ee authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Remove deprecated copyText function

parent 1645a344
...@@ -17,7 +17,7 @@ import { isPrivate } from '../../helpers/permissions'; ...@@ -17,7 +17,7 @@ import { isPrivate } from '../../helpers/permissions';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import type { ToastMessengerService } from '../../services/toast-messenger'; import type { ToastMessengerService } from '../../services/toast-messenger';
import { useSidebarStore } from '../../store'; import { useSidebarStore } from '../../store';
import { copyText } from '../../util/copy-to-clipboard'; import { copyPlainText } from '../../util/copy-to-clipboard';
import MenuArrow from '../MenuArrow'; import MenuArrow from '../MenuArrow';
import ShareLinks from '../ShareLinks'; import ShareLinks from '../ShareLinks';
...@@ -94,9 +94,9 @@ function AnnotationShareControl({ ...@@ -94,9 +94,9 @@ function AnnotationShareControl({
// bears further discussion. // bears further discussion.
const showShareLinks = inContextAvailable; const showShareLinks = inContextAvailable;
const copyShareLink = () => { const copyShareLink = async () => {
try { try {
copyText(shareUri); await copyPlainText(shareUri);
toastMessenger.success('Copied share link to clipboard'); toastMessenger.success('Copied share link to clipboard');
} catch (err) { } catch (err) {
toastMessenger.error('Unable to copy link'); toastMessenger.error('Unable to copy link');
......
...@@ -66,7 +66,7 @@ describe('AnnotationShareControl', () => { ...@@ -66,7 +66,7 @@ describe('AnnotationShareControl', () => {
}; };
fakeCopyToClipboard = { fakeCopyToClipboard = {
copyText: sinon.stub(), copyPlainText: sinon.stub(),
}; };
fakeToastMessenger = { fakeToastMessenger = {
success: sinon.stub(), success: sinon.stub(),
...@@ -147,16 +147,16 @@ describe('AnnotationShareControl', () => { ...@@ -147,16 +147,16 @@ describe('AnnotationShareControl', () => {
getIconButton(wrapper, 'CopyIcon').props().onClick(); getIconButton(wrapper, 'CopyIcon').props().onClick();
assert.calledWith( assert.calledWith(
fakeCopyToClipboard.copyText, fakeCopyToClipboard.copyPlainText,
'https://www.example.com', 'https://www.example.com',
); );
}); });
it('confirms link copy when successful', () => { it('confirms link copy when successful', async () => {
const wrapper = createComponent(); const wrapper = createComponent();
openElement(wrapper); openElement(wrapper);
getIconButton(wrapper, 'CopyIcon').props().onClick(); await getIconButton(wrapper, 'CopyIcon').props().onClick();
assert.calledWith( assert.calledWith(
fakeToastMessenger.success, fakeToastMessenger.success,
...@@ -165,7 +165,7 @@ describe('AnnotationShareControl', () => { ...@@ -165,7 +165,7 @@ describe('AnnotationShareControl', () => {
}); });
it('flashes an error if link copying unsuccessful', () => { it('flashes an error if link copying unsuccessful', () => {
fakeCopyToClipboard.copyText.throws(); fakeCopyToClipboard.copyPlainText.throws();
const wrapper = createComponent(); const wrapper = createComponent();
openElement(wrapper); openElement(wrapper);
......
...@@ -8,7 +8,7 @@ import { withServices } from '../../service-context'; ...@@ -8,7 +8,7 @@ import { withServices } from '../../service-context';
import type { GroupsService } from '../../services/groups'; import type { GroupsService } from '../../services/groups';
import type { ToastMessengerService } from '../../services/toast-messenger'; import type { ToastMessengerService } from '../../services/toast-messenger';
import { useSidebarStore } from '../../store'; import { useSidebarStore } from '../../store';
import { copyText } from '../../util/copy-to-clipboard'; import { copyPlainText } from '../../util/copy-to-clipboard';
import MenuItem from '../MenuItem'; import MenuItem from '../MenuItem';
export type GroupListItemProps = { export type GroupListItemProps = {
...@@ -75,9 +75,9 @@ function GroupListItem({ ...@@ -75,9 +75,9 @@ function GroupListItem({
onExpand(!isExpanded); onExpand(!isExpanded);
}; };
const copyLink = (url: string) => { const copyLink = async (url: string) => {
try { try {
copyText(url); await copyPlainText(url);
toastMessenger.success(`Copied link for "${group.name}"`); toastMessenger.success(`Copied link for "${group.name}"`);
} catch (err) { } catch (err) {
toastMessenger.error('Unable to copy link'); toastMessenger.error('Unable to copy link');
......
...@@ -6,7 +6,7 @@ import GroupListItem, { $imports } from '../GroupListItem'; ...@@ -6,7 +6,7 @@ import GroupListItem, { $imports } from '../GroupListItem';
describe('GroupListItem', () => { describe('GroupListItem', () => {
let fakeConfirm; let fakeConfirm;
let fakeCopyText; let fakeCopyPlainText;
let fakeToastMessenger; let fakeToastMessenger;
let fakeGroupsService; let fakeGroupsService;
let fakeStore; let fakeStore;
...@@ -47,7 +47,7 @@ describe('GroupListItem', () => { ...@@ -47,7 +47,7 @@ describe('GroupListItem', () => {
leave: sinon.stub(), leave: sinon.stub(),
}; };
fakeCopyText = sinon.stub(); fakeCopyPlainText = sinon.stub();
function FakeMenuItem() { function FakeMenuItem() {
return null; return null;
...@@ -64,7 +64,7 @@ describe('GroupListItem', () => { ...@@ -64,7 +64,7 @@ describe('GroupListItem', () => {
$imports.$mock({ $imports.$mock({
'../MenuItem': FakeMenuItem, '../MenuItem': FakeMenuItem,
'../../util/copy-to-clipboard': { '../../util/copy-to-clipboard': {
copyText: fakeCopyText, copyPlainText: fakeCopyPlainText,
}, },
'../../helpers/group-list-item-common': fakeGroupListItemCommon, '../../helpers/group-list-item-common': fakeGroupListItemCommon,
'../../store': { useSidebarStore: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
...@@ -87,10 +87,10 @@ describe('GroupListItem', () => { ...@@ -87,10 +87,10 @@ describe('GroupListItem', () => {
); );
}; };
function clickMenuItem(wrapper, label) { async function clickMenuItem(wrapper, label) {
act(() => { await act(() =>
wrapper.find(`MenuItem[label="${label}"]`).props().onClick(); wrapper.find(`MenuItem[label="${label}"]`).props().onClick(),
}); );
wrapper.update(); wrapper.update();
} }
...@@ -359,22 +359,22 @@ describe('GroupListItem', () => { ...@@ -359,22 +359,22 @@ describe('GroupListItem', () => {
}); });
}); });
it('copies activity URL if "Copy link" action is clicked', () => { it('copies activity URL if "Copy link" action is clicked', async () => {
const wrapper = createGroupListItem(fakeGroup, { const wrapper = createGroupListItem(fakeGroup, {
isExpanded: true, isExpanded: true,
}); });
clickMenuItem(getSubmenu(wrapper), 'Copy invite link'); await clickMenuItem(getSubmenu(wrapper), 'Copy invite link');
assert.calledWith(fakeCopyText, 'https://annotate.com/groups/groupid'); assert.calledWith(fakeCopyPlainText, 'https://annotate.com/groups/groupid');
assert.calledWith(fakeToastMessenger.success, 'Copied link for "Test"'); assert.calledWith(fakeToastMessenger.success, 'Copied link for "Test"');
}); });
it('reports an error if "Copy link" action fails', () => { it('reports an error if "Copy link" action fails', () => {
fakeCopyText.throws(new Error('Something went wrong')); fakeCopyPlainText.throws(new Error('Something went wrong'));
const wrapper = createGroupListItem(fakeGroup, { const wrapper = createGroupListItem(fakeGroup, {
isExpanded: true, isExpanded: true,
}); });
clickMenuItem(getSubmenu(wrapper), 'Copy invite link'); clickMenuItem(getSubmenu(wrapper), 'Copy invite link');
assert.calledWith(fakeCopyText, 'https://annotate.com/groups/groupid'); assert.calledWith(fakeCopyPlainText, 'https://annotate.com/groups/groupid');
assert.calledWith(fakeToastMessenger.error, 'Unable to copy link'); assert.calledWith(fakeToastMessenger.error, 'Unable to copy link');
}); });
}); });
...@@ -11,7 +11,7 @@ import { pageSharingLink } from '../../helpers/annotation-sharing'; ...@@ -11,7 +11,7 @@ import { pageSharingLink } from '../../helpers/annotation-sharing';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import type { ToastMessengerService } from '../../services/toast-messenger'; import type { ToastMessengerService } from '../../services/toast-messenger';
import { useSidebarStore } from '../../store'; import { useSidebarStore } from '../../store';
import { copyText } from '../../util/copy-to-clipboard'; import { copyPlainText } from '../../util/copy-to-clipboard';
import ShareLinks from '../ShareLinks'; import ShareLinks from '../ShareLinks';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
...@@ -32,10 +32,10 @@ function ShareAnnotations({ toastMessenger }: ShareAnnotationsProps) { ...@@ -32,10 +32,10 @@ function ShareAnnotations({ toastMessenger }: ShareAnnotationsProps) {
const shareURI = const shareURI =
sharingReady && pageSharingLink(mainFrame.uri, focusedGroup.id); sharingReady && pageSharingLink(mainFrame.uri, focusedGroup.id);
const copyShareLink = useCallback(() => { const copyShareLink = useCallback(async () => {
try { try {
if (shareURI) { if (shareURI) {
copyText(shareURI); await copyPlainText(shareURI);
toastMessenger.success('Copied share link to clipboard'); toastMessenger.success('Copied share link to clipboard');
} }
} catch (err) { } catch (err) {
......
...@@ -26,7 +26,7 @@ describe('ShareAnnotations', () => { ...@@ -26,7 +26,7 @@ describe('ShareAnnotations', () => {
beforeEach(() => { beforeEach(() => {
fakeBouncerLink = 'http://hyp.is/go?url=http%3A%2F%2Fwww.example.com'; fakeBouncerLink = 'http://hyp.is/go?url=http%3A%2F%2Fwww.example.com';
fakeCopyToClipboard = { fakeCopyToClipboard = {
copyText: sinon.stub(), copyPlainText: sinon.stub(),
}; };
fakePageSharingLink = sinon.stub().returns(fakeBouncerLink); fakePageSharingLink = sinon.stub().returns(fakeBouncerLink);
...@@ -148,13 +148,13 @@ describe('ShareAnnotations', () => { ...@@ -148,13 +148,13 @@ describe('ShareAnnotations', () => {
wrapper.find('IconButton').props().onClick(); wrapper.find('IconButton').props().onClick();
assert.calledWith(fakeCopyToClipboard.copyText, fakeBouncerLink); assert.calledWith(fakeCopyToClipboard.copyPlainText, fakeBouncerLink);
}); });
it('confirms link copy when successful', () => { it('confirms link copy when successful', async () => {
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('IconButton').props().onClick(); await wrapper.find('IconButton').props().onClick();
assert.calledWith( assert.calledWith(
fakeToastMessenger.success, fakeToastMessenger.success,
...@@ -163,7 +163,7 @@ describe('ShareAnnotations', () => { ...@@ -163,7 +163,7 @@ describe('ShareAnnotations', () => {
}); });
it('flashes an error if link copying unsuccessful', () => { it('flashes an error if link copying unsuccessful', () => {
fakeCopyToClipboard.copyText.throws(); fakeCopyToClipboard.copyPlainText.throws();
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('IconButton').props().onClick(); wrapper.find('IconButton').props().onClick();
......
...@@ -5,7 +5,7 @@ import type { ComponentChildren } from 'preact'; ...@@ -5,7 +5,7 @@ import type { ComponentChildren } from 'preact';
import type { VersionData } from '../helpers/version-data'; import type { VersionData } from '../helpers/version-data';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import type { ToastMessengerService } from '../services/toast-messenger'; import type { ToastMessengerService } from '../services/toast-messenger';
import { copyText } from '../util/copy-to-clipboard'; import { copyPlainText } from '../util/copy-to-clipboard';
type VersionInfoItemProps = { type VersionInfoItemProps = {
label: string; label: string;
...@@ -37,9 +37,9 @@ export type VersionInfoProps = { ...@@ -37,9 +37,9 @@ export type VersionInfoProps = {
}; };
function VersionInfo({ toastMessenger, versionData }: VersionInfoProps) { function VersionInfo({ toastMessenger, versionData }: VersionInfoProps) {
const copyVersionData = () => { const copyVersionData = async () => {
try { try {
copyText(versionData.asFormattedString()); await copyPlainText(versionData.asFormattedString());
toastMessenger.success('Copied version info to clipboard'); toastMessenger.success('Copied version info to clipboard');
} catch (err) { } catch (err) {
toastMessenger.error('Unable to copy version info'); toastMessenger.error('Unable to copy version info');
......
...@@ -31,7 +31,7 @@ describe('VersionInfo', () => { ...@@ -31,7 +31,7 @@ describe('VersionInfo', () => {
beforeEach(() => { beforeEach(() => {
fakeCopyToClipboard = { fakeCopyToClipboard = {
copyText: sinon.stub(), copyPlainText: sinon.stub(),
}; };
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
...@@ -82,13 +82,13 @@ describe('VersionInfo', () => { ...@@ -82,13 +82,13 @@ describe('VersionInfo', () => {
wrapper.find('Button').props().onClick(); wrapper.find('Button').props().onClick();
assert.calledWith(fakeCopyToClipboard.copyText, 'fakeString'); assert.calledWith(fakeCopyToClipboard.copyPlainText, 'fakeString');
}); });
it('confirms info copy when successful', () => { it('confirms info copy when successful', async () => {
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('Button').props().onClick(); await wrapper.find('Button').props().onClick();
assert.calledWith( assert.calledWith(
fakeToastMessenger.success, fakeToastMessenger.success,
...@@ -97,7 +97,7 @@ describe('VersionInfo', () => { ...@@ -97,7 +97,7 @@ describe('VersionInfo', () => {
}); });
it('flashes an error if info copying unsuccessful', () => { it('flashes an error if info copying unsuccessful', () => {
fakeCopyToClipboard.copyText.throws(); fakeCopyToClipboard.copyPlainText.throws();
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('Button').props().onClick(); wrapper.find('Button').props().onClick();
......
/**
* Copy the string `text` to the clipboard.
*
* In most browsers, this function can only be called in response to a user
* gesture. For example in response to a "click" event.
*
* @throws {Error}
* This function may throw an exception if the browser rejects the attempt
* to copy text.
*
* @deprecated Use copyPlainText instead
*/
export function copyText(text: string) {
const temp = document.createElement('textarea'); // use textarea instead of input to preserve line breaks
temp.value = text;
temp.setAttribute('data-testid', 'copy-text');
// Recipe from https://stackoverflow.com/a/34046084/14463679
temp.contentEditable = 'true';
document.body.appendChild(temp);
temp.focus();
try {
const range = document.createRange();
const selection = document.getSelection()!;
selection.removeAllRanges();
range.selectNodeContents(temp);
selection.addRange(range);
temp.setSelectionRange(0, temp.value.length);
document.execCommand('copy');
} finally {
temp.remove();
}
}
/** /**
* Copy the string `text` to the clipboard verbatim. * Copy the string `text` to the clipboard verbatim.
* *
......
import { copyPlainText, copyHTML, copyText } from '../copy-to-clipboard'; import { copyPlainText, copyHTML } from '../copy-to-clipboard';
describe('copy-to-clipboard', () => { describe('copy-to-clipboard', () => {
const createFakeNavigator = clipboard => ({ clipboard }); const createFakeNavigator = clipboard => ({ clipboard });
describe('copyText', () => {
beforeEach(() => {
sinon.stub(document, 'execCommand');
});
afterEach(() => {
document.execCommand.restore();
});
/**
* Returns the temporary element used to hold text being copied.
*/
function tempSpan() {
return document.querySelector('[data-testid=copy-text]');
}
beforeEach(() => {
// Make no hidden element created for copying text has been left over
// from a previous test.
assert.isNull(tempSpan());
// Make sure there is nothing already selected to copy.
window.getSelection().removeAllRanges();
});
it('copies the passed text to the clipboard', () => {
// We can't actually copy to the clipboard due to security restrictions,
// but we can verify that `execCommand("copy")` was called and that the
// passed text was selected at the time.
document.execCommand.callsFake(() => {
assert.equal(document.getSelection().toString(), 'test string');
});
copyText('test string');
assert.calledWith(document.execCommand, 'copy');
assert.isNull(tempSpan());
});
it('removes temporary span if copying fails', () => {
document.execCommand.callsFake(() => {
assert.ok(tempSpan());
throw new Error('No clipboard access for you!');
});
try {
copyText('fibble-wobble');
} catch (e) {
assert.equal(e.message, 'No clipboard access for you!');
}
assert.isNull(tempSpan());
});
});
describe('copyPlainText', () => { describe('copyPlainText', () => {
it('writes provided text to clipboard', async () => { it('writes provided text to clipboard', async () => {
const text = 'Lorem ipsum dolor sit amet'; const text = 'Lorem ipsum dolor sit amet';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment