Skip to content

Commit dc1fb79

Browse files
committed
allow matching queryAllRowsByFirstCellText with a regex
1 parent 9d90219 commit dc1fb79

6 files changed

+235
-99
lines changed

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ expect(getRowByFirstCellText('John Smith')).toBeVisible()
113113
fireEvent.click(within(getRowByFirstCellText('John Smith')).getByText('Delete'))
114114
```
115115

116-
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.
116+
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. You can also use regular expression matching instead of looking for an exact text:
117+
118+
```js
119+
expect(getRowByFirstCellText(/John Smith/)).toBeVisible()
120+
```
117121

118122
### Column cells by header text
119123

@@ -125,7 +129,7 @@ ageCells.forEach((cell, index) => {
125129
})
126130
```
127131

128-
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:
132+
Returns an array of cells based on the text (or a regex) 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:
129133

130134
```js
131135
const { getAllColumnCellsByHeaderText } = render(<MyTable />)
@@ -141,7 +145,7 @@ expect(getCellByRowAndColumnHeaders('John Smith', 'Age')).toHaveTextContent(
141145
)
142146
```
143147

144-
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.
148+
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 (or regex), it only uses the first column with the specified header text (or regex) (but will handle multiple rows), and supports a header index.
145149

146150
## Examples
147151

@@ -150,7 +154,6 @@ See [example tests](./example/src/SimpleTable.test.js)
150154
## Future changes
151155

152156
- Address the first column limitation
153-
- Allow custom text normalisation/matching
154157
- Allow Nth cell in a row, rather than just first
155158

156159
## Development

src/cellByRowAndColumnHeaders.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { nthHeaderError } from './utils/nthHeaderError'
66

