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?

3 Answers 3

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")
4
  • 28
    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/closest
    – pico_prob
    Commented May 18, 2021 at 16:37
  • 7
    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. Commented Dec 21, 2021 at 10:36
  • 14
    @JakobJanKamminga What are the other ways? Commented Oct 4, 2022 at 18:33
  • I have posted an answer to this question, you can find it below. Commented Oct 7, 2022 at 12:01
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().

3
  • 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?
    – ggorlen
    Commented Dec 24, 2021 at 0:34
  • 1
    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. Commented Dec 31, 2021 at 11:20
  • 4
    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. Commented Jan 4, 2022 at 14:33
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('...');
1
  • 1
    There is an eslint rule to prevent you from direct accessing nodes. "Avoid direct Node access. Prefer using the methods from Testing Library". Commented Nov 16, 2023 at 20:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.