wix-style-react
Version:
669 lines (579 loc) • 22.5 kB
JavaScript
import React from 'react';
import eventually from 'wix-eventually';
import { mount } from 'enzyme';
import {
createRendererWithDriver,
createRendererWithUniDriver,
cleanup,
} from '../../../../test/utils/react';
import { enzymeUniTestkitFactoryCreator } from 'wix-ui-test-utils/enzyme';
import DataTable from '../DataTable';
import dataTableDriverFactory from '../DataTable.driver';
import { dataTableUniDriverFactory } from '../DataTable.uni.driver';
describe('Table', () => {
describe('[sync]', () => {
runTests(createRendererWithDriver(dataTableDriverFactory));
});
describe('[async]', () => {
runTests(createRendererWithUniDriver(dataTableUniDriverFactory));
});
function runTests(render) {
afterEach(() => cleanup());
const createDriver = jsx => render(jsx).driver;
const createDefaultProps = () => ({
id: 'id',
data: [
{ a: 'value 1', b: 'value 2' },
{ a: 'value 3', b: 'value 4' },
],
columns: [
{ title: 'Row Num', render: (row, rowNum) => rowNum },
{ title: 'A', render: row => row.a },
{ title: 'B', render: row => row.b },
],
rowClass: 'class-name',
});
const defaultProps = createDefaultProps();
it('should pass id prop to child', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.hasChildWithId(defaultProps.id)).toBe(true);
});
describe('data is empty', () => {
const rowDataHook = 'row-data-hook';
const props = Object.assign({}, defaultProps, {
data: [],
rowDataHook,
});
it('should not render the component when no data supplied', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.exists()).toEqual(false);
});
it('should render the component when data is supplied', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.exists()).toEqual(true);
});
it('should display nothing', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isDisplayingNothing()).toBe(true);
});
it('should count 0 rows', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.getRowsCount()).toEqual(0);
});
it('should count 0 rows with class name', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(
await driver.getRowsWithClassCount(defaultProps.rowClass),
).toEqual(0);
});
it('should find 0 rows with data-hook', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.getRowsWithDataHook(rowDataHook)).toHaveLength(0);
});
it('should not find a row with data-hook', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.getRowWithDataHook(rowDataHook)).toEqual(null);
});
it('should not a header only', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isDisplayingHeaderOnly()).toBeFalsy;
});
it('should not a header ', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isDisplayingHeader()).toBeFalsy;
});
it('should not find a child with id', async () => {
const driver = createDriver(<DataTable {...props} />);
expect(await driver.hasChildWithId(defaultProps.id)).toBeFalsy;
});
// There rest of the driver's methods would throw some error
});
it('should display something when data is non-empty', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.isDisplayingNothing()).toBe(false);
});
it('should display header only when data is empty and showHeaderWhenEmpty is true', async () => {
const props = {
...defaultProps,
data: [],
showHeaderWhenEmpty: true,
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isDisplayingHeaderOnly()).toBe(true);
});
it('should render column titles', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.getTitles()).toEqual(
defaultProps.columns.map(col => col.title),
);
});
describe('column width', () => {
const columns = [
{ title: 'Row Num', width: '30%', render: (row, rowNum) => rowNum },
{ title: 'A', width: '40px', render: row => row.a },
{ title: 'B', width: '50px', render: row => row.b },
];
it('should apply column.width', async () => {
const driver = createDriver(
<DataTable {...defaultProps} columns={columns} />,
);
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
const column = columns[colIndex];
expect(await driver.getHeaderCellWidth(colIndex)).toEqual(
column.width,
);
for (
let rowIndex = 0;
rowIndex < (await driver.getRowsCount());
rowIndex++
) {
expect(await driver.getCellWidth(rowIndex, colIndex)).toBe('');
}
}
});
it('should apply column.width when header is hidden', async () => {
const driver = createDriver(
<DataTable {...defaultProps} columns={columns} hideHeader />,
);
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
const column = columns[colIndex];
for (
let rowIndex = 0;
rowIndex < (await driver.getRowsCount());
rowIndex++
) {
if (rowIndex === 0) {
expect(await driver.getCellWidth(rowIndex, colIndex)).toEqual(
column.width,
);
} else {
expect(await driver.getCellWidth(rowIndex, colIndex)).toBe('');
}
}
}
});
});
it('should display correct amount of rows', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.getRowsCount()).toBe(defaultProps.data.length);
});
it('should render rows', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.getRowText(0)).toEqual(['0', 'value 1', 'value 2']);
expect(await driver.getRowText(1)).toEqual(['1', 'value 3', 'value 4']);
});
it('should assign class to rows', async () => {
const driver = createDriver(<DataTable {...defaultProps} />);
expect(await driver.getRowsWithClassCount(defaultProps.rowClass)).toBe(
defaultProps.data.length,
);
});
it('should assign a dynamic class to rows', async () => {
const getClass = rowData => rowData.a.replace(/[\s]+/g, '-');
const driver = createDriver(
<DataTable {...defaultProps} dynamicRowClass={getClass} />,
);
expect(await driver.getRowsWithClassCount('value-1')).toBe(1);
expect(await driver.getRowsWithClassCount('value-3')).toBe(1);
expect(await driver.getRowsWithClassCount(defaultProps.rowClass)).toBe(
defaultProps.data.length,
);
});
it('should assign dataHook to rows', async () => {
const rowDataHook = 'row-data-hook';
const props = Object.assign({}, defaultProps, { rowDataHook });
const driver = createDriver(<DataTable {...props} />);
expect(
(await driver.getRowsWithDataHook(rowDataHook))[0].textContent,
).toBe('0value 1value 2');
expect(
(await driver.getRowsWithDataHook(rowDataHook))[1].textContent,
).toBe('1value 3value 4');
expect(await driver.getRowsWithDataHook(rowDataHook)).toHaveLength(
defaultProps.data.length,
);
});
it('should assign a dynamic dataHook to rows', async () => {
const calcDataHook = (rowData, rowIndex) =>
`row-index-${rowIndex}-a-${rowData.a.replace(' ', '_')}`;
const props = Object.assign({}, defaultProps, {
rowDataHook: calcDataHook,
});
const driver = createDriver(<DataTable {...props} />);
expect(
(await driver.getRowWithDataHook(`row-index-0-a-value_1`)).textContent,
).toBe('0value 1value 2');
expect(
(await driver.getRowWithDataHook(`row-index-1-a-value_3`)).textContent,
).toBe('1value 3value 4');
});
it('should get a row classes', async () => {
const getDynamicClass = (rowData, rowNum) =>
rowNum === 1 ? 'rowNum1' : '';
const driver = createDriver(
<DataTable {...defaultProps} dynamicRowClass={getDynamicClass} />,
);
expect((await driver.getRowClasses(1)).sort()).toEqual([
'class-name',
'rowNum1',
]);
});
it('should hide table header', async () => {
const driver = createDriver(<DataTable {...defaultProps} hideHeader />);
expect(await driver.isDisplayingHeader()).toBe(false);
});
it('should override default table header styles', async () => {
const inlineThStyle = {
thPadding: '1px',
thHeight: '2px',
thFontSize: '3px',
thBorder: '4px',
thColor: 'rgb(18, 52, 86)',
thOpacity: '0.8',
thLetterSpacing: '1.5px',
};
const driver = createDriver(
<DataTable {...defaultProps} {...inlineThStyle} />,
);
expect(await driver.getHeaderCellStyle(0)).toEqual(
jasmine.objectContaining({
padding: '1px',
height: '2px',
'font-size': '3px',
border: '4px',
color: 'rgb(18, 52, 86)',
opacity: '0.8',
'letter-spacing': '1.5px',
}),
);
});
it('should override default cell styles', async () => {
const clonedProps = Object.assign({}, defaultProps);
clonedProps.columns.push({
title: 'c',
render: () => 'c',
style: {
paddingLeft: '1px',
height: '2px',
width: '100px',
},
});
const driver = createDriver(<DataTable {...clonedProps} />);
expect(await driver.getCellStyle(0, 3)).toEqual(
jasmine.objectContaining({
'padding-left': '1px',
height: '2px',
width: '100px',
}),
);
});
it('should override default cell styles using function', async () => {
const clonedProps = Object.assign({}, defaultProps);
clonedProps.columns.push({
title: 'c',
render: () => 'c',
style: (_column, _row, rowNum) => ({
paddingLeft: rowNum + 'px',
}),
});
const driver = createDriver(<DataTable {...clonedProps} />);
expect(await driver.getCellStyle(1, 3)).toEqual(
jasmine.objectContaining({
'padding-left': '1px',
}),
);
});
describe('clickableDataRow class', () => {
it('should not assign the class to rows by default', async () => {
const props = { ...defaultProps };
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isRowClickable(0)).toBe(false);
});
it('should assign the class to rows when onRowClick prop is provided', async () => {
const props = {
...defaultProps,
onRowClick: jest.fn(),
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isRowClickable(0)).toBe(true);
});
it('should not assign the class to rows when isRowDisabled returns true', async () => {
const props = {
...defaultProps,
onRowClick: jest.fn(),
isRowDisabled: () => true,
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isRowClickable(0)).toBe(false);
});
});
describe('animatedDataRow class', () => {
it('should not assign the class to rows by default', async () => {
const props = { ...defaultProps };
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isRowAnimated(0)).toBe(false);
});
it('should assign the class to rows when rowDetails prop is provided', async () => {
const props = {
...defaultProps,
rowDetails: row => <span>{row.a}</span>,
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.isRowAnimated(0)).toBe(true);
});
});
describe('Row event handlers', () => {
const tests = [
{ handler: 'onRowClick', driverMethod: 'clickRow' },
{ handler: 'onMouseEnterRow', driverMethod: 'mouseEnterRow' },
{ handler: 'onMouseLeaveRow', driverMethod: 'mouseLeaveRow' },
];
tests.forEach(({ handler, driverMethod }) => {
it(`should call ${handler} with row data and index`, async () => {
const props = {
...defaultProps,
[handler]: jest.fn(),
};
const driver = createDriver(<DataTable {...props} />);
await driver[driverMethod](0);
expect(props[handler]).toBeCalledWith(props.data[0], 0);
await driver[driverMethod](1);
expect(props[handler]).toHaveBeenLastCalledWith(props.data[1], 1);
});
});
it(`should not call onRowClick if row is disabled`, async () => {
const props = {
...defaultProps,
onRowClick: jest.fn(),
isRowDisabled: row => row === defaultProps.data[0],
};
const driver = createDriver(<DataTable {...props} />);
await driver.clickRow(0);
expect(props.onRowClick).not.toBeCalledWith(props.data[0], 0);
await driver.clickRow(1);
expect(props.onRowClick).toBeCalledWith(props.data[1], 1);
});
it('should expand with correct content and collapse', async () => {
const animationSpeed = 500;
const props = {
...defaultProps,
rowDetails: row => <span>{row.a}</span>,
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.hasRowDetails(0)).toBe(true);
expect(await driver.getRowDetailsText(0)).toBe('');
await driver.clickRow(0);
// After clicking content will appear at once
expect(await driver.getRowDetailsText(0)).toBe(defaultProps.data[0].a);
await driver.clickRow(0);
expect(await driver.hasRowDetails(0)).toBe(true);
// When we clicking second time to collapse content will disappear after a while (based on animation speed)
expect(await driver.getRowDetailsText(0)).not.toBe('');
return eventually(
async () => {
expect(await driver.getRowDetailsText(0)).toBe('');
},
{ timeout: animationSpeed },
);
});
it('should position rowDetails correctly when adding rows to the top of the table', async () => {
const props = {
data: [{ a: 'foo', b: 'bar' }],
columns: [
{ title: 'Row Num', render: (row, rowNum) => rowNum },
{ title: 'A', render: row => row.a },
{ title: 'B', render: row => row.b },
],
rowDetails: row => row.a,
};
const dataHook = 'myDataHook';
const wrapper = mount(<DataTable {...props} dataHook={dataHook} />);
const dataTableEnzymeTestkitFactory = enzymeUniTestkitFactoryCreator(
dataTableUniDriverFactory,
);
const dataTableTestkit = dataTableEnzymeTestkitFactory({
wrapper,
dataHook,
});
expect(await dataTableTestkit.getRowDetailsText(0)).toBe('');
await dataTableTestkit.clickRow(0);
expect(await dataTableTestkit.getRowDetailsText(0)).toBe('foo');
wrapper.setProps({
data: [{ a: 'value 5', b: 'value 6' }, ...props.data],
});
await eventually(async () => {
expect(await dataTableTestkit.getRowDetailsText(0)).toBe('');
expect(await dataTableTestkit.getRowDetailsText(1)).toBe('foo');
});
});
it('should have correct row count when row details enabled', async () => {
const props = {
...defaultProps,
rowDetails: jest.fn(),
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.getRowsCount()).toBe(2);
await driver.clickRow(0);
expect(await driver.getRowsCount()).toBe(2);
});
});
describe('onCellClick', () => {
it(`should call onCellClick with column data, row data and index`, async () => {
const columnClickHandler = jest.fn();
const props = {
...defaultProps,
columns: [
{
title: 'Row Num',
render: (row, rowNum) => rowNum,
onCellClick: columnClickHandler,
},
{ title: 'A', render: row => row.a },
],
};
const driver = createDriver(<DataTable {...props} />);
await driver.clickColumn(0, 0);
expect(columnClickHandler).toBeCalledWith(
props.columns[0],
props.data[0],
0,
expect.anything(),
);
columnClickHandler.mockReset();
await driver.clickColumn(0, 1);
expect(columnClickHandler).not.toBeCalled();
});
});
describe('Sortable column titles', () => {
let props;
beforeEach(() => {
props = {
...defaultProps,
columns: [
{ title: 'Row Num', render: (row, rowNum) => rowNum },
{
title: 'A',
sortable: true,
sortDescending: false,
render: row => row.a,
},
{ title: 'B', render: row => row.b },
{
title: 'C',
sortable: true,
sortDescending: true,
render: row => row.a,
},
],
};
});
it('should display sortable title', async () => {
const _props = Object.assign({}, props, { onSortClick: jest.fn() });
const driver = createDriver(<DataTable {..._props} />);
expect(await driver.hasSortableTitle(0)).toBe(false);
expect(await driver.hasSortableTitle(1)).toBe(true);
});
it('should display sort asc/desc style', async () => {
const _props = Object.assign({}, props, { onSortClick: jest.fn() });
const driver = createDriver(<DataTable {..._props} />);
expect(await driver.hasSortDescending(1)).toBe(false);
expect(await driver.hasSortDescending(3)).toBe(true);
});
it('should call on sort callback', async () => {
const _props = Object.assign({}, props, { onSortClick: jest.fn() });
const driver = createDriver(<DataTable {..._props} />);
await driver.clickSort(1);
expect(_props.onSortClick).toBeCalledWith(props.columns[1], 1);
});
it('should not call on sort callback when non-sortable column is clicked', async () => {
const _props = Object.assign({}, props, { onSortClick: jest.fn() });
const driver = createDriver(<DataTable {..._props} />);
await driver.clickSort(2);
expect(_props.onSortClick).not.toHaveBeenCalled();
});
});
describe('Tooltip titles', () => {
it('should display tooltip icon', async () => {
const props = {
...defaultProps,
columns: [
{ title: 'Row Num', render: (row, rowNum) => rowNum },
{
title: 'A',
infoTooltipProps: {
content: 'Vary informative tooltip text',
children: false,
},
render: row => row.a,
},
{ title: 'B', render: row => row.b },
],
};
const driver = createDriver(<DataTable {...props} />);
expect(await driver.hasInfoIcon(0)).toBe(false);
expect(await driver.hasInfoIcon(1)).toBe(true);
});
});
describe('Virtualization', () => {
it('should render a small portion of the lines', async () => {
const tableProps = createDefaultProps();
tableProps.data = [...Array(14)].map((_n, i) => ({
id: i,
a: `value a ${i}`,
b: `value b ${i}`,
}));
const props = {
...tableProps,
virtualized: true,
virtualizedTableHeight: 350,
virtualizedLineHeight: 60,
};
const driver = createDriver(<DataTable {...props} />);
// 5 items can fit in fully.
// 1 item appear only partially
// 2 items are for better scrolling reasons
expect(await driver.getRowsCount()).toBe(8);
});
});
it('should highlight even rows', async () => {
const driver = createDriver(
<DataTable
{...defaultProps}
isRowHighlight={(_, rowNum) => rowNum % 2 === 0}
/>,
);
const classes = (await driver.getRowClasses(0)).sort();
expect(classes[0]).toContain('highlight');
});
}
describe('row keys', () => {
const createDefaultProps = () => ({
id: 'id',
data: [
{ a: 'value 1', b: 'value 2' },
{ a: 'value 3', b: 'value 4' },
],
columns: [
{ title: 'Row Num', render: (row, rowNum) => rowNum },
{ title: 'A', render: row => row.a },
{ title: 'B', render: row => row.b },
],
rowClass: 'class-name',
});
const getRowKey = (wrapper, index) =>
wrapper.find('tbody tr[data-table-row="dataTableRow"]').at(index).key();
it('should assign data.id as row keys', async () => {
const props = createDefaultProps();
props.data[0].id = '000';
props.data[1].id = '111';
const wrapper = mount(<DataTable {...props} />);
expect(getRowKey(wrapper, 0)).toBe('000');
expect(getRowKey(wrapper, 1)).toBe('111');
});
it('should use rowIndex as keys', async () => {
const wrapper = mount(<DataTable {...createDefaultProps()} />);
expect(getRowKey(wrapper, 0)).toBe('0');
expect(getRowKey(wrapper, 1)).toBe('1');
});
});
});