react-table
Version:
Hooks for building lightweight, fast and extendable datagrids for React
480 lines (443 loc) • 11.7 kB
JavaScript
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',
])
})