-
Notifications
You must be signed in to change notification settings - Fork 470
Queries for tables #583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Could you post the markup for your table so that we can identify how testing-library should work? |
As an example:
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>23</td>
<td><button>Delete</button></td>
</tr>
<tr>
<td>Jane</td>
<td>34</td>
<td><button>Delete</button></td>
</tr>
<tr>
<td>Joe</td>
<td>45</td>
<td><button>Delete</button></td>
</tr>
</tbody>
</table> Query examples: expect(getAllByRole('row')[3]).toContainTextContent(/45/) Picking the row by index doesn't seem to be testing as a user would. And I'm not really testing the Age column, so if I added a phone number and he happened to have 45 in that, my test no longer verifies the age. Really what I want to do here is "Get row with Joe in the first column, Get cell in "Age" column" e.g. Click the delete button against Jane fireEvent.click(within(getAllByRole('row')[2]).getByText('Delete')) Again, I'm having to pick the row by index, which makes the test more brittle and harder to refactor if something changes. Another alternative would be adding data-testid attributes all over that table, but again, not exactly how a user would see it. Really what I want to do here is "Get row with Jane in the first column, click item inside with Delete text" |
const row = getByRole('cell', {name: 'Jane'}).closest('tr')
fireEvent.click(
within(row).getByText('Delete')
) Of course, this doesn't work if you aren't using literal table elements, virtualized tables, etc. |
How about const row = getByText('Jane', { closest: 'row' })
fireEvent.click(
within(row).getByText('Delete')
) or const row = within(getByRole('cell', {name: 'Jane'})).getByClosestRole('row')
fireEvent.click(
within(row).getByText('Delete')
) The general idea is to use a role to get the closest element instead of a selector. |
@filipegiusti I guess these are further api proposals, rather than actual (workaround) implementation? |
Exactly, those are further api proposals. As this is from May I don't expect it to be ever approved. |
Instead of fireEvent.click(getByText(getBySomething(rowStuff), 'Delete')) |
Are there any examples or documentation of the current best practices for working with tables? |
I'm also interested in this. I feel the same that testing table's right now does not feel like testing how a user would. If I were to visually process the rows of data in a table, I would typically reference the column header text as I scan across the row data. Alternatively, I might look for a particular column, and then scan down the table looking for values of interest. As a user, you're also typically using context to determine what constitutes uniqueness of data in a table. Perhaps it's a single column value, but maybe it's a composite key of two or more columns like first and last name for instance. So in that case, say I'm looking for the favorite color of someone named "Steve Rogers." I might scan for first name's of "Steve" and as I find a row that matches, then I'd check whether the value in the last name column matches "Rogers" continuing my scan until the desired row is found or no more rows exist. If found, I could look to the column for favorite color to get the information I'm looking for. Expressed in RTL, I think the way I'd have to achieve that now would be something like this (untested and just typed here by hand so not sure this is 100% sound).
But that just doesn't seem very elegant or to be in the spirit of RTL. I don't have a proposal for how to do it in a more "user testing" manner, but maybe I'm just being rigid in my thinking and someone can offer a better way that the above could be achieved in a more RTL-ish way. |
This got my head in the right space to solve a similar issue. |
A couple of solutions here suggest using 'closest' but the testing-library linter dislikes this: |
After thinking about this alot, I found a way that seems to work without using closest and would most "resemble the way your software is used". getByRole('row', { name: /Joe/ }) You can then use All By using regex for the |
I've been digging into this for a project I've been working on with the intention of building queries based on table headers, which is something I've found challenging in the past. I tried to be pretty comprehensive to account for semantic and I'm sure that I've made some incorrect assumptions but figured I'd share what I came up with and propose a path forward if this is of interest. Code sampleHere's the gist of my solution 🔗 So far I've tested this for the following table:
APIMy proposal is to add a query, const gentRow = within(table).getByRole('row', /Gent/);
const gentTrousers = within(gentRow).getByTableHeaderText('Trousers');
expect(gentTrousers).toHaveTableHeaderText('Gent'); A few more examples: // Note: the second argument with scope is optional
const belgiumCells = screen.getAllByTableHeaderText('Belgium', { scope: 'rowgroup' });
const gentCells = screen.getAllByTableHeaderText('Gent', { scope: 'row' });
const gentHeader = within(table).getByRole('rowheader', { name: 'Gent' });
expect(gentHeader).toHaveTableHeaderText('Belgium', { scope: 'rowgroup' });
const clothesCells = screen.getAllByTableHeaderText('Clothes', { scope: 'colgroup' });
const trousersCells = screen.getAllByTableHeaderText('Trousers', { scope: 'col' });
const trousersHeader = within(table).getByRole('columnheader', { name: 'Trousers' });
expect(trousersHeader).toHaveTableHeaderText('Clothes', { scope: 'colgroup' });
// Filter to get the intersection
const gentTrousersCells = _.intersection(gentCells, trousersCells);
// Alternatively without lodash this intersection would be:
// gentCells.filter(cell => trousersCells.includes(cell)); If this is of interest I'd be happy to put up a PR! |
This is how I am doing it right now:
This means it will return |
I think we have a winner idea already, the most liked comment above. |
Was this ever integrated into testing-library? |
It is often that you would like to assert the content of a tabular information
|
Off the topic sorry, but why the role is cell and not gridcell as shown int he inspector ? @mathieu-suen-sonarsource |
cell is the more generic role of a gridcell according to w3c |
This let me think that we can also encourage the following techniques using this API: |
why not using snapshot testing on the entire table in order to assert all table headers/rows/cells to be exact values? 😊
|
@christopherstock That's because snapshot testing doesn't apply universally. The moment you start applying it to non-trivial components, snapshot testing becomes a maintenance nightmare. In the context of unit testing this is generally a moot point; snapshot testing may have its place in the universe, but your argument effectively questions the entire idea of having query functions in the first place. That's why I think the need for querying the tables effectively still holds up. |
Thanks for the detailed explanation! @bl-nero 👍🏻 🌺 |
Very sad to see that the idea from @enagy27 didn't get any reply from a maintainer after more than three years. Is this something that is considered to be added or can this be closed? |
Describe the feature you'd like:
The current queries don't really allow anything matching how a user would interact with a table.
There is limited support using the
ByRole
selectors, to choose all rows, or all cells, or nesting between these, but doesn't always match the principle of "use your component as a user would".e.g. In a table where each row has a "Delete" icon, choosing the delete icon corresponding to the particular row contents. A user would identify which row they're looking at by the value in one of the cells, then scanning across.
Suggested implementation:
I've implemented some custom queries in https://github.com/lexanth/testing-library-table-queries#readme, but some of it is a bit hacky.
Describe alternatives you've considered:
getAllByRole('row')
gets rows, but then filtering by text content is slightly awkward (either index based or filtering based on content).Teachability, Documentation, Adoption, Migration Strategy:
This would be additional queries, so backwards-compatible.
The text was updated successfully, but these errors were encountered: