testing-library / jest-dom

:owl: Custom jest matchers to test the state of the DOM
https://testing-library.com/docs/ecosystem-jest-dom
MIT License
4.4k stars 392 forks source link

toHaveStyle Color not working as expected #350

Open espipj opened 3 years ago

espipj commented 3 years ago

Relevant code or config:

Edit Material demo (forked)

Problem Statement:

I was trying to test the styling of some components (applied via Material UI styling solution) and it seems that toHaveStyledoesn't work properly. The component style is computed/rendered properly in the web dom, the TabeHead cells styles show up as:

color: #fff;
background-color: #000;

But when using Testing library...

  expect(screen.getByText("Calories")).toHaveStyle(`color: #FFF`);

It throws the following error: Expected - color: rgb(255, 255, 255); + color: rgba(0, 0, 0, 0.87); rgba(0, 0, 0, 0.87) is doesn't match with hex #FFF

At the same time this weirdly works with background-color. Please check the code sandbox for more context, if you need further explanation let me know and Im happy to help 😃

gnapse commented 3 years ago

I sometimes think we should not have added the .toHaveStyle custom matcher to this library. It is by far the one that gives more headaches.

In this case I cannot say for sure, but I bet that in the context of running the tests in jest, the stylesheet is not attached to the document that's being used in this context to run the test.

See for instance the code for when we run the very tests of this matcher, to see how we have to explicitly create a style element with the css we want to be in place, and we have to explicitly attach it to the document before these styles are enforced. This bit of the puzzle does not work in the context of the test in the same way as it works when you run a full-fledged page.

So to be clear, what I suspect is that the test is running in a document that looks like this more or less:

<html>
  <head>
    <!-- no style elements or link rel elements here -->
  </head>
  <body>
    <!-- no style elements or link rel elements here -->
    <div>
      <!-- this is react's root element and where the <Demo /> component is mounted -->
    </div>
  <body>
</html>

If what I suspect is correct, there's little we can do. It depends on how css is built into your pages during the build steps of your bundler, or something along those lines.

Suggestions are welcome.

espipj commented 3 years ago

Hey thanks @gnapse Yes I guess is somehow related with how MUI theming/styling is applied... I'll try to do some research in the next days about it :)

ganeshr43 commented 3 years ago

I'm Having exact same problem with .toHaveStyle. I'm using material components as well. @espipj let me know if you've found some solution/workaround for this problem.

espipj commented 3 years ago

Hey @ganeshr43 I'm afraid I didn't get anywhere sadly 😔

jaymathew commented 3 years ago

I ran into this same issue today. Did anyone figure this out yet or maybe a workaround?

bzalasky commented 3 years ago

We encountered a similar issue, however, the actual styles were there in the expectation and received values. The issue is that the received value was wrong. We have a component that generates HSLA colors using the d3-scale-chromatic and color libraries. These colors were being incorrectly transformed to the RGBA model (mapping to a gray while preserving the alpha channel). We were able to resolve our issue by switching to @emotion/jest's toHaveStyleRule for making color assertions. For other kinds of style assertions (which we tend to avoid except for special cases like this), things should have been working fine.

pupgray commented 3 years ago

Possible janky solution for this, if you check document.head.innerHTML does actually contain the style definitions MUI should be injecting, then try just replacing it on the document after rendering:

it('should hide menu button when menu bar is open', async () => {
  const { findByLabelText, debug } = render((
    <SomeMenuBar />
  ));
  document.head.innerHTML = document.head.innerHTML; // ⚠️ Add this line. 
  const button = await findByLabelText('menu');
  fireEvent.click(button);
  expect(button).not.toBeVisible(); // And now my style is applied.
});

Why this works... no clue 🤷

bfellows37 commented 3 years ago

I have run into this as well, with MaterialUI. In my case, reassigning document.head.innerHTML doesn't work.

const StyledTable = styled(Table)({
  '& .MuiTableCell': {
    '&-head': {
      color: '#5C5C5C',
    },
  },
})
    const table = <StyledTable>
      <TableHead>
        <TableRow>
          <TableCell>Head cell</TableCell>
        </TableRow>
      </TableHead>
      <TableBody>
        <TableRow>
          <TableCell>Body cell</TableCell>
        </TableRow>
      </TableBody>
    </StyledTable>

    act(() => {
      render(table, container)
    })
    const tableHead = container.querySelector('.MuiTableCell-head')

    document.head.innerHTML = document.head.innerHTML // doesn't seem to matter whether I include this here
    expect(tableHead).toHaveStyle({
      color: '#353535', // receives the base MuiTable th color instead
    })

