如何使用 React 测试库通过包含 html 标签的文本字符串进行查询?
Posted
技术标签:
【中文标题】如何使用 React 测试库通过包含 html 标签的文本字符串进行查询?【英文标题】:How to query by text string which contains html tags using React Testing Library? 【发布时间】:2019-08-25 20:26:10 【问题描述】:当前工作解决方案
使用这个 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 Library的getByText
方法:
expect(getByText('Name: Bob (special guest)')).toBeTruthy()
但这不起作用。
那么,这个问题……
有没有更简单的方法来使用 React 测试库来查找带标签的文本内容字符串?
【问题讨论】:
【参考方案1】:如果您在项目中使用testing-library/jest-dom
。你也可以使用toHaveTextContent
。
expect(getByTestId('foo')).toHaveTextContent('Name: Bob (special guest)')
如果需要部分匹配,也可以使用正则表达式搜索模式
expect(getByTestId('foo')).toHaveTextContent(/Name: Bob/)
这是package的链接
【讨论】:
【参考方案2】:为了避免匹配多个元素,对于 一些 用例只返回本身实际上具有文本内容的元素,过滤掉不需要的父元素就好了:
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>
因此,对于通用解决方案,其他答案肯定更好。
【讨论】:
【参考方案3】:现有答案已过时。新的*ByRole 查询支持这一点:
getByRole('button', name: 'Bob (special guest)')
【讨论】:
在这种没有“按钮”的情况下如何工作? @jarthur - 使用可访问性 DOM 检查您的目标元素以确定其角色。 我想知道在 OP 的上下文中,没有明显的作用。除非p
有默认角色?
@jarthur - 具有段落的作用。然而,奇怪的是 getByRole 忽略了段落。因此,您需要使用 getByRole 当前支持的不同包装元素,例如标题或区域。
@CoryHouse - 如果没有具有可访问角色的元素并且只有这样的元素怎么办: [AL] 阿尔巴尼亚 [DZ] 阿尔及利亚 如何通过文本查询第一个元素?【参考方案4】:对于子串匹配,可以使用exact
:
https://testing-library.com/docs/dom-testing-library/api-queries#textmatch
【讨论】:
这是迄今为止最简单的解决方案。在这种情况下,类似于:expect(getByText('Name:', exact: false ).textContent).toEqual('Name: Bob (special guest)');【参考方案5】:更新 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 个来自Giorgio Polvara's Blog 的可靠答案:
查询也接受函数
你可能见过这样的错误:
找不到带有以下文字的元素:Hello world。 这可能是因为文本被多个元素分解。 在这种情况下,您可以为您的文本提供一个功能 matcher 让你的 matcher 更加灵活。
通常,这是因为您的 HTML 如下所示:
<div>Hello <span>world</span></div>
解决方案包含在错误消息中:“[...] you can provide a function for your text matcher [...]”。
那是怎么回事?事实证明,匹配器接受字符串、正则表达式或函数。
函数会为您渲染的每个节点调用。它接收两个参数:节点的内容和节点本身。您所要做的就是根据节点是否是您想要的节点返回 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”或空字符串。
我们检查的是当前节点是否拥有正确的textContent。 hasText
是一个小助手功能。我宣布它是为了保持清洁。
这还不是全部。我们的div
不是我们要查找的文本的唯一节点。例如,body
在这种情况下具有相同的文本。为了避免返回比需要更多的节点,我们确保没有一个子节点与其父节点具有相同的文本。通过这种方式,我们确保我们返回的节点是最小的——换句话说,那个节点靠近我们的 DOM 树的底部。
阅读Five Things You (Probably) Didn't Know About Testing Library的其余部分
【讨论】:
我不明白为什么这是必要的,因为根据testing-library docs,getByText
已经在寻找 textContent,因此getByText("Hello World")
应该可以工作,对(虽然它似乎不是出于某种原因)?
那是因为getByText
正在使用getNodeText
助手,它正在寻找每个文本节点 的textContent
属性。在您的情况下,作为<p>
直接子级的唯一文本节点是Name:
和` `。我不确定为什么 RTL 决定不以递归方式查找作为子节点的子节点的文本节点。也许是出于性能原因,但事实就是如此。也许@kentcdodds 可以对此提供更多见解
考虑一下 RTL 不会寻找孩子的孩子,因为否则这个 getAllByText(<div><div>Hello</div></div>
, 'Hello') 会返回两个结果。有道理
不错的答案。我还必须捕获getByText
抛出的异常并用text
重新抛出另一条消息,因为使用自定义匹配器时它不包含在错误消息中。我认为在@testing-library
中默认包含这个助手会很棒。
@PaoloMoretti - ?? 您能否发布您描述的解决方案作为此问题的另一个答案?【参考方案6】:
更新
以下解决方案有效,但在某些情况下,它可能会返回多个结果。这是正确的实现:
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)')
您可以将代码放入辅助函数中,这样您就不必一直键入它:
const getByText = render(<App />)
const getByTextWithMarkup = (text) =>
getByText((_, node) => node.textContent === text)
【讨论】:
这个解决方案可以在简单的场景中工作,但是如果它产生错误“找到多个带有文本的元素:(_, node) => node.textContent === 'Name: Bob (special guest )'",然后尝试另一个答案的解决方案,它也检查子节点。 同意,解决方案其实取自我的博客:D 感谢您对 Giorgio 的洞察。当我发现在新测试中需要这些解决方案时,我不断地回到这些答案。 :) 有没有办法修改这个想法以使用 cypress-testing-library?以上是关于如何使用 React 测试库通过包含 html 标签的文本字符串进行查询?的主要内容,如果未能解决你的问题,请参考以下文章