Parent node in react-testing-library
Asked Answered
G

3

54

The component that I have testing renders something this:

<div>Text<span>span text</span></div>

As it turns out for testing the only reliable text that I have is the 'span text' but I want to get the 'Text' part of the <div>. Using Jest and react-testing-library I can

await screen.findByText(spanText)

This returns an HTMLElement but it seems limited as I don't have any of the context around the element. For example HTML methods like parentNode and previousSibling return null or undefined. Ideally I would like to get the text content of the parent <div>. Any idea how I can do this with either Jest or react-testing-library?

Guardado answered 7/7, 2020 at 8:20 Comment(0)
M
100

A good solution for this is the closest function. In description of closest function is written: Returns the first (starting at element) including ancestor that matches selectors, and null otherwise.

The solution would look like this:

screen.getByText("span text").closest("div")
Martins answered 22/7, 2020 at 2:43 Comment(4)
Just as sidenote: getByText() returns a DOM Element. So closest() is a standard js function (and not part of the testing library): developer.mozilla.org/en-US/docs/Web/API/Element/closestHensel
It is not best practice to use direct Node access. There is an eslint rule from TL that says. "Avoid direct Node access. Prefer using the methods from Testing Library". There are other ways to do this.Stickney
@JakobJanKamminga What are the other ways?Chadchadabe
I have posted an answer to this question, you can find it below.Stickney
S
19

Admittedly, Testing Library doesn't communicate clearly how to do this. It includes an eslint rule no-direct-node-access that says "Avoid direct Node access. Prefer using the methods from Testing Library". This gives the impression that TL exposes a method for a situation like this, but at the moment it does not.

It could be you don't want to use .closest(), either because your project enforces that eslint rule, or because it is not always a reliable selector. I've found two alternative ways to tackle a situation like you describe.

  1. within(): If your element is inside another element that is selectable by a Testing Library method (like a footer or an element with unique text), you can use within() like:
within(screen.getByRole('footer')).getByText('Text');
  1. find() within the element with a custom function:
screen.getAllByText('Text').find(div => div.innerHTML.includes('span text'));

Doesn't look the prettiest, but you can pass any JS function you want so it's very flexible and controllable.

Ps. if you use my second option depending on your TypeScript config you may need to make an undefined check before asserting on the element with Testing Library's expect(...).toBeDefined().

Stickney answered 21/12, 2021 at 10:56 Comment(3)
I'm a bit confused. You say not to use native DOM nodes, but then access div.innerHTML on a native DOM node. Is innerHTML OK but closest isn't simply because innerHTML doesn't traverse the DOM tree? (Arguably, it still does, only a stringified version of it). As I understand it, RTL wouldn't want you to touch innerHTML either, right?Genniegennifer
Unfortunately I can't find a more thorough explanation of why RTL doesn't want you to use closest(). But what I suspect it has to do with, is that closest() can go up the DOM tree (in fact, you don't know for sure which way it will go!), whereas calling (children's) properties of an element you selected will only query within that element. So it's more narrow, and as a result harder to break or cause performance leaks. But! This is an interesting question, if I find some time I'll throw it to Kent in the KCD Discord, I'm sure he has thoughts on it.Stickney
Interestingly, Kent recommended me to not enable the eslint rule in question (yes, the one included in TL itself). OP's case would be a good case where accessing DOM nodes directly is quite necessary, because TL doesn't directly offer you the tools to do it. I will update my answer accordingly.Stickney
C
9

But I have used HTML methods a lot and there was no problem yet. What was your problem with HTML methods?

You can try this code.

const spanElement = screen.getElementByText('span text');
const parentDiv = spanElement.parentElement as HTMLElement;
within(parentDiv).getElementByText('...');
Caldron answered 31/8, 2022 at 9:44 Comment(1)
There is an eslint rule to prevent you from direct accessing nodes. "Avoid direct Node access. Prefer using the methods from Testing Library".Compte

© 2022 - 2024 — McMap. All rights reserved.