如何将Material-UI样式表注入到Jest / React-Testing-Library测试中?

时间:2019-05-20 08:10:09

标签: jestjs material-ui react-testing-library

似乎,如果不将Material-UI样式表注入到jest / react-testing-library测试中,则jsdom将无法从组件中获取正确的样式(例如,运行getComputedStyle(component)将返回错误的组件的样式)。

如何正确设置一个玩笑/反应测试库测试,以便将样式正确地插入到测试中?我已经将这些组件包装在一个主题提供程序中,可以很好地工作。

4 个答案:

答案 0 :(得分:3)

作为一种变通方法,在声明前重新插入整个头部(或注入JSS样式的元素)似乎可以同时getComputedStyle()应用样式,并对测试库的toHaveStyle()做出反应:

import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { render } from "@testing-library/react";

test("test my styles", () => {
  const { getByTestId } = render(
    <div data-testid="wrapper">
      <MyButtonStyledWithJSS/>
    </div>
  );
  const button = getByTestId("wrapper").firstChild;
  document.head.innerHTML = document.head.innerHTML;
  expect(button).toHaveStyle(`border-radius: 4px;`);
});

使用dynamic styles时,此操作仍然会失败,例如:

myButton: {
  padding: props => props.spacing,
  ...
}

这是因为JSS使用CSSStyleSheet.insertRule方法来注入这些样式,并且不会在头部显示为style节点。解决此问题的一种方法是使用浏览器的insertRule方法,并将传入的规则作为样式标签添加到头部。要将所有这些提取到函数中:

function mockStyleInjection() {
  const defaultInsertRule = window.CSSStyleSheet.prototype.insertRule;
  window.CSSStyleSheet.prototype.insertRule = function (rule, index) {
    const styleElement = document.createElement("style");
    const textNode = document.createTextNode(rule);
    styleElement.appendChild(textNode);
    document.head.appendChild(styleElement);
    return defaultInsertRule.bind(this)(rule, index);
  };
  // cleanup function, which reinserts the head and cleans up method overwrite
  return function applyJSSRules() {
    window.CSSStyleSheet.prototype.insertRule = defaultInsertRule;
    document.head.innerHTML = document.head.innerHTML;
  };
}

此功能在我们先前的测试中的用法示例:

import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { render } from "@testing-library/react";

test("test my styles", () => {
  const applyJSSRules = mockStyleInjection();
  const { getByTestId } = render(
    <div data-testid="wrapper">
      <MyButtonStyledWithJSS spacing="8px"/>
    </div>
  );
  const button = getByTestId("wrapper").firstChild;
  applyJSSRules();
  expect(button).toHaveStyle("border-radius: 4px;");
  expect(button).toHaveStyle("padding: 8px;");
});

答案 1 :(得分:0)

我不能专门讲Material-UI样式表,但是您可以将样式表注入渲染的组件中:

import {render} from '@testing-library/react';
import fs from 'fs';
import path from 'path';

const stylesheetFile = fs.reactFileSync(path.resolve(__dirname, '../path-to-stylesheet'), 'utf-8');

const styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.innerHTML = stylesheetFile;

const rendered = render(<MyComponent>);
rendered.append(style);

您不必一定要读取文件,可以使用所需的任何文本。

答案 2 :(得分:0)

这最终看起来像是JSS和各种浏览器实现(例如jsdom和Blink,至少在Chrome中是这样)的问题。尝试修改/启用/禁用这些样式规则时,您可以在Chrome中看到它(不能)。

该行为似乎是由于使用CSSOM insertRule API的JSS库导致的。 DOM中生成了一个样式表,用于我们期望在组件中使用的样式,但是标记为空-它仅用于将阴影CSS链接回DOM。样式永远不会写入DOM中的内联样式表,因此,getComputedStyle方法不会返回预期的结果。

an open issue可以解决此问题并简化开发过程。

我将自定义组件切换为styled-components,该组件没有其中的某些特质。 Material-UI is planning on transitioning soon as well

答案 3 :(得分:0)

您可以将其添加到custom render function中。渲染后,该函数从cssom中提取样式并将其放入样式标签中。这是一个实现:

let customRender = (ui, options) => {
    let renderResult = render(ui, options);
    let styleElement = document.createElement("style");
    let styleText = "";
    for (let styleSheet of document.styleSheets) {
        for (let rule of styleSheet.cssRules) {
            styleText += rule.cssText + "\n";
        }
    }
    styleElement.textContent = styleText.slice(0, -1);
    document.head.appendChild(styleElement);
    // remove old style elements
    let emptyStyleElements = document.head.querySelectorAll('style[data-jss=""]');
    for (let element of emptyStyleElements) {
        element.remove();
    }
    return renderResult;
}