UNPKG

react-table

Version:

Hooks for building lightweight, fast and extendable datagrids for React

480 lines (443 loc) 11.7 kB
import React from 'react' import { render, fireEvent } from '../../../test-utils/react-testing' import { useTable } from '../../hooks/useTable' import { useFilters } from '../useFilters' import { useGlobalFilter } from '../useGlobalFilter' const makeData = () => [ { firstName: 'tanner', lastName: 'linsley', age: 29, visits: 100, status: 'In Relationship', progress: 50, }, { firstName: 'derek', lastName: 'perkins', age: 40, visits: 40, status: 'Single', progress: 80, }, { firstName: 'joe', lastName: 'bergevin', age: 45, visits: 20, status: 'Complicated', progress: 10, }, { firstName: 'jaylen', lastName: 'linsley', age: 26, visits: 99, status: 'In Relationship', progress: 70, }, ] const defaultColumn = { Cell: ({ value, column: { id } }) => `${id}: ${value}`, Filter: ({ column: { filterValue, setFilter } }) => ( <input value={filterValue || ''} onChange={e => { setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely }} placeholder="Search..." /> ), } function App(props) { const [data, setData] = React.useState(makeData) const columns = React.useMemo(() => { if (props.columns) { return props.columns } return [ { Header: 'Name', columns: [ { Header: 'First Name', accessor: 'firstName', }, { Header: 'Last Name', accessor: 'lastName', }, ], }, { Header: 'Info', columns: [ { Header: 'Age', accessor: 'age', }, { Header: 'Visits', accessor: 'visits', }, { Header: 'Status', accessor: 'status', }, { Header: 'Profile Progress', accessor: 'progress', }, ], }, ] }, [props.columns]) const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, visibleColumns, state, setGlobalFilter, } = useTable( { columns, data, defaultColumn, }, useFilters, useGlobalFilter ) const reset = () => setData(makeData()) return ( <> <button onClick={reset}>Reset Data</button> <table {...getTableProps()}> <thead> {headerGroups.map(headerGroup => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map(column => ( <th {...column.getHeaderProps()}> {column.render('Header')} {column.canFilter ? column.render('Filter') : null} </th> ))} </tr> ))} <tr> <th colSpan={visibleColumns.length} style={{ textAlign: 'left', }} > <span> <input value={state.globalFilter || ''} onChange={e => { setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely }} placeholder={`Global search...`} style={{ fontSize: '1.1rem', border: '0', }} /> </span> </th> </tr> </thead> <tbody {...getTableBodyProps()}> {rows.map( (row, i) => prepareRow(row) || ( <tr {...row.getRowProps()}> {row.cells.map(cell => ( <td {...cell.getCellProps()}>{cell.render('Cell')}</td> ))} </tr> ) )} </tbody> </table> </> ) } test('renders a filterable table', async () => { const rendered = render(<App />) const resetButton = rendered.getByText('Reset Data') const globalFilterInput = rendered.getByPlaceholderText('Global search...') const filterInputs = rendered.getAllByPlaceholderText('Search...') expect(filterInputs).toHaveLength(6) fireEvent.change(filterInputs[1], { target: { value: 'l' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: tanner', 'firstName: jaylen']) fireEvent.change(filterInputs[1], { target: { value: 'er' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: derek', 'firstName: joe']) fireEvent.change(filterInputs[2], { target: { value: 'nothing' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([]) fireEvent.change(filterInputs[1], { target: { value: '' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([]) fireEvent.change(filterInputs[2], { target: { value: '' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: tanner', 'firstName: derek', 'firstName: joe', 'firstName: jaylen', ]) fireEvent.change(globalFilterInput, { target: { value: 'li' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: tanner', 'firstName: joe', 'firstName: jaylen']) fireEvent.click(resetButton) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: tanner', 'firstName: derek', 'firstName: joe', 'firstName: jaylen', ]) }) test('does not filter columns marked as disableFilters', () => { const columns = [ { Header: 'Name', columns: [ { Header: 'First Name', accessor: 'firstName', disableFilters: true, }, { Header: 'Last Name', accessor: 'lastName', disableFilters: true, }, ], }, { Header: 'Info', columns: [ { Header: 'Age', accessor: 'age', }, { Header: 'Visits', accessor: 'visits', }, { Header: 'Status', accessor: 'status', }, { Header: 'Profile Progress', accessor: 'progress', }, ], }, ] const rendered = render(<App columns={columns} />) const filterInputs = rendered.getAllByPlaceholderText('Search...') expect(filterInputs).toHaveLength(4) // should be Age column fireEvent.change(filterInputs[0], { target: { value: '45' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: joe']) fireEvent.change(filterInputs[0], { target: { value: '' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: tanner', 'firstName: derek', 'firstName: joe', 'firstName: jaylen', ]) }) test('does not filter columns with GlobalFilter if marked disableGlobalFilter', () => { const columns = [ { Header: 'Name', columns: [ { Header: 'First Name', accessor: 'firstName', disableGlobalFilter: true, }, { Header: 'Last Name', accessor: 'lastName', disableGlobalFilter: true, }, ], }, { Header: 'Info', columns: [ { Header: 'Age', accessor: 'age', }, { Header: 'Visits', accessor: 'visits', }, { Header: 'Status', accessor: 'status', }, { Header: 'Profile Progress', accessor: 'progress', }, ], }, ] const rendered = render(<App columns={columns} />) const globalFilterInput = rendered.getByPlaceholderText('Global search...') fireEvent.change(globalFilterInput, { target: { value: '' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: tanner', 'firstName: derek', 'firstName: joe', 'firstName: jaylen', ]) // global filter shouldn't apply to firstName or lastName fireEvent.change(globalFilterInput, { target: { value: 'li' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: joe']) // double check global filter ignore (should ignore joe bergevin) fireEvent.change(globalFilterInput, { target: { value: 'in' } }) expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual(['firstName: tanner', 'firstName: derek', 'firstName: jaylen']) }) test('test custom filter type', () => { const columns = [ { Header: 'Name', columns: [ { Header: 'First Name', accessor: 'firstName', disableFilters: true, }, { Header: 'Last Name', accessor: 'lastName', disableFilters: true, }, ], }, { Header: 'Status', columns: [ { Header: 'In Relationship', id: 'inRelationship', accessor: ({status}) => status === 'In Relationship', Filter: ({column: {filterValue, setFilter}}) => { const onClick = () => { if (typeof filterValue === 'undefined') { setFilter(false); } else { setFilter(undefined); } } return <button onClick={onClick}> {typeof filterValue === 'undefined' ? 'Any status' : 'Single'} </button> }, // could use 'exact' filter type, but we want to use a custom filter method for this test filter: function strictEquals(rows, id, filterValue) { return rows.filter( row => { const rowValue = row.values[id]; return rowValue === filterValue; }) } } ] } ] const rendered = render(<App columns={columns} />) // check button cycles through states undefined -> false -> undefined const filterButton = rendered.getByText('Any status') fireEvent.click(filterButton, {}); let nextButtonState = rendered.getByText('Single') expect(nextButtonState).toEqual(filterButton); expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: derek', 'firstName: joe', ]) fireEvent.click(filterButton, {}); nextButtonState = rendered.getByText('Any status') expect(nextButtonState).toEqual(filterButton); expect( rendered .queryAllByRole('row') .slice(3) .map(row => Array.from(row.children)[0].textContent) ).toEqual([ 'firstName: tanner', 'firstName: derek', 'firstName: joe', 'firstName: jaylen', ]) })