Mantine 库导致 Vitest toBeEmptyDOMElement 断言失败
对于那些根据条件渲染的 React 组件:
export default function PublishedTag({ isPublished }: { isPublished?: boolean }) {
if (!isPublished) {
return null;
}
return <span>Published</span>;
}针对组件有时候不会返回任何渲染结果的情况,我是这么写测试用例的:
describe("PublishedTag", () => {
it("should render nothing when the isPublished prop is falsy", () => {
const { container } = render(<PublishedTag />)
expect(container).toBeEmptyDOMElement();
});
});然后在项目里发现上面的 toBeEmptyDOMElement 断言失败了:
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 才能正常工作:
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 方法:
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 标签:
<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 标签:
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 即可:
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 断言啦。