对于那些根据条件渲染的 React 组件:

1
2
3
4
5
6
7
export default function PublishedTag({ isPublished }: { isPublished?: boolean }) {
if (!isPublished) {
return null;
}

return <span>Published</span>;
}

针对组件有时候不会返回任何渲染结果的情况,我是这么写测试用例的:

1
2
3
4
5
6
7
describe("PublishedTag", () => {
it("should render nothing when the isPublished prop is falsy", () => {
const { container } = render(<PublishedTag />)

expect(container).toBeEmptyDOMElement();
});
});

然后在项目里发现上面的 toBeEmptyDOMElement 断言失败了:

1
2
3
4
5
should render nothing when the isPublished prop is falsy
Error: expect(element).toBeEmptyDOMElement()

Received:
"<style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style>"Error: expect(element).toBeEmptyDOMElement()

看报错信息并调查发现项目使用了 Mantine 组件库,这个组件库必须要在根组件位置渲染 MantineProvider 才能正常工作:

1
2
3
4
5
6
7
import { createTheme, MantineProvider } from '@mantine/core';

const theme = createTheme({ ... });

export default function App({ children }: { children: React.ReactNode }) {
return <MantineProvider theme={theme}>{children}</MantineProvider>
}

同样,为了测试组件,测试代码也需要在 render 的时候加上一个 wrapper 来渲染这个 MantineProvider, 不然会抛错,于是就定义了一个自定义的 render 方法:

1
2
3
4
5
6
7
8
9
10
import { MantineProvider } from "@mantine/core";
import { RenderOptions, RenderResult, render as reactTestingLibRender } from "@testing-library/react";
import { ReactElement } from "react";

export function render(ui: ReactElement, options?: Omit<RenderOptions, "wrapper">): RenderResult {
return reactTestingLibRender(ui, {
wrapper: ({ children }: { children: React.ReactNode }) => <MantineProvider theme={{}}>{children}</MantineProvider>,
...options,
});
}

最开始断言失败的问题的原因就是因为渲染了 MantineProvider 导致的,MantineProvider 的渲染结果不是空的,它会返回一个 style 标签:

1
<style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style>

所以即使前述 PublishedTag 组件有时候没有任何渲染结果,由于 MantineProvider 返回的 style 标签的存在,就会导致 toBeEmptyDOMElement 断言的失败。知道原因之后去翻了翻 MantineProvider 的源码,发现它里面的 MantineCssVariables 和 MantineClasses 组件会有可能返回 style 标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export function MantineProvider({
...
withGlobalClasses = true,
withCssVariables = true,
...
}: MantineProviderProps) {

...

return (
<MantineContext.Provider
value={{ ... }}
>
<MantineThemeProvider theme={theme}>
{withCssVariables && (
<MantineCssVariables
cssVariablesSelector={cssVariablesSelector}
deduplicateCssVariables={deduplicateCssVariables}
/>
)}
{withGlobalClasses && <MantineClasses />}
{children}
</MantineThemeProvider>
</MantineContext.Provider>
);
}

MantineCssVariables 和 MantineClasses 这两个组件可以通过 withGlobalClasses、withCssVariables 这两个 props 控制到底渲不渲染,于是解决办法也找到了,在测试代码里渲染 MantineProvider 时将这两个值设为 false 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { MantineProvider } from "@mantine/core";
import { RenderOptions, RenderResult, render as reactTestingLibRender } from "@testing-library/react";
import { ReactElement } from "react";

export function render(ui: ReactElement, options?: Omit<RenderOptions, "wrapper">): RenderResult {
return reactTestingLibRender(ui, {
wrapper: ({ children }: { children: React.ReactNode }) => (
<MantineProvider withCssVariables={false} withGlobalClasses={false} theme={{}}>
{children}
</MantineProvider>
),
...options,
});
}

这样又可以愉快地使用 toBeEmptyDOMElement 断言啦。