Skip to content

lexanth/testing-library-table-queries

Repository files navigation

testing-library-table-queries

Additional testing-library queries for querying tables like a user

NPM

Install

yarn add -D testing-library-table-queries

Usage

These examples all use @testing-library/react, but there isn't anything tying use of this library to that, it should work with anything @testing-library/dom-based.

Inline

import {
  getRowByFirstCellText,
  getAllCells
} from 'testing-library-table-queries'

const { container } = render(<MyTable />)
expect(getRowByFirstCellText(container, 'John Smith')).toBeVisible()
const cells = getAllCells(getRowByFirstCellText(container, 'John Smith'))

Global custom query

For e.g. React, we can add the custom queries to a global custom render (see docs)

import { render, queries } from '@testing-library/react'
import tableQueries from 'testing-library-table-queries'

const customRender = (ui, options) =>
  render(ui, { queries: { ...queries, ...tableQueries }, ...options })

// re-export everything
export * from '@testing-library/react'

// override render method
export { customRender as render }

You can then use the queries just like any other query:

const { getRowByFirstCellText } = render(<MyTable />)
expect(getRowByFirstCellText('John Smith')).toBeVisible()
const cells = within(getRowByFirstCellText('John Smith')).getAllCells()

Limitations

  • Assumes tables are using standard semantic table HTML elements (tbody, thead, tr etc.) with valid DOM nesting.
  • Doesn't handle tables with cells spanning multiple rows (rowspan)
    • But colspan is fine!

Queries

All queries have the standard set of get/getAll/query/queryAll/find/findAll variants. The pluralisation varies slightly to make them sensible.

Rows

const { getAllRows } = render(<MyTable />)
expect(getAllRows()).toHaveLength(5)

Just a shorthand to get all rows. No better than getAllByRole('row'), but used internally by some other queries, so might as well export it.

Cells

const { getAllCells } = render(<MyTable />)
expect(getAllCells()).toHaveLength(48)

Shorthand to get all cells, whether th or td. Most useful when combined with other queries (as is done internally).

expect(within(headerRow).getAllCells()).toHaveLength(6)

Row groups

const { getByRowgroupType, queryByRowgroupType } = render(<MyTable />)
const header = getByRowgroupType('thead')
expect(within(header).getAllCells()).toHaveLength(6)
expect(queryByRowgroupType('tfoot')).toBeNull()

Finds row groups, based on which type of grouping it is (thead, tbody or tfoot). These would typically be indicated to a user through styling.

Rows by row groups

const { getAllRowsByRowgroupType } = render(<MyTable />)
expect(getAllRowsByRowgroupType('tbody')).toHaveLength(8)

Finds rows, based on which type of rowgroup they are in.

Rows by first cell text

const { getRowByFirstCellText } = render(<MyTable />)
expect(getRowByFirstCellText('John Smith')).toBeVisible()
fireEvent.click(within(getRowByFirstCellText('John Smith')).getByText('Delete'))

Users will generally find rows by scanning the content in the first column, then reading across the row. This finds that row (rather than just the first cell), which can then be used to identify other items within that row.

Column cells by header text

const { getAllColumnCellsByHeaderText } = render(<MyTable />)
const ageCells = getAllColumnCellsByHeaderText('Age')
ageCells.forEach((cell, index) => {
  expect(cell).toHaveTextContent(expectedValues[index])
})

Returns an array of cells based on the text in the column header. Note that there is no DOM 'column' element, so it is an array of cells. If multiple columns have the same header text, the first is used. Optionally, this also supports an index (starting from zero) to support having multiple header rows:

const { getAllColumnCellsByHeaderText } = render(<MyTable />)
const ageCells = getAllColumnCellsByHeaderText('Age', 1) // Use the second header row, rather than the first

Cell by row and column headers

const { getCellByRowAndColumnHeaders } = render(<MyTable />)
expect(getCellByRowAndColumnHeaders('John Smith', 'Age')).toHaveTextContent(
  '28'
)

If a user is trying to find a specific value for a specific entity, they might scan from the row and column headers. This finds cells based on those headers. Like column cells by header text, it only uses the first column with the specified header text (but will handle multiple rows), and supports a header index.

Examples

See example tests

Future changes

  • Address the first column limitation
  • Allow custom text normalisation/matching
  • Allow Nth cell in a row, rather than just first

Development

TODO:

  • Set up changelog stuff
  • Publish from CI

Publishing

Run

yarn publish

This will ask for the version bump, publish to NPM, commit the bump to git, tag it.

License

MIT © lexanth