import React from 'react' import { renderHook, act } from '@testing-library/react' import { useRangeCopy } from './use-range-copy' import { GridProvider } from '../context/grid-context' import type { GridRangeSelection, GridRowData } from '../types' import type { SpreadsheetRangeCopy } from '../grids/spreadsheet-grid/types' import type { UseGridReturn } from './use-grid' import { createGrid } from './use-grid' describe('useRangeCopy', () => { let grid: UseGridReturn const mockContainer = document.createElement('div') const mockRange: GridRangeSelection = { from: { rowId: 'row1', columnId: 'col1' }, to: { rowId: 'row2', columnId: 'col2' }, } const mockRangeData = { rowIds: ['row1', 'row2'], columnIds: ['col1', 'col2'], } beforeEach(() => { grid = createGrid() grid.dispatch({ type: 'applyProps', payload: { actionsMenuPresent: false, columns: [ { id: 'col1', label: 'Column 1', cell: { label({ value }) { return `Label: ${value}` }, }, }, { id: 'col2', label: 'Column 2', cell: { label({ value }) { return `Label: ${value}` }, }, }, ], rows: [ { id: 'row1', col1: 'R1C1', col2: 'R1C2' }, { id: 'row2', col1: 'R2C1', col2: 'R2C2' }, ], footerEnabled: false, rowDrag: { enabled: false }, }, }) jest.spyOn(grid.selectors, 'selectRangeSelection').mockReturnValue( mockRange ) jest.spyOn(grid.selectors, 'selectRangeIds').mockReturnValue( mockRangeData ) jest.spyOn(grid.selectors, 'selectIsSpreadsheet').mockReturnValue(true) }) it('should return undefined when not a spreadsheet', () => { jest.mocked(grid.selectors.selectIsSpreadsheet).mockReturnValue(false) const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) expect(result.current).toBeUndefined() }) it('should return handleCopy function when is a spreadsheet', () => { const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) expect(result.current).toBeInstanceOf(Function) }) it('handleCopy should do nothing when there is no range selection', () => { jest.mocked(grid.selectors.selectRangeSelection).mockReturnValue(null) const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) const mockEvent = { preventDefault: jest.fn(), } as unknown as React.ClipboardEvent act(() => { result.current?.(mockEvent) }) expect(mockEvent.preventDefault).not.toHaveBeenCalled() }) it('handleCopy should process values when onRangeCopyIncoming is "values"', () => { const mockClipboardData = { setData: jest.fn(), } const mockEvent = { preventDefault: jest.fn(), clipboardData: mockClipboardData, } as unknown as React.ClipboardEvent const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) act(() => { result.current?.(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(mockClipboardData.setData).toHaveBeenCalledTimes(3) expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/plain', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/html', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'application/x-pvds-grid', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot() expect(mockContainer).toHaveAttribute( 'data-pvds-grid-range-copied', 'true' ) }) it('handleCopy should properly handle column span', () => { grid.dispatch({ type: 'applyProps', payload: { actionsMenuPresent: false, columns: [ { id: 'col1', label: 'Column 1', cell: { label({ value }) { return `Label: ${value}` }, colSpan({ row }) { if (row.id === 'row1') return [] return ['col1', 'col2'] }, }, }, { id: 'col2', label: 'Column 2', cell: { label({ value }) { return `Label: ${value}` }, }, }, ], rows: [ { id: 'row1', col1: 'R1C1', col2: 'R1C2' }, { id: 'row2', col1: 'R2C1', col2: 'R2C2' }, ], footerEnabled: false, rowDrag: { enabled: false }, }, }) const mockClipboardData = { setData: jest.fn(), } const mockEvent = { preventDefault: jest.fn(), clipboardData: mockClipboardData, } as unknown as React.ClipboardEvent const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) act(() => { result.current?.(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(mockClipboardData.setData).toHaveBeenCalledTimes(3) expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/plain', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/html', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'application/x-pvds-grid', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot() expect(mockContainer).toHaveAttribute( 'data-pvds-grid-range-copied', 'true' ) }) it('handleCopy should process labels when onRangeCopyIncoming is "labels"', () => { const mockClipboardData = { setData: jest.fn(), } const mockEvent = { preventDefault: jest.fn(), clipboardData: mockClipboardData, } as unknown as React.ClipboardEvent const { result } = renderHook( () => useRangeCopy('labels', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) act(() => { result.current?.(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(mockClipboardData.setData).toHaveBeenCalledTimes(3) expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/plain', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'text/html', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot() expect(mockClipboardData.setData).toHaveBeenCalledWith( 'application/x-pvds-grid', expect.any(String) ) expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot() }) it('handleCopy should call custom function when onRangeCopyIncoming is a function', () => { const customCopyFn: SpreadsheetRangeCopy = jest.fn() const mockEvent = { preventDefault: jest.fn(), } as unknown as React.ClipboardEvent const { result } = renderHook( () => useRangeCopy(customCopyFn, mockContainer), { wrapper: ({ children }) => ( {children} ), } ) act(() => { result.current?.(mockEvent) }) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(customCopyFn).toHaveBeenCalledWith( mockRange, mockRangeData, mockEvent ) }) it('should remove attribute after animation duration', () => { jest.useFakeTimers() const mockEvent = { preventDefault: jest.fn(), clipboardData: { setData: jest.fn(), }, } as unknown as React.ClipboardEvent const { result } = renderHook( () => useRangeCopy('values', mockContainer), { wrapper: ({ children }) => ( {children} ), } ) act(() => { result.current?.(mockEvent) }) expect(mockContainer).toHaveAttribute( 'data-pvds-grid-range-copied', 'true' ) act(() => { jest.advanceTimersByTime(300) }) expect(mockContainer).not.toHaveAttribute('data-pvds-grid-range-copied') jest.useRealTimers() }) })