77
function queryAllCellsByRowAndColumnHeaders(
88
container: HTMLElement,
9-
rowHeaderText: string,
10-
columnheaderText: string,
9+
rowHeaderTextQuery: string | RegExp,
10+
columnheaderTextQuery: string | RegExp,
1111
headerRowIndex = 0
1212
) {
13-
const rows = queryAllRowsByFirstCellText(container, rowHeaderText)
13+
const rows = queryAllRowsByFirstCellText(container, rowHeaderTextQuery)
1414

1515
const columnIndex = getColumnIndexByHeaderText(
1616
container,
17-
columnheaderText,
17+
columnheaderTextQuery,
1818
headerRowIndex
1919
)
2020

@@ -25,17 +25,18 @@ function queryAllCellsByRowAndColumnHeaders(
2525

2626
const getMultipleError = (
2727
_c: Element | null,
28-
rowHeaderText: string,
29-
columnheaderText: string,
28+
rowHeaderText: string | RegExp,
29+
columnheaderText: string | RegExp,
3030
headerRowIndex = 0
3131
) =>
3232
`Found multiple cells with ${rowHeaderText} in the first column and ${columnheaderText} in the ${nthHeaderError(
3333
headerRowIndex
3434
)}`
35+
3536
const getMissingError = (
3637
_c: Element | null,
37-
rowHeaderText: string,
38-
columnheaderText: string,
38+
rowHeaderText: string | RegExp,
39+
columnheaderText: string | RegExp,
3940
headerRowIndex = 0
4041
) =>
4142
`Found no rows with ${rowHeaderText} in the first column and ${columnheaderText} in the ${nthHeaderError(

src/columnCellsByHeaderText.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { nthHeaderError } from './utils/nthHeaderError'
66

77
function queryAllColumnCellsByHeaderText(
88
container: HTMLElement,
9-
textContent: string,
9+
textQuery: string | RegExp,
1010
headerRowIndex = 0
1111
) {
1212
const cellIndex = getColumnIndexByHeaderText(
1313
container,
14-
textContent,
14+
textQuery,
1515
headerRowIndex
1616
)
1717

@@ -24,18 +24,33 @@ function queryAllColumnCellsByHeaderText(
2424

2525
const getMultipleError = (
2626
_c: Element | null,
27-
textContent: string,
27+
textQuery: string | RegExp,
2828
headerRowIndex = 0
29-
) =>
30-
`Found multiple cells with ${textContent} in the ${nthHeaderError(
29+
) => {
30+
if (typeof textQuery === 'string') {
31+
return `Found multiple cells with ${textQuery} in the ${nthHeaderError(
32+
headerRowIndex
33+
)}`
34+
}
35+
return `Found multiple cells matching ${textQuery} in the ${nthHeaderError(
3136
headerRowIndex
3237
)}`
38+
}
39+
3340
const getMissingError = (
3441
_c: Element | null,
35-
textContent: string,
42+
textQuery: string | RegExp,
3643
headerRowIndex = 0
37-
) =>
38-
`Found no rows with ${textContent} in the ${nthHeaderError(headerRowIndex)}`
44+
) => {
45+
if (typeof textQuery === 'string') {
46+
return `Found no rows with ${textQuery} in the ${nthHeaderError(
47+
headerRowIndex
48+
)}`
49+
}
50+
return `Found no rows matching ${textQuery} in the ${nthHeaderError(
51+
headerRowIndex
52+
)}`
53+
}
3954

4055
const [
4156
queryColumnCellByHeaderText,

src/queries.test.ts

+163-66
Original file line numberDiff line numberDiff line change
@@ -206,77 +206,174 @@ Ignored nodes: comments, script, style
206206
expect(queries.getAllCells(container)).toHaveLength(48)
207207
})
208208

209-
it('should find cells by row and column headings', () => {
210-
const container = render(simpleTable)
211-
expect(
212-
queries.getCellByRowAndColumnHeaders(container, 'trouble', 'Status').id
213-
).toEqual('body-cell-29')
214-
expect(
215-
queries.getCellByRowAndColumnHeaders(container, 'reason', 'Age').id
216-
).toEqual('body-cell-21')
217-
expect(
218-
queries.queryCellByRowAndColumnHeaders(container, 'NOT A ROW', 'Status')
219-
).toBeNull()
220-
expect(
221-
queries.queryCellByRowAndColumnHeaders(
209+
describe('should find cells by row and column headings', () => {
210+
it('using exact test match', () => {
211+
const container = render(simpleTable)
212+
expect(
213+
queries.getCellByRowAndColumnHeaders(container, 'trouble', 'Status').id
214+
).toEqual('body-cell-29')
215+
expect(
216+
queries.getCellByRowAndColumnHeaders(container, 'reason', 'Age').id
217+
).toEqual('body-cell-21')
218+
expect(
219+
queries.queryCellByRowAndColumnHeaders(container, 'NOT A ROW', 'Status')
220+
).toBeNull()
221+
expect(
222+
queries.queryCellByRowAndColumnHeaders(
223+
container,
224+
'trouble',
225+
'Not a column'
226+
)
227+
).toBeNull()
228+
})
229+
it('using a regex', () => {
230+
const container = render(simpleTable)
231+
expect(
232+
queries.getCellByRowAndColumnHeaders(container, /trouble/, /Status/).id
233+
).toEqual('body-cell-29')
234+
expect(
235+
queries.getCellByRowAndColumnHeaders(container, /reason/, /Age/).id
236+
).toEqual('body-cell-21')
237+
expect(
238+
queries.queryCellByRowAndColumnHeaders(container, /NOT A ROW/, /Status/)
239+
).toBeNull()
240+
expect(
241+
queries.queryCellByRowAndColumnHeaders(
242+
container,
243+
/trouble/,
244+
/Not a column/
245+
)
246+
).toBeNull()
247+
expect(
248+
queries.queryCellByRowAndColumnHeaders(container, /.*/, /Not a column/)
249+
).toBeNull()
250+
expect(
251+
queries.queryAllCellsByRowAndColumnHeaders(container, /.*/, /Age/)
252+
).toHaveLength(8)
253+
expect(
254+
queries.queryAllCellsByRowAndColumnHeaders(container, /reason/, /.*/)
255+
).toHaveLength(1)
256+
expect(
257+
queries.queryAllCellsByRowAndColumnHeaders(container, /.*/, /.*/)
258+
).toHaveLength(8)
259+
})
260+
})
261+
262+
describe('should find column cells by header text', () => {
263+
it('using exact test match', () => {
264+
const container = render(simpleTable)
265+
expect(
266+
queries.queryAllColumnCellsByHeaderText(container, 'NOT A COLUMN')
267+
).toHaveLength(0)
268+
const ageCells = queries.getAllColumnCellsByHeaderText(container, 'Age')
269+
expect(ageCells).toHaveLength(8)
270+
expect(ageCells.map((cell) => cell.id)).toEqual([
271+
'header-cell-3',
272+
'body-cell-3',
273+
'body-cell-9',
274+
'body-cell-15',
275+
'body-cell-21',
276+
'body-cell-27',
277+
'body-cell-33',
278+
'body-cell-39'
279+
])
280+
const statusCells = queries.getAllColumnCellsByHeaderText(
222281
container,
223-
'trouble',
224-
'Not a column'
282+
/Status/
225283
)
226-
).toBeNull()
227-
})
284+
expect(statusCells).toHaveLength(8)
285+
expect(statusCells.map((cell) => cell.id)).toEqual([
286+
'header-cell-5',
287+
'body-cell-5',
288+
'body-cell-11',
289+
'body-cell-17',
290+
'body-cell-23',
291+
'body-cell-29',
292+
'body-cell-35',
293+
'body-cell-41'
294+
])
295+
})
228296

229-
it('should find column cells by header text', () => {
230-
const container = render(simpleTable)
231-
expect(
232-
queries.queryAllColumnCellsByHeaderText(container, 'NOT A COLUMN')
233-
).toHaveLength(0)
234-
const ageCells = queries.getAllColumnCellsByHeaderText(container, 'Age')
235-
expect(ageCells).toHaveLength(8)
236-
expect(ageCells.map((cell) => cell.id)).toEqual([
237-
'header-cell-3',
238-
'body-cell-3',
239-
'body-cell-9',
240-
'body-cell-15',
241-
'body-cell-21',
242-
'body-cell-27',
243-
'body-cell-33',
244-
'body-cell-39'
245-
])
246-
const statusCells = queries.getAllColumnCellsByHeaderText(
247-
container,
248-
'Status'
249-
)
250-
expect(statusCells).toHaveLength(8)
251-
expect(statusCells.map((cell) => cell.id)).toEqual([
252-
'header-cell-5',
253-
'body-cell-5',
254-
'body-cell-11',
255-
'body-cell-17',
256-
'body-cell-23',
257-
'body-cell-29',
258-
'body-cell-35',
259-
'body-cell-41'
260-
])
297+
it('using a regex match', () => {
298+
const container = render(simpleTable)
299+
expect(
300+
queries.queryAllColumnCellsByHeaderText(container, /NOT A COLUMN/)
301+
).toHaveLength(0)
302+
expect(
303+
queries.queryAllColumnCellsByHeaderText(container, /.*/)
304+
).toHaveLength(8)
305+
const ageCells = queries.getAllColumnCellsByHeaderText(container, /Age/)
306+
expect(ageCells).toHaveLength(8)
307+
expect(ageCells.map((cell) => cell.id)).toEqual([
308+
'header-cell-3',
309+
'body-cell-3',
310+
'body-cell-9',
311+
'body-cell-15',
312+
'body-cell-21',
313+
'body-cell-27',
314+
'body-cell-33',
315+
'body-cell-39'
316+
])
317+
const statusCells = queries.getAllColumnCellsByHeaderText(
318+
container,
319+
/Status/
320+
)
321+
expect(statusCells).toHaveLength(8)
322+
expect(statusCells.map((cell) => cell.id)).toEqual([
323+
'header-cell-5',
324+
'body-cell-5',
325+
'body-cell-11',
326+
'body-cell-17',
327+
'body-cell-23',
328+
'body-cell-29',
329+
'body-cell-35',
330+
'body-cell-41'
331+
])
332+
})
261333
})
262334

263-
it('should find rows by the first cell text', () => {
264-
const container = render(simpleTable)
265-
expect(
266-
queries.queryAllRowsByFirstCellText(container, 'NOT A ROW')
267-
).toHaveLength(0)
268-
expect(queries.getAllRowsByFirstCellText(container, 'reason')).toHaveLength(
269-
1
270-
)
271-
expect(queries.getRowByFirstCellText(container, 'reason').id).toEqual(
272-
'body-row-4'
273-
)
274-
expect(queries.getRowByFirstCellText(container, 'First Name').id).toEqual(
275-
'header-row'
276-
)
277-
expect(queries.getRowByFirstCellText(container, 'midnight').id).toEqual(
278-
'body-row-2'
279-
)
335+
describe('should find rows by the first cell text', () => {
336+
it('using exact text match', () => {
337+
const container = render(simpleTable)
338+
expect(
339+
queries.queryAllRowsByFirstCellText(container, 'NOT A ROW')
340+
).toHaveLength(0)
341+
expect(
342+
queries.getAllRowsByFirstCellText(container, 'reason')
343+
).toHaveLength(1)
344+
expect(queries.getRowByFirstCellText(container, 'reason').id).toEqual(
345+
'body-row-4'
346+
)
347+
expect(queries.getRowByFirstCellText(container, 'First Name').id).toEqual(
348+
'header-row'
349+
)
350+
expect(queries.getRowByFirstCellText(container, 'midnight').id).toEqual(
351+
'body-row-2'
352+
)
353+
})
354+
355+
it('using a regex', () => {
356+
const container = render(simpleTable)
357+
expect(
358+
queries.queryAllRowsByFirstCellText(
359+
container,
360+
/this-regex-has-no-match/
361+
)
362+
).toHaveLength(0)
363+
expect(
364+
queries.getAllRowsByFirstCellText(container, /reas*/)
365+
).toHaveLength(1)
366+
expect(queries.getAllRowsByFirstCellText(container, /.*/)).toHaveLength(8)
367+
expect(queries.getRowByFirstCellText(container, 'reason').id).toEqual(
368+
'body-row-4'
369+
)
370+
expect(queries.getRowByFirstCellText(container, 'First Name').id).toEqual(
371+
'header-row'
372+
)
373+
expect(queries.getRowByFirstCellText(container, 'midnight').id).toEqual(
374+
'body-row-2'
375+
)
376+
})
280377
})
281378

282379
it('should find rowgroups', () => {

0 commit comments

Comments
 (0)