import React from 'react' import '@testing-library/jest-dom' import { act, fireEvent, render, waitFor } from '@testing-library/react' import { Column, DataSheetGrid, DataSheetGridRef, keyColumn, textColumn, } from '../src' jest.mock('react-resize-detector', () => ({ useResizeDetector: () => ({ width: 100, height: 100 }), })) const columns: Column[] = [ keyColumn('firstName', textColumn), keyColumn('lastName', textColumn), ] type DataTransferType = { 'text/html'?: string 'text/plain'?: string text?: string } class MockDataTransfer { constructor(private data: DataTransferType) {} getData(format: keyof DataTransferType) { return this.data[format] } get types() { return Object.keys(this.data) } } const paste = (data: DataTransferType) => { fireEvent.paste(document, { clipboardData: new MockDataTransfer(data) }) } const emptyRows = [ { firstName: null, lastName: null }, { firstName: null, lastName: null }, ] test('Single value text', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setActiveCell({ col: 0, row: 1 })) paste({ text: 'Jeff' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: null, lastName: null }, { firstName: 'Jeff', lastName: null }, ], [{ type: 'UPDATE', fromRowIndex: 1, toRowIndex: 2 }] ) expect(ref.current.activeCell).toEqual({ col: 0, colId: 'firstName', row: 1, }) }) test('Single value text plain', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setActiveCell({ col: 1, row: 0 })) paste({ 'text/plain': 'Musk' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: null, lastName: 'Musk' }, { firstName: null, lastName: null }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 1 }] ) }) test('HTML over text', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setActiveCell({ col: 1, row: 0 })) paste({ 'text/plain': 'foo', text: 'foo', 'text/html': '<table><tr><td>Musk</td></tr></table>', }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: null, lastName: 'Musk' }, { firstName: null, lastName: null }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 1 }] ) }) test('Single value on multiple rows selection', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setSelection({ min: { col: 0, row: 0 }, max: { col: 1, row: 1 }, }) ) paste({ text: 'Elon' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: 'Elon', lastName: null }, { firstName: 'Elon', lastName: null }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 2 }] ) expect(ref.current.selection).toEqual({ min: { col: 0, colId: 'firstName', row: 0, }, max: { col: 0, colId: 'firstName', row: 1, }, }) }) test('Single row on multiple rows selection', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setSelection({ min: { col: 0, row: 0 }, max: { col: 0, row: 1 }, }) ) paste({ text: 'Elon\tMusk' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: 'Elon', lastName: 'Musk' }, { firstName: 'Elon', lastName: 'Musk' }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 2 }] ) expect(ref.current.selection).toEqual({ min: { col: 0, colId: 'firstName', row: 0, }, max: { col: 1, colId: 'lastName', row: 1, }, }) }) test('Single row on multiple rows selection with overflow to the right', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setSelection({ min: { col: 1, row: 0 }, max: { col: 1, row: 1 }, }) ) paste({ text: 'Elon\tMusk' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: null, lastName: 'Elon' }, { firstName: null, lastName: 'Elon' }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 2 }] ) expect(ref.current.selection).toEqual({ min: { col: 1, colId: 'lastName', row: 0, }, max: { col: 1, colId: 'lastName', row: 1, }, }) }) test('Multiple rows', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={emptyRows} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setSelection({ min: { col: 0, row: 0 }, max: { col: 1, row: 0 }, }) ) paste({ text: 'Elon\nJeff' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: 'Elon', lastName: null }, { firstName: 'Jeff', lastName: null }, ], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 2 }] ) expect(ref.current.selection).toEqual({ min: { col: 0, colId: 'firstName', row: 0, }, max: { col: 0, colId: 'firstName', row: 1, }, }) }) test('Multiple rows with overflow to the right', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={[{}, {}, {}]} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setSelection({ min: { col: 1, row: 0 }, max: { col: 1, row: 2 }, }) ) paste({ text: 'Elon\tMusk\nJeff\tBezos' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [{ lastName: 'Elon' }, { lastName: 'Jeff' }, {}], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 2 }] ) expect(ref.current.selection).toEqual({ min: { col: 1, colId: 'lastName', row: 0, }, max: { col: 1, colId: 'lastName', row: 1, }, }) }) test('Multiple rows with overflow at the bottom', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={[{}]} onChange={onChange} columns={columns} ref={ref} /> ) act(() => ref.current.setActiveCell({ col: 0, row: 0 })) paste({ text: 'Elon\tMusk\nJeff\tBezos\nRichard\tBranson' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [ { firstName: 'Elon', lastName: 'Musk' }, { firstName: 'Jeff', lastName: 'Bezos' }, { firstName: 'Richard', lastName: 'Branson' }, ], [ { type: 'UPDATE', fromRowIndex: 0, toRowIndex: 1 }, { type: 'CREATE', fromRowIndex: 1, toRowIndex: 3 }, ] ) expect(ref.current.selection).toEqual({ min: { col: 0, colId: 'firstName', row: 0, }, max: { col: 1, colId: 'lastName', row: 2, }, }) }) test('Multiple rows with overflow at the bottom and locked rows', async () => { const ref = { current: null as unknown as DataSheetGridRef } const onChange = jest.fn() render( <DataSheetGrid value={[{}]} onChange={onChange} columns={columns} lockRows ref={ref} /> ) act(() => ref.current.setActiveCell({ col: 0, row: 0 })) paste({ text: 'Elon\tMusk\nJeff\tBezos\nRichard\tBranson' }) await waitFor(() => expect(onChange).toHaveBeenCalled()) expect(onChange).toHaveBeenCalledWith( [{ firstName: 'Elon', lastName: 'Musk' }], [{ type: 'UPDATE', fromRowIndex: 0, toRowIndex: 1 }] ) expect(ref.current.selection).toEqual({ min: { col: 0, colId: 'firstName', row: 0, }, max: { col: 1, colId: 'lastName', row: 0, }, }) })