Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to query by text string which contains html tags using React Testing Library?

Current Working Solution

Using this html:

<p data-testid="foo">Name: <strong>Bob</strong> <em>(special guest)</em></p> 

I can use the React Testing Library getByTestId method to find the textContent:

expect(getByTestId('foo').textContent).toEqual('Name: Bob (special guest)')  

Is there a better way?

I would like to simply use this html:

<p>Name: <strong>Bob</strong> <em>(special guest)</em></p> 

And use React Testing Library's getByText method like this:

expect(getByText('Name: Bob (special guest)')).toBeTruthy() 

But this does not work.

So, the question…

Is there a simpler way to use React Testing Library to find strings of text content with the tags striped out?

like image 356
Beau Smith Avatar asked Apr 04 '19 07:04

Beau Smith


People also ask

How do I get the element by type in React testing library?

To find elements by className in React testing library: Render a component and destructure the container object from the result. Use the getElementsByClassName() method on the container to find elements by class name.

What is getByTestId?

getByTestId only when there isn't anything you cant easily latch onto from your UI. getByTestId: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by role or text or it doesn't make sense (e.g. the text is dynamic).


2 Answers

Update 2

Having used this many times, I've created a helper. Below is an example test using this helper.

Test helper:

// withMarkup.ts import { MatcherFunction } from '@testing-library/react'  type Query = (f: MatcherFunction) => HTMLElement  const withMarkup = (query: Query) => (text: string): HTMLElement =>   query((content: string, node: HTMLElement) => {     const hasText = (node: HTMLElement) => node.textContent === text     const childrenDontHaveText = Array.from(node.children).every(       child => !hasText(child as HTMLElement)     )     return hasText(node) && childrenDontHaveText   })  export default withMarkup 

Test:

// app.test.tsx import { render } from '@testing-library/react' import App from './App' import withMarkup from '../test/helpers/withMarkup'  it('tests foo and bar', () => {   const { getByText } = render(<App />)   const getByTextWithMarkup = withMarkup(getByText)   getByTextWithMarkup('Name: Bob (special guest)') }) 

Update 1

Here is an example where a new matcher getByTextWithMarkup is created. Note that this function extends getByText in a test, thus it must be defined there. (Sure the function could be updated to accept getByText as a parameter.)

import { render } from "@testing-library/react"; import "jest-dom/extend-expect";  test("pass functions to matchers", () => {   const Hello = () => (     <div>       Hello <span>world</span>     </div>   );   const { getByText } = render(<Hello />);    const getByTextWithMarkup = (text: string) => {     getByText((content, node) => {       const hasText = (node: HTMLElement) => node.textContent === text       const childrenDontHaveText = Array.from(node.children).every(         child => !hasText(child as HTMLElement)       )       return hasText(node) && childrenDontHaveText     })   }    getByTextWithMarkup('Hello world') 

Here is a solid answer from the 4th of Five Things You (Probably) Didn't Know About Testing Library from Giorgio Polvara's Blog:


Queries accept functions too

You have probably seen an error like this one:

Unable to find an element with the text: Hello world. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Usually, it happens because your HTML looks like this:

<div>Hello <span>world</span></div> 

The solution is contained inside the error message: "[...] you can provide a function for your text matcher [...]".

What's that all about? It turns out matchers accept strings, regular expressions or functions.

The function gets called for each node you're rendering. It receives two arguments: the node's content and the node itself. All you have to do is to return true or false depending on if the node is the one you want.

An example will clarify it:

import { render } from "@testing-library/react"; import "jest-dom/extend-expect";  test("pass functions to matchers", () => {   const Hello = () => (     <div>       Hello <span>world</span>     </div>   );   const { getByText } = render(<Hello />);    // These won't match   // getByText("Hello world");   // getByText(/Hello world/);    getByText((content, node) => {     const hasText = node => node.textContent === "Hello world";     const nodeHasText = hasText(node);     const childrenDontHaveText = Array.from(node.children).every(       child => !hasText(child)     );      return nodeHasText && childrenDontHaveText;   }); }); 

We're ignoring the content argument because in this case, it will either be "Hello", "world" or an empty string.

What we are checking instead is that the current node has the right textContent. hasText is a little helper function to do that. I declared it to keep things clean.

That's not all though. Our div is not the only node with the text we're looking for. For example, body in this case has the same text. To avoid returning more nodes than needed we are making sure that none of the children has the same text as its parent. In this way we're making sure that the node we're returning is the smallest—in other words the one closes to the bottom of our DOM tree.


Read the rest of Five Things You (Probably) Didn't Know About Testing Library

like image 144
Beau Smith Avatar answered Oct 13 '22 01:10

Beau Smith


If you are using testing-library/jest-dom in your project. You can also use toHaveTextContent.

expect(getByTestId('foo')).toHaveTextContent('Name: Bob (special guest)') 

if you need a partial match, you can also use regex search patterns

expect(getByTestId('foo')).toHaveTextContent(/Name: Bob/) 

Here's a link to the package

like image 21
forrestDinos Avatar answered Oct 12 '22 23:10

forrestDinos