diff --git a/frontend/src/__tests__/components/molecules/MessagePlansList.test.tsx b/frontend/src/__tests__/components/molecules/MessagePlansList.test.tsx index 83b5eab7a..ec449aeac 100644 --- a/frontend/src/__tests__/components/molecules/MessagePlansList.test.tsx +++ b/frontend/src/__tests__/components/molecules/MessagePlansList.test.tsx @@ -1,5 +1,9 @@ -import { render, screen } from '@testing-library/react'; -import { MessagePlansList } from '@molecules/MessagePlansList/MessagePlansList'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { + MessagePlanListItem, + MessagePlansList, +} from '@molecules/MessagePlansList/MessagePlansList'; +import userEvent from '@testing-library/user-event'; describe('MessagePlansList', () => { it('matches snapshot when data is available', async () => { @@ -60,6 +64,87 @@ describe('MessagePlansList', () => { expect(lastEditedCell).toHaveTextContent('13:00'); }); + it('should copy message plan names and IDs to clipboard when button is clicked', async () => { + const mockPlans: MessagePlanListItem[] = [ + { name: 'Plan 1', id: 'id-1', lastUpdated: '2026-01-23T10:00:00Z' }, + { name: 'Plan 2', id: 'id-2', lastUpdated: '2026-01-23T11:00:00Z' }, + ]; + + const mockClipboardWrite = jest.fn().mockResolvedValue(undefined); + + Object.defineProperty(navigator, 'clipboard', { + value: { write: mockClipboardWrite }, + writable: true, + configurable: true, + }); + + global.ClipboardItem = jest.fn( + (data) => data + ) as unknown as typeof ClipboardItem; + + const { getByTestId } = render( + + ); + + const expander = getByTestId('message-plans-list-draft'); + fireEvent.click(expander); + + const copyButton = getByTestId('copy-button-draft'); + + await userEvent.click(copyButton); + + expect(mockClipboardWrite).toHaveBeenCalledTimes(1); + expect(copyButton).toHaveTextContent('Names and IDs copied to clipboard'); + + const [clipboardItem] = mockClipboardWrite.mock.calls[0][0]; + + const csv = clipboardItem['text/plain']; + + const expectedCSV = [ + 'routing_plan_name,routing_plan_id', + '"Plan 1","id-1"', + '"Plan 2","id-2"', + ].join('\n'); + + expect(csv).toEqual(expectedCSV); + }); + + it('should display error message when clipboard write fails', async () => { + const mockPlans: MessagePlanListItem[] = [ + { name: 'Plan 1', id: 'id-1', lastUpdated: '2026-01-23T10:00:00Z' }, + ]; + + const mockClipboardWrite = jest + .fn() + .mockRejectedValue(new Error('Permission denied')); + + Object.defineProperty(navigator, 'clipboard', { + value: { write: mockClipboardWrite }, + writable: true, + configurable: true, + }); + + global.ClipboardItem = jest.fn( + (data) => data + ) as unknown as typeof ClipboardItem; + + const { getByTestId } = render( + + ); + + const expander = getByTestId('message-plans-list-draft'); + fireEvent.click(expander); + + const copyButton = getByTestId('copy-button-draft'); + + await userEvent.click(copyButton); + + expect(mockClipboardWrite).toHaveBeenCalledTimes(1); + expect(copyButton).toHaveTextContent( + 'Failed copying names and IDs to clipboard' + ); + }); + it('matches snapshot when data is available - COMPLETED', async () => { const data = { count: 1, diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlansList.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlansList.test.tsx.snap index 0011ce944..ac9fb1f73 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlansList.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlansList.test.tsx.snap @@ -101,6 +101,14 @@ exports[`MessagePlansList matches snapshot when data is available - COMPLETED 1` + @@ -207,6 +215,14 @@ exports[`MessagePlansList matches snapshot when data is available 1`] = ` + diff --git a/frontend/src/__tests__/hooks/use-copy-table-to-clipboard.hook.test.tsx b/frontend/src/__tests__/hooks/use-copy-table-to-clipboard.hook.test.tsx new file mode 100644 index 000000000..a3c3cf96a --- /dev/null +++ b/frontend/src/__tests__/hooks/use-copy-table-to-clipboard.hook.test.tsx @@ -0,0 +1,178 @@ +import { renderHook, act } from '@testing-library/react'; +import { useCopyTableToClipboard } from '../../hooks/use-copy-table-to-clipboard.hook'; + +type TestData = { + name: string; + id: string; + value: string; +}; + +describe('useCopyTableToClipboard', () => { + let mockClipboardWrite: jest.Mock; + + beforeEach(() => { + mockClipboardWrite = jest.fn().mockResolvedValue(undefined); + + Object.defineProperty(navigator, 'clipboard', { + value: { write: mockClipboardWrite }, + writable: true, + configurable: true, + }); + + global.ClipboardItem = jest.fn( + (data) => data + ) as unknown as typeof ClipboardItem; + + jest.useFakeTimers(); + }); + + afterEach(() => { + act(() => { + jest.runOnlyPendingTimers(); + }); + jest.useRealTimers(); + jest.clearAllMocks(); + }); + + it('should copy data in both CSV and HTML formats to clipboard', async () => { + const { result } = renderHook(() => useCopyTableToClipboard()); + + const testData: TestData[] = [ + { name: 'Test "quoted" value', id: 'id-1', value: '100' }, + { name: '