src/js/components/DataTable/__tests__/DataTable-test.tsx
import React, { useState } 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 { Data } from '../../Data';
import { Pagination } from '../../Pagination';
import { Text } from '../../Text';
import { DataTable, Sections, SortType } from '..';
import { BackgroundType, BorderType } from '../../../utils';
interface TestDataItem {
a: string;
b: number;
c?: string;
}
const DATA: TestDataItem[] = [];
for (let i = 0; i < 95; i += 1) {
DATA.push({
a: `entry-${i}`,
b: i,
c: i === 0 || i === 1 || i === 35 || i === 50 ? 'option 1' : 'option 2',
});
}
describe('DataTable', () => {
test('empty', () => {
const { container } = render(
<Grommet>
<DataTable />
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('basic', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('!primaryKey', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
primaryKey={false}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('paths', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b.c', header: 'B' },
]}
data={[
{ a: 'one', b: { c: 1 } },
{ a: 'two', b: { c: 2 } },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('primaryKey', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
primaryKey="b"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('footer', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('footer node', () => {
const { getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: <span>Total</span> },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(getByText('Total')).not.toBeNull();
});
test('sortable', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
sortable
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
const headerCell = getByText('A');
fireEvent.click(headerCell, {});
expect(container.firstChild).toMatchSnapshot();
});
test('sort null data', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
{ property: 'c', header: 'C' },
{ property: 'd', header: 'D' },
]}
data={[
{ a: undefined, b: 0, c: 'first', d: 'y' },
{ a: 'one', b: 1, c: null },
{ a: 'two', b: 2, c: 'second' },
{ a: undefined, b: 3, c: null, d: 'z' },
]}
sortable
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
onSort={onSort}
sortable
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
onSort={onSort}
sort={{ property: 'a', direction: 'asc', external: true }}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
sort={{ property: 'a', direction: 'asc' }}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('sort controlled', () => {
const Test = () => {
const [sort, setSort] = React.useState<SortType>({
property: 'a',
direction: 'asc',
});
return (
<Grommet>
<Button
label="Sort data"
onClick={() => setSort({ property: 'a', direction: 'desc' })}
/>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
sort={sort}
/>
</Grommet>
);
};
const { asFragment } = render(<Test />);
expect(asFragment()).toMatchSnapshot();
const sortButton = screen.getByRole('button', { name: 'Sort data' });
fireEvent.click(sortButton);
expect(asFragment()).toMatchSnapshot();
});
test('sort nested object', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{
property: 'b.value',
header: 'Value',
render: (datum) => datum.b && datum.b.value,
},
]}
data={[
{ a: 'zero', b: { value: 1 } },
{ a: 'one', b: { value: 2 } },
{ a: 'two', b: { value: 3 } },
]}
sort={{ property: 'b.value', direction: 'asc' }}
/>
</Grommet>,
);
expect(container.querySelectorAll('td').item(0).textContent).toBe('1');
expect(container.querySelectorAll('td').item(1).textContent).toBe('2');
expect(container.querySelectorAll('td').item(2).textContent).toBe('3');
fireEvent.click(getByText('Value'));
expect(container.querySelectorAll('td').item(0).textContent).toBe('3');
expect(container.querySelectorAll('td').item(1).textContent).toBe('2');
expect(container.querySelectorAll('td').item(2).textContent).toBe('1');
expect(container.firstChild).toMatchSnapshot();
});
test('sort nested object with onSort', () => {
const onSort = jest.fn();
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{
property: 'b.value',
header: 'Value',
render: (datum) => datum.b && datum.b.value,
},
]}
data={[
{ a: 'zero', b: { value: 1 } },
{ a: 'one', b: { value: 2 } },
{ a: 'two', b: { value: 3 } },
]}
onSort={onSort}
sort={{ property: 'b.value', direction: 'asc' }}
/>
</Grommet>,
);
expect(container.querySelectorAll('td').item(0).textContent).toBe('1');
expect(container.querySelectorAll('td').item(1).textContent).toBe('2');
expect(container.querySelectorAll('td').item(2).textContent).toBe('3');
fireEvent.click(getByText('Value'));
expect(onSort).toBeCalledWith(
expect.objectContaining({ property: 'b.value' }),
);
expect(container.querySelectorAll('td').item(0).textContent).toBe('3');
expect(container.querySelectorAll('td').item(1).textContent).toBe('2');
expect(container.querySelectorAll('td').item(2).textContent).toBe('1');
expect(container.firstChild).toMatchSnapshot();
});
test('sort external', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'zero', b: 0 },
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
sort={{ property: 'a', direction: 'asc', external: true }}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('search', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A', search: true }]}
data={[{ a: 'Alpha' }, { a: 'beta' }, { a: '[]' }]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
fireEvent.click(
container.querySelector(
'[aria-label="Open search by a"]',
) as HTMLButtonElement,
);
const searchInput = container.querySelector(
'[name="search-a"]',
) as HTMLInputElement;
expect(document.activeElement).toBe(searchInput);
fireEvent.change(searchInput, {
target: { value: '[' },
});
expect(container.firstChild).toMatchSnapshot();
});
test('resizeable', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
resizeable
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('aggregate', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{
property: 'b',
header: 'B',
aggregate: 'sum',
footer: { aggregate: true },
},
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('aggregate with nested object', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{
property: 'obj.value',
header: 'object',
aggregate: 'sum',
footer: { aggregate: true },
},
{
property: 'obj2.value',
header: 'object 2',
render: (datum) => datum.obj2.value,
},
]}
data={[
{ a: 'one', obj: { value: 1 }, obj2: { value: 10 } },
{ a: 'two', obj: { value: 2 }, obj2: { value: 20 } },
]}
/>
</Grommet>,
);
expect(getByText('3')).toBeTruthy();
expect(container.firstChild).toMatchSnapshot();
});
test('rowDetails', () => {
const { container, getAllByLabelText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
rowDetails={(row) => <Box>{row.a}</Box>}
primaryKey="b"
/>
</Grommet>,
);
const expandButtons = getAllByLabelText('expand');
fireEvent.click(expandButtons[1], {});
expect(container.firstChild).toMatchSnapshot();
});
test('rowDetails condtional', () => {
const { container, getAllByLabelText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
rowDetails={(row: TestDataItem) => {
if (row.b === 1.1) {
return <Box> {row.a} </Box>;
}
return (
<Box>
{row.a} : {row.b}{' '}
</Box>
);
}}
primaryKey="b"
/>
</Grommet>,
);
const expandButtons = getAllByLabelText('expand');
fireEvent.click(expandButtons[1], {});
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy="a"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
const headerCell = getByText('A');
fireEvent.click(headerCell, {});
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy 0 value', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
{ property: 'c', header: 'C' },
]}
data={[
{ a: 'one', b: 1.1, c: 0 },
{ a: 'two', b: 1.2, c: 0 },
{ a: 'three', b: 2.1, c: 1 },
{ a: 'four', b: 2.2, c: 2 },
]}
groupBy="c"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy toggle', () => {
function TestComponent() {
const [groupBy, setGroupBy] = React.useState<string | undefined>();
const toggle = () => setGroupBy(groupBy === undefined ? 'a' : undefined);
return (
<Grommet>
<button type="button" onClick={toggle}>
toggle
</button>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B', primary: true },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy={groupBy}
/>
</Grommet>
);
}
const { container, getByText } = render(<TestComponent />);
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(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
onClickRow={onClickRow}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
disabled={['alpha']}
onClickRow={onClickRow}
/>
</Grommet>,
);
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<BackgroundType | string[], BackgroundType, BackgroundType>
)[] = [
'accent-1',
['accent-1', 'accent-2'],
{ header: 'accent-1', body: 'accent-2', footer: 'accent-3' },
];
const { container } = render(
<Grommet>
{backgrounds.map((background) => (
<DataTable
key={JSON.stringify(background)}
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
background={background}
/>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('border', () => {
const borders: (BorderType | Sections<BorderType>)[] = [
true,
'top',
{ color: 'accent-1', side: 'top', size: 'small' },
{
header: 'top',
body: { color: 'accent-1', side: 'top', size: 'small' },
},
];
const { container } = render(
<Grommet>
{borders.map((border) => (
<DataTable
key={JSON.stringify(border)}
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
border={border}
/>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('pad', () => {
const { container } = render(
<Grommet>
{[
'small',
{ vertical: 'small', horizontal: 'medium' },
{
header: 'small',
body: { vertical: 'small', horizontal: 'medium' },
},
].map((pad) => (
<DataTable
key={JSON.stringify(pad)}
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
pad={pad}
/>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('rowProps', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
rowProps={{
one: { background: 'accent-1', border: 'bottom', pad: 'large' },
}}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy property', () => {
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy={{ property: 'a' }}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
const headerCell = getByText('A');
fireEvent.click(headerCell, {});
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy expand', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
primaryKey="b"
groupBy={{ property: 'a', expand: ['one'] }}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('groupBy onExpand', () => {
const onExpand = jest.fn((groupState) => groupState);
const { getAllByLabelText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
primaryKey="b"
groupBy={{ property: 'a', onExpand }}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
primaryKey="b"
step={2}
replace
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('themeColumnSizes', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', size: 'medium' },
{ property: 'b', header: 'B', size: 'small' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('absoluteColumnSizes', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', size: '400px' },
{ property: 'b', header: 'B', size: '200px' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('relativeColumnSizes', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', size: '2/3' },
{ property: 'b', header: 'B', size: '1/3' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('fill', () => {
const fills: (boolean | 'vertical' | 'horizontal')[] = [
true,
'horizontal',
'vertical',
];
const { container } = render(
<Grommet>
{fills.map((fill) => (
<DataTable
key={JSON.stringify(fill)}
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
fill={fill}
/>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('pin', () => {
const pins: (boolean | 'header' | 'footer')[] = [true, 'header', 'footer'];
const { container } = render(
<Grommet>
{pins.map((pin) => (
<DataTable
key={JSON.stringify(pin)}
columns={[
{ property: 'a', header: 'A', footer: 'Total', pin: true },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
pin={pin}
/>
))}
</Grommet>,
);
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(
<Grommet theme={theme}>
{pins.map((pin) => (
<DataTable
background={{ pinned: 'red' }}
key={JSON.stringify(pin)}
columns={[
{ property: 'a', header: 'A', footer: 'Total', pin: true },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
pin={pin}
/>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('pin + background context', () => {
const { container } = render(
<Grommet>
{[
'background-back',
'background-front',
{ color: 'background-back', dark: true },
].map((contextBackground) => (
<Box
key={JSON.stringify(contextBackground)}
background={contextBackground}
>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: 'Total', pin: true },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
pin
/>
</Box>
))}
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('select', () => {
const onSelect = jest.fn();
const { container, getByLabelText } = render(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
primaryKey="a"
select={['alpha']}
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
primaryKey="a"
select={['alpha']}
onSelect={onSelect}
allowSelectAll={false}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
primaryKey="a"
disabled={['alpha']}
select={['beta']}
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet theme={customTheme}>
<DataTable
columns={[{ property: 'a', header: 'A' }]}
data={[{ a: 'alpha' }, { a: 'beta' }]}
primaryKey="a"
select={['alpha']}
sortable
resizeable
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
fireEvent.mouseOver(getByLabelText('select beta'));
expect(container.firstChild).toMatchSnapshot();
});
test('should apply custom theme for groupHeader', () => {
const theme = {
dataTable: {
groupHeader: {
background: 'tomato',
border: {
side: 'top',
size: 'medium',
},
pad: 'medium',
},
},
};
const { asFragment } = render(
<Grommet theme={theme}>
<DataTable
columns={[
{ header: 'Group', property: 'group' },
{ header: 'Value', primary: true, property: 'value' },
]}
data={[
{ group: 1, value: 1 },
{ group: 1, value: 2 },
{ group: 2, value: 3 },
{ group: 2, value: 4 },
]}
groupBy="group"
/>
</Grommet>,
);
expect(asFragment()).toMatchSnapshot();
});
test('units', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B', units: '(TiB)' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('placeholder', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A', footer: 'Total' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
placeholder="test placeholder"
/>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
placeholder={<Text weight="bold">test placeholder</Text>}
/>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
placeholder={<Text weight="bold">test placeholder</Text>}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('should paginate', () => {
const { container, getAllByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
pin
/>
</Grommet>,
);
const results = getAllByText('entry', { exact: false });
expect(results.length).toEqual(50);
expect(container.firstChild).toMatchSnapshot();
});
test('should apply pagination styling', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate={{ background: 'red', margin: 'large' }}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('should show correct item index when "show" is a number', () => {
const show = 15;
const { container, getByText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
show={show}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
show={{ page: desiredPage }}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
step={step}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={DATA}
paginate
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[]}
paginate
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('should not show paginate controls when length of data < step', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: `entry-1`, b: 1 },
{ a: `entry-2`, b: 2 },
{ a: `entry-3`, b: 3 },
]}
paginate
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('onSelect select/unselect all', () => {
const onSelect = jest.fn();
const { container, getByLabelText } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B', primary: true },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B', primary: true },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy="a"
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B', primary: true },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy="a"
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy="a"
primaryKey="b"
onSelect={onSelect}
/>
</Grommet>,
);
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(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B', primary: true },
]}
data={[
{ a: 'one', b: 1.1 },
{ a: 'one', b: 1.2 },
{ a: 'two', b: 2.1 },
{ a: 'two', b: 2.2 },
]}
groupBy="a"
select={[1.1]}
onSelect={onSelect}
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('verticalAlign', () => {
const { asFragment } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b.c', header: 'B' },
]}
data={[
{ a: 'one', b: { c: 1 } },
{ a: 'two', b: { c: 2 } },
]}
verticalAlign="top"
/>
<DataTable
columns={[
{
property: 'This is a long header that wraps',
header: 'A',
footer: 'This is a long footer that wraps',
size: 'xsmall',
},
{ property: 'b.c', header: 'B' },
]}
data={[
{ a: 'this is long data that might wrap also', b: { c: 1 } },
{ a: 'two', b: { c: 2 } },
]}
verticalAlign={{
header: 'bottom',
body: 'top',
footer: 'top',
}}
/>
</Grommet>,
);
expect(asFragment()).toMatchSnapshot();
});
test('should base table body max height on global size', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
size="small"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('should base table body max height on css value', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{ property: 'a', header: 'A' },
{ property: 'b', header: 'B' },
]}
data={[
{ a: 'one', b: 1 },
{ a: 'two', b: 2 },
]}
size="50px"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('rowProps on group header rows', () => {
const { container } = render(
<Grommet>
<DataTable
columns={[
{
property: 'location',
header: 'Location',
},
{
property: 'name',
header: <Text>Name with extra</Text>,
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"
/>
</Grommet>,
);
expect(container.firstChild).toMatchSnapshot();
});
test('border on CheckBox cell', () => {
const { asFragment } = render(
<Grommet>
<DataTable
data={[
{ name: 'Alan', percent: 20 },
{ name: 'Bryan', percent: 30 },
{ name: 'Chris', percent: 20 },
{ name: 'Eric', percent: 80 },
]}
columns={[
{
property: 'name',
header: 'Name',
primary: true,
},
{
property: 'percent',
header: 'Percent Complete',
},
]}
border={{ body: { side: 'bottom' } }}
onSelect={() => {}}
/>
</Grommet>,
);
expect(asFragment()).toMatchSnapshot();
});
test('onSelect datum argument should be defined', () => {
const onSelect = jest.fn();
render(
<Grommet>
<DataTable
onSelect={onSelect}
data={[
{ name: 'Alan', percent: 20 },
{ name: 'Bryan', percent: 30 },
]}
columns={[
{
property: 'name',
header: 'Name',
primary: true,
},
{
property: 'percent',
header: 'Percent Complete',
},
]}
/>
</Grommet>,
);
fireEvent.click(screen.getByRole('checkbox', { name: 'select Alan' }));
expect(onSelect).toBeCalledWith(['Alan'], { name: 'Alan', percent: 20 });
});
test('Data + onSelect should display correct selected amount', () => {
const App = () => {
const [select, setSelect] = useState<(string | number)[]>([]);
return (
<Grommet>
<Data data={DATA} toolbar>
<DataTable
select={select}
onSelect={(select, _) => setSelect(select)}
/>
<Pagination />
</Data>
</Grommet>
);
};
render(<App />);
const headerCheckBox = screen.getByLabelText('select all');
// select all first page
fireEvent.click(headerCheckBox);
expect(screen.getByText('10 selected')).toBeTruthy();
// navigate to new page
const secondPageButton = screen.getByRole('button', {
name: 'Go to page 2',
});
fireEvent.click(secondPageButton);
// select all second page
fireEvent.click(headerCheckBox);
// should add to previous selection
expect(screen.getByText('20 selected')).toBeTruthy();
// apply filters and select view
const filterButton = screen.getByRole('button', { name: 'Open filters' });
fireEvent.click(filterButton);
fireEvent.click(screen.getByRole('checkbox', { name: 'option 1' }));
fireEvent.click(screen.getByRole('button', { name: 'Apply filters' }));
fireEvent.click(headerCheckBox);
expect(screen.getByText('22 selected')).toBeTruthy();
// unselect single checkbox
fireEvent.click(
screen.getByRole('checkbox', { name: 'unselect entry-35' }),
);
expect(screen.getByText('21 selected')).toBeTruthy();
// clear view + unselect all from current page
fireEvent.click(screen.getByRole('button', { name: 'Clear filters' }));
fireEvent.click(headerCheckBox);
expect(screen.getByText('11 selected')).toBeTruthy();
});
test('onUpdate groupBy.onSelect', () => {
const onGroupSelect = jest.fn();
const App = () => {
const [groupSelected] = React.useState<{
[key: string]: 'all' | 'some' | 'none';
}>({ '': 'some', 'Fort Collins': 'some' });
const groupBy = React.useMemo(() => {
return {
property: 'location',
select: groupSelected,
onSelect: onGroupSelect,
};
}, [groupSelected]);
return (
<DataTable
primaryKey="id"
columns={[
{
property: 'id',
header: 'ID',
},
{
property: 'b',
header: 'B',
},
{
property: 'location',
header: 'Location',
},
]}
data={[
{
id: 0,
b: 'zero',
location: 'Fort Collins',
},
{
id: 1,
b: 'one',
location: 'Fort Collins',
},
{
id: 2,
b: 'two',
location: 'San Francisco',
},
{
id: 3,
b: 'three',
location: 'San Francisco',
},
{
id: 4,
b: 'four',
location: 'Boise',
},
{
id: 5,
b: 'five',
location: 'Los Angeles',
},
]}
step={10}
groupBy={groupBy}
onSelect={() => {}}
onUpdate={() => {}}
/>
);
};
const { asFragment } = render(<App />);
expect(asFragment()).toMatchSnapshot();
fireEvent.click(
screen.getByRole('checkbox', { name: 'select San Francisco' }),
);
expect(onGroupSelect).toHaveBeenCalledWith(
[2, 3],
{ location: 'San Francisco' },
{ '': 'some', 'Fort Collins': 'some' },
);
fireEvent.click(screen.getByRole('checkbox', { name: 'select all' }));
expect(onGroupSelect).toHaveBeenCalledWith([0, 1, 2, 3, 4, 5], undefined, {
'': 'all',
});
});
});