Jest.js 如何使用React测试库按包含html标签的文本字符串进行查询?

b1uwtaje  于 2022-12-25  发布在  Jest
关注(0)|答案(8)|浏览(209)

当前工作溶液

使用此html:

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

我可以使用React Testing LibrarygetByTestId方法来查找textContent

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

有更好的办法吗?

我想简单地使用这个html:

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

并像这样使用React Testing LibrarygetByText方法:

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

但这行不通。

所以,问题是...

有没有更简单的方法来使用React Testing Library查找标记被剥离的文本内容字符串?

rkkpypqq

rkkpypqq1#

更新2

在使用了很多次之后,我创建了一个助手。下面是一个使用这个助手的示例测试。

测试助手:

// 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

试验:

// 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)')
})

更新1

下面是一个创建新匹配器getByTextWithMarkup的例子。注意,这个函数在测试中扩展了getByText,因此必须在那里定义它。(当然,这个函数可以更新为接受getByText作为参数。)

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')

以下是Five Things You (Probably) Didn't Know About Testing Library的第4个可靠答案:

查询也接受函数

您可能已经看到了类似下面的错误:
找不到具有以下文本的元素:Hello world.这可能是因为文本被多个元素分解了.在这种情况下,你可以为你的文本匹配器提供一个函数,让你的匹配器更灵活。
通常,这是因为你的HTML看起来像这样:

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

解决方案包含在错误消息中:"[...]您可以为您的文本匹配器提供一个函数[...]"。
这是怎么回事?原来匹配器接受字符串、正则表达式或函数。
为渲染的每个节点调用该函数。它接收两个参数:节点的内容和节点本身。你所要做的就是返回true或false,这取决于节点是否是你想要的。
下面的例子可以说明这一点:

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;
  });
});

我们忽略content参数,因为在本例中,它要么是"Hello"、"world",要么是空字符串。
我们要检查的是当前节点是否有正确的textContenthasText是一个帮助函数,我声明它是为了保持简洁。
这还不是全部,我们的div不是唯一包含我们要查找的文本的节点。body有相同的文本。为了避免返回过多的节点,我们要确保所有子节点都没有与父节点相同的文本。这样我们就可以确保返回的节点是最小的-换句话说,它靠近DOM树的底部。
阅读Five Things You (Probably) Didn't Know About Testing Library的其余部分

5jdjgkvh

5jdjgkvh2#

如果在项目中使用testing-library/jest-dom,也可以使用toHaveTextContent

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

如果需要部分匹配,还可以使用regex搜索模式

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

下面是该软件包的链接

uajslkp6

uajslkp63#

对于子字符串匹配,可以传递{ exact: false }
https://testing-library.com/docs/dom-testing-library/api-queries#textmatch

const el = getByText('Name:', { exact: false })
expect(el.textContent).toEqual('Name: Bob (special guest)');
cnwbcb6i

cnwbcb6i4#

现有答案已过时。新的 *ByRole查询支持以下功能:

getByRole('button', {name: 'Bob (special guest)'})
qaxu7uf2

qaxu7uf25#

    • 更新**

下面的解决方案有效,但在某些情况下,它可能返回多个结果。这是正确的实现:

getByText((_, node) => {
  const hasText = node => node.textContent === "Name: Bob (special guest)";
  const nodeHasText = hasText(node);
  const childrenDontHaveText = Array.from(node.children).every(
    child => !hasText(child)
  );

  return nodeHasText && childrenDontHaveText;
});

可以将方法传递给getbyText

getByText((_, node) => node.textContent === 'Name: Bob (special guest)')

你可以把代码放到一个helper函数中,这样你就不必一直输入代码了:

const { getByText } = render(<App />)
  const getByTextWithMarkup = (text) =>
    getByText((_, node) => node.textContent === text)
vxbzzdmp

vxbzzdmp6#

为了避免匹配多个元素,对于 * 某些 * 用例,只需返回本身实际具有文本内容的元素,即可过滤掉不需要的父元素:

expect(
  // - content: text content of current element, without text of its children
  // - element.textContent: content of current element plus its children
  screen.getByText((content, element) => {
    return content !== '' && element.textContent === 'Name: Bob (special guest)';
  })
).toBeInTheDocument();

上面的代码需要测试元素的 * 一些 * 内容,因此适用于:

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

...但如果<p>没有自己的文本内容,则不会:

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

因此,对于一般的解决方案,其他答案肯定更好。

vltsax25

vltsax257#

其他的答案都是类型错误或者根本没有功能的代码,这对我很有效。
注意:这里我使用的是screen.*

import React from 'react';
import { screen } from '@testing-library/react';

/**
 * Preparation: generic function for markup 
 * matching which allows a customized 
 * /query/ function.
 **/
namespace Helper {
    type Query = (f: MatcherFunction) => HTMLElement

    export const byTextWithMarkup = (query: Query, textWithMarkup: string) => {
        return query((_: string, node: Element | null) => {
            const hasText = (node: Element | null) => !!(node?.textContent === textWithMarkup);
            const childrenDontHaveText = node ? Array.from(node.children).every(
                child => !hasText(child as Element)
            ) : false;
        return hasText(node) && childrenDontHaveText
    })}
}

/**
 * Functions you use in your test code.
 **/
export class Jest {
    static getByTextWithMarkup = (textWithMarkup: string) =>  Helper.byTextWithMarkup(screen.getByText, textWithMarkup);
    static queryByTextWith = (textWithMarkup: string) =>  Helper.byTextWithMarkup(screen.queryByText, textWithMarkup);
}

用法:

Jest.getByTextWithMarkup("hello world");
Jest.queryByTextWithMarkup("hello world");
bis0qfac

bis0qfac8#

getByText('Hello World'); // full string match
getByText('llo Worl', { exact: false }); // substring match
getByText('hello world', { exact: false }); // ignore case-sensitivity

资料来源https://testing-library.com/docs/react-testing-library/cheatsheet/#queries

相关问题