If I change the way I send styles into the Mui component to using a Theme Provider and some overrides around the MuiTable rules, the test will pass. But that approach introduces friction when dealing with an outer theme provider, so the styled component/withStyles approach is preferable.

Hoping these additional details help.

matheusliraofficial commented 2 years ago

after struggling a lot with the toHaveStyle method, I resolved my tests converting the HEX to RGB with this function below:

export const convertHexToRGBA = (hexCode: string) => {
  let hex = hexCode.replace('#', '');

  if (hex.length === 3) {
    hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
  }

  const r = parseInt(hex.substring(0, 2), 16);
  const g = parseInt(hex.substring(2, 4), 16);
  const b = parseInt(hex.substring(4, 6), 16);

  return { r, g, b };
};

your tests will look like this:

expect(screen.getByText("Calories")).toHaveStyle({ color: convertHexToRGBA('#FFF') });

cassiourugit commented 2 years ago

What fixed this to me was changing the jest-environment-jsdom-fourteen to "jest-environment-jsdom": "^26.6.2",

jadir-junior commented 2 years ago

@cassiourugit I tried that and a get a error

ReferenceError: global is not defined

      at Object.<anonymous> (node_modules/graceful-fs/graceful-fs.js:92:1)
      at Object.<anonymous> (node_modules/expect/build/toThrowMatchers.js:10:24)
      at Object.<anonymous> (node_modules/expect/build/index.js:35:48)
matheusliraofficial commented 2 years ago

@jadir-junior

If you're using webpack, I believe you can enable node.global polyfill in your webpack config:

module.exports = {
  //...
  node: {
    global: true
  }
};
strtw commented 10 months ago

I'm having issues getting the correct value for background-color for a React MUI SnackbarContent component. I'm using testing-library. I've confirmed that my element is the correct element and that the background color of the element is rgb(230, 243, 250) in the inspector.

Here is my test code:

test('Renders correct color', async () => {
        render(<Info {...story.args} />);
        const content = screen.getByRole('alert');
        expect(content).toHaveStyle(`background-color: #E6F3FA`);
});

Here is the error message:

SnackbarContent > Renders info
-----
Error: expect(element).toHaveStyle()

- Expected

- background-color: rgb(230, 243, 250);
+ background-color: rgb(50, 50, 50);

I've also attempted using the color converter mentioned above but this results in the test passing no matter what color I pass to the function.

expect(content).toHaveStyle({
            backgroundColor: convertHexToRGBA('#E6F3FA'),
});
matheusTA commented 6 months ago

I had the same problem, and for me it works to put it inside an object

expect(screen.getByText("Calories")).toHaveStyle({ color: '#FFF' });

dimaka77 commented 6 months ago

I'm having issues getting the correct value for background-color for a React MUI SnackbarContent component. I'm using testing-library. I've confirmed that my element is the correct element and that the background color of the element is rgb(230, 243, 250) in the inspector.

Here is my test code:


test('Renders correct color', async () => {

      render(<Info {...story.args} />);

      const content = screen.getByRole('alert');

      expect(content).toHaveStyle(`background-color: #E6F3FA`);

});

Here is the error message:


SnackbarContent > Renders info

-----

Error: expect(element).toHaveStyle()

- Expected

- background-color: rgb(230, 243, 250);

+ background-color: rgb(50, 50, 50);

I've also attempted using the color converter mentioned above but this results in the test passing no matter what color I pass to the function.


expect(content).toHaveStyle({

          backgroundColor: convertHexToRGBA('#E6F3FA'),

});

@strtw were you able to resolve this?

krishnachaitanya137 commented 6 months ago

I had the same problem, and for me it works to put it inside an object

expect(screen.getByText("Calories")).toHaveStyle({ color: '#FFF' });

This worked for me too. We didn't upgrade any package. We just upgraded node version

BreakBB commented 1 month ago

For me the following does NOT work:

    it("should work", () => {
        render(<button style={{ color: "white" }}>123</button>);

        expect(screen.getByText("123")).toHaveStyle({ color: "white" });
    });

error:

- Expected

- color: white;
+ color: rgb(255, 255, 255);

But this works:

    it("should work", () => {
        render(<button style={{ color: "white" }}>123</button>);

        expect(screen.getByText("123")).toHaveStyle({ color: "#FFF" });
    });

This makes me wonder if it is helpful/feasible to add a color translation step for the .toHaveStyle matcher into testing library?

Or at least raise an error, that named color strings are not supported ("no # at the string start -> error) 🤷🏻