Why does getComputedStyle() in a JEST test return different results to computed styles in Chrome / Firefox DevTools
Asked Answered
O

1

25

I've written a custom button (MyStyledButton) based on material-ui Button.

import React from "react";
import { Button } from "@material-ui/core";
import { makeStyles } from "@material-ui/styles";

const useStyles = makeStyles({
  root: {
    minWidth: 100
  }
});

function MyStyledButton(props) {
  const buttonStyle = useStyles(props);
  const { children, width, ...others } = props;

  return (

      <Button classes={{ root: buttonStyle.root }} {...others}>
        {children}
      </Button>
     );
}

export default MyStyledButton;

It is styled using a theme and this specifies the backgroundColor to be a shade of yellow (Specficially #fbb900)

import { createMuiTheme } from "@material-ui/core/styles";

export const myYellow = "#FBB900";

export const theme = createMuiTheme({
  overrides: {
    MuiButton: {
      containedPrimary: {
        color: "black",
        backgroundColor: myYellow
      }
    }
  }
});

The component is instantiated in my main index.js and wrapped in the theme.

  <MuiThemeProvider theme={theme}>
     <MyStyledButton variant="contained" color="primary">
       Primary Click Me
     </MyStyledButton>
  </MuiThemeProvider>

If I examine the button in Chrome DevTools the background-color is "computed" as expected. This is also the case in Firefox DevTools.

Screenshot from Chrome

However when I write a JEST test to check the background-color and I query the DOM node style òf the button using getComputedStyles() I get transparent back and the test fails.

const wrapper = mount(
    <MyStyledButton variant="contained" color="primary">
      Primary
    </MyStyledButton>
  );
  const foundButton = wrapper.find("button");
  expect(foundButton).toHaveLength(1);
  //I want to check the background colour of the button here
  //I've tried getComputedStyle() but it returns 'transparent' instead of #FBB900
  expect(
    window
      .getComputedStyle(foundButton.getDOMNode())
      .getPropertyValue("background-color")
  ).toEqual(myYellow);

I've included a CodeSandbox with the exact problem, the minimum code to reproduce and the failing JEST test.

Edit headless-snow-nyofd

Ovine answered 18/12, 2019 at 16:39 Comment(9)
.MuiButtonBase-root-33 background-color is transparent while .MuiButton-containedPrimary-13 is not - so problem is, classes in CSS are equaly important, so only load order distinguish them --> in test styles are loaded in wrong order.Harvestman
@Andreas - Updated as requestedOvine
@Zyndar - Yes I know that. Is there any way to get this test to pass ?Ovine
Wouldn't the theme need to be used in the test? As in, wrap the <MyStyledButton> in the <MuiThemeProvider theme={theme}>? Or use some wrapper function to add the theme to all components?Heat
No that doesn't make any difference.Ovine
if the point of @Harvestman is still valid, try to fix that - by giving it more importance(e.g. by doubling the class selector) or load it in the same order.Biquadrate
@Biquadrate - Can you provide an example please ?Ovine
If .myclass is overwritten by .otherclass you can create a rule like .myclass.myclass that then is more specific than .otherclass.Biquadrate
This may has to do with cascading issue of styles in jsdom: github.com/facebook/jest/issues/8464Waers
Y
3

I've gotten closer, but not quite at a solution yet.

The main issue is that MUIButton injects a tag to the element to power the styles. This isn't happening in your unit test. I was able to get this to work by using the createMount that the material tests use.

After this fix, the style is correctly showing up. However, the computed style still doesn't work. It looks like others have run into issues with enzyme handling this correctly - so I'm not sure if it's possible.

To get to where I was, take your test snippet, copy this to the top, and then change your test code to:

const myMount = createMount({ strict: true });
  const wrapper = myMount(
    <MuiThemeProvider theme={theme}>
      <MyStyledButton variant="contained" color="primary">
        Primary
      </MyStyledButton>
    </MuiThemeProvider>
  );
class Mode extends React.Component {
  static propTypes = {
    /**
     * this is essentially children. However we can't use children because then
     * using `wrapper.setProps({ children })` would work differently if this component
     * would be the root.
     */
    __element: PropTypes.element.isRequired,
    __strict: PropTypes.bool.isRequired,
  };

  render() {
    // Excess props will come from e.g. enzyme setProps
    const { __element, __strict, ...other } = this.props;
    const Component = __strict ? React.StrictMode : React.Fragment;

    return <Component>{React.cloneElement(__element, other)}</Component>;
  }
}

// Generate an enhanced mount function.
function createMount(options = {}) {

  const attachTo = document.createElement('div');
  attachTo.className = 'app';
  attachTo.setAttribute('id', 'app');
  document.body.insertBefore(attachTo, document.body.firstChild);

  const mountWithContext = function mountWithContext(node, localOptions = {}) {
    const strict = true;
    const disableUnnmount = false;
    const localEnzymeOptions = {};
    const globalEnzymeOptions = {};

    if (!disableUnnmount) {
      ReactDOM.unmountComponentAtNode(attachTo);
    }

    // some tests require that no other components are in the tree
    // e.g. when doing .instance(), .state() etc.
    return mount(strict == null ? node : <Mode __element={node} __strict={Boolean(strict)} />, {
      attachTo,
      ...globalEnzymeOptions,
      ...localEnzymeOptions,
    });
  };

  mountWithContext.attachTo = attachTo;
  mountWithContext.cleanUp = () => {
    ReactDOM.unmountComponentAtNode(attachTo);
    attachTo.parentElement.removeChild(attachTo);
  };

  return mountWithContext;
}
Yoghurt answered 23/12, 2019 at 3:29 Comment(1)
Do we have any progress on that, I am also facing the same issue with Material components.Neelon

© 2022 - 2024 — McMap. All rights reserved.