import React from 'react'; import 'jest-styled-components'; import { render, fireEvent, screen } from '@testing-library/react'; import { Grommet } from '../../Grommet'; import { Box } from '../../Box'; import { Button } from '../../Button'; import { Text } from '../../Text'; import { DataTable, Sections, SortType } from '..'; import { BackgroundType, BorderType } from '../../../utils'; interface TestDataItem { a: string; b: number; } const DATA: TestDataItem[] = []; for (let i = 0; i < 95; i += 1) { DATA.push({ a: `entry-${i}`, b: i }); } describe('DataTable', () => { test('empty', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('basic', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('!primaryKey', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('paths', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('primaryKey', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('footer', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('footer node', () => { const { getByText } = render( Total }, { property: 'b', header: 'B' }, ]} data={[ { a: 'one', b: 1 }, { a: 'two', b: 2 }, ]} /> , ); expect(getByText('Total')).not.toBeNull(); }); test('sortable', () => { const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); const headerCell = getByText('A'); fireEvent.click(headerCell, {}); expect(container.firstChild).toMatchSnapshot(); }); test('sort null data', () => { const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); let headerCell = getByText('A'); fireEvent.click(headerCell, {}); expect(container.firstChild).toMatchSnapshot(); headerCell = getByText('C'); fireEvent.click(headerCell, {}); expect(container.firstChild).toMatchSnapshot(); headerCell = getByText('D'); fireEvent.click(headerCell, {}); expect(container.firstChild).toMatchSnapshot(); }); test('onSort', () => { const onSort = jest.fn(); const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); const headerCell = getByText('A'); fireEvent.click(headerCell, {}); expect(onSort).toBeCalledWith( expect.objectContaining({ property: 'a', direction: 'asc' }), ); expect(container.firstChild).toMatchSnapshot(); }); test('onSort external', () => { const onSort = jest.fn(); const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); const headerCell = getByText('A'); fireEvent.click(headerCell, {}); expect(onSort).toBeCalledWith( expect.objectContaining({ property: 'a', direction: 'desc', external: true, }), ); expect(container.firstChild).toMatchSnapshot(); }); test('sort', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('sort controlled', () => { const Test = () => { const [sort, setSort] = React.useState({ property: 'a', direction: 'asc', }); return ( ); } const { container, getByText } = render(); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByText('toggle')); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByText('toggle')); expect(container.firstChild).toMatchSnapshot(); }); test('click', () => { const onClickRow = jest.fn(); const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByText('beta')); expect(onClickRow).toBeCalledWith( expect.objectContaining({ datum: { a: 'beta' } }), ); expect(container.firstChild).toMatchSnapshot(); }); test('disabled click', () => { const onClickRow = jest.fn(); const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByText('beta')); expect(onClickRow).toBeCalledWith( expect.objectContaining({ datum: { a: 'beta' } }), ); expect(container.firstChild).toMatchSnapshot(); }); test('background', () => { const backgrounds: ( | BackgroundType | BackgroundType[] | Sections )[] = [ 'accent-1', ['accent-1', 'accent-2'], { header: 'accent-1', body: 'accent-2', footer: 'accent-3' }, ]; const { container } = render( {backgrounds.map((background) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('border', () => { const borders: (BorderType | Sections)[] = [ true, 'top', { color: 'accent-1', side: 'top', size: 'small' }, { header: 'top', body: { color: 'accent-1', side: 'top', size: 'small' }, }, ]; const { container } = render( {borders.map((border) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('pad', () => { const { container } = render( {[ 'small', { vertical: 'small', horizontal: 'medium' }, { header: 'small', body: { vertical: 'small', horizontal: 'medium' }, }, ].map((pad) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('rowProps', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('groupBy property', () => { const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); const headerCell = getByText('A'); fireEvent.click(headerCell, {}); expect(container.firstChild).toMatchSnapshot(); }); test('groupBy expand', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('groupBy onExpand', () => { const onExpand = jest.fn((groupState) => groupState); const { getAllByLabelText } = render( , ); const expandButtons = getAllByLabelText('expand'); fireEvent.click(expandButtons[1], {}); expect(onExpand).toBeCalled(); expect(onExpand.mock.results[0].value).toEqual(['one']); expect(onExpand.mock.results[0].value).toMatchSnapshot(); }); test('replace', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('themeColumnSizes', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('absoluteColumnSizes', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('relativeColumnSizes', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('fill', () => { const fills: (boolean | 'vertical' | 'horizontal')[] = [ true, 'horizontal', 'vertical', ]; const { container } = render( {fills.map((fill) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('pin', () => { const pins: (boolean | 'header' | 'footer')[] = [true, 'header', 'footer']; const { container } = render( {pins.map((pin) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('pin + background', () => { const theme = { dataTable: { pinned: { header: { background: { color: 'blue', }, }, footer: { background: { color: 'green', }, }, }, }, }; const pins: (boolean | 'header' | 'footer')[] = [true, 'header', 'footer']; const { container } = render( {pins.map((pin) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('pin + background context', () => { const { container } = render( {[ 'background-back', 'background-front', { color: 'background-back', dark: true }, ].map((contextBackground) => ( ))} , ); expect(container.firstChild).toMatchSnapshot(); }); test('select', () => { const onSelect = jest.fn(); const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByLabelText('select beta')); expect(onSelect).toBeCalledWith(expect.arrayContaining(['alpha', 'beta']), { a: 'beta', }); expect(container.firstChild).toMatchSnapshot(); }); test('select with allowSelectAll={false}', () => { const onSelect = jest.fn(); const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByLabelText('select beta')); expect(onSelect).toBeCalledWith(expect.arrayContaining(['alpha', 'beta']), { a: 'beta', }); expect(container.firstChild).toMatchSnapshot(); }); test('disabled select', () => { const onSelect = jest.fn(); const { container, getByText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByText('alpha')); expect(onSelect).not.toBeCalled(); }); test('custom theme', () => { const customTheme = { dataTable: { header: { background: 'skyblue', border: { color: 'brand', size: 'medium', }, gap: 'none', pad: { horizontal: 'small', vertical: 'xsmall' }, font: { weight: 'bold', }, hover: { background: { color: 'light-2', }, }, }, resize: { hover: { border: { color: 'red', side: 'end', size: 'xsmall', }, }, }, }, }; const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.mouseOver(getByLabelText('select beta')); expect(container.firstChild).toMatchSnapshot(); }); test('units', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('placeholder', () => { const { container } = render( test placeholder} /> test placeholder} /> , ); expect(container.firstChild).toMatchSnapshot(); }); test('should paginate', () => { const { container, getAllByText } = render( , ); const results = getAllByText('entry', { exact: false }); // default DataTable step 50 expect(results.length).toEqual(50); expect(container.firstChild).toMatchSnapshot(); }); test('should pin and paginate', () => { const { container, getAllByText } = render( , ); const results = getAllByText('entry', { exact: false }); expect(results.length).toEqual(50); expect(container.firstChild).toMatchSnapshot(); }); test('should apply pagination styling', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('should show correct item index when "show" is a number', () => { const show = 15; const { container, getByText } = render( , ); const result = getByText(`entry-${show}`); expect(result).toBeTruthy(); expect(container.firstChild).toMatchSnapshot(); }); test('should show correct page when "show" is { page: # }', () => { const desiredPage = 2; const { container } = render( , ); const activePage = ( container.querySelector(`[aria-current="page"]`) as HTMLButtonElement ).innerHTML; expect(activePage).toEqual(`${desiredPage}`); expect(container.firstChild).toMatchSnapshot(); }); test('should render correct num items per page (step)', () => { const step = 14; const { container, getAllByText } = render( , ); const results = getAllByText('entry', { exact: false }); expect(results.length).toEqual(step); expect(container.firstChild).toMatchSnapshot(); }); test('should render new data when page changes', () => { const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.click(getByLabelText('Go to next page')); expect(container.firstChild).toMatchSnapshot(); }); test('should not show paginate controls when data is empty array', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('should not show paginate controls when length of data < step', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('onSelect select/unselect all', () => { const onSelect = jest.fn(); const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); let headerCheckBox; headerCheckBox = getByLabelText('select all'); fireEvent.click(headerCheckBox); expect(onSelect).toBeCalledWith([1.1, 1.2, 2.1, 2.2]); expect(container.firstChild).toMatchSnapshot(); // aria-label should have changed since all entries // are selected headerCheckBox = getByLabelText('unselect all'); fireEvent.click(headerCheckBox); expect(onSelect).toBeCalledWith([]); expect(container.firstChild).toMatchSnapshot(); }); test('onSelect + groupBy should select/deselect all when grouped', () => { const onSelect = jest.fn(); const { container, getByLabelText } = render( , ); expect(container.firstChild).toMatchSnapshot(); let headerCheckBox; headerCheckBox = getByLabelText('select all'); fireEvent.click(headerCheckBox); expect(container.firstChild).toMatchSnapshot(); // aria-label should have changed since all entries // are selected headerCheckBox = getByLabelText('unselect all'); fireEvent.click(headerCheckBox); expect(container.firstChild).toMatchSnapshot(); }); test('onSelect + groupBy should select all items within a group', () => { const onSelect = jest.fn(); const { container, getByLabelText } = render( , ); const groupCheckBox = getByLabelText('select one'); fireEvent.click(groupCheckBox); expect(onSelect).toBeCalledWith( expect.arrayContaining([1.1, 1.2]), expect.objectContaining({ a: 'one' }), ); expect(container.firstChild).toMatchSnapshot(); }); test(`onSelect + groupBy should render indeterminate checkbox on table and group if subset of group items are selected`, () => { const onSelect = jest.fn(); const { container, getAllByLabelText, getByLabelText } = render( , ); const groupCheckBox = getByLabelText('select one'); fireEvent.click(groupCheckBox); const expandButtons = getAllByLabelText('expand'); fireEvent.click(expandButtons[1], {}); fireEvent.click(getByLabelText('unselect 1.1')); expect(container.firstChild).toMatchSnapshot(); }); test(`onSelect + groupBy should render indeterminate checkbox on table and group when controlled`, () => { const onSelect = jest.fn(); const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('verticalAlign', () => { const { asFragment } = render( , ); expect(asFragment()).toMatchSnapshot(); }); test('should base table body max height on global size', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('should base table body max height on css value', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('rowProps on group header rows', () => { const { container } = render( Name with extra, primary: true, }, ]} rowProps={{ 'Fort Collins': { background: 'yellow' } }} data={[ { name: 'Bryan', location: 'Fort Collins' }, { name: 'Doug', location: 'Fort Collins' }, { name: 'Tracy', location: 'San Francisco' }, ]} groupBy="location" /> , ); expect(container.firstChild).toMatchSnapshot(); }); test('border on CheckBox cell', () => { const { asFragment } = render( {}} /> , ); expect(asFragment()).toMatchSnapshot(); }); test('onSelect datum argument should be defined', () => { const onSelect = jest.fn(); render( , ); fireEvent.click(screen.getByRole('checkbox', { name: 'select Alan' })); expect(onSelect).toBeCalledWith(['Alan'], { name: 'Alan', percent: 20 }); }); });