How to test Material UI v5 components with sx props in @testing-library/react?
Asked Answered
B

2

12

React Testing Library does not apply sx props of Material UI components during rendering.

For example, I specify properties to hide an element at certain breakpoints.

<>
  <AppBar
    data-testid="mobile"
    ...
    sx={{
      display: { xs: "block", sm: "none" }
    }}
  >
    MOBILE
  </AppBar>
  <AppBar
    data-testid="desktop"
    ...
    sx={{
      display: { xs: "none", sm: "block" }
    }}
  >
    DESKTOP
  </AppBar>
</>

In the browser everything works as expected. When rendering in React Testing Library, I get a result with two elements. And it is clear from the styles that they are basic and the sx property was not applied. Link to codesandbox

import { ThemeProvider } from "@mui/material/styles";
import { screen, render } from "@testing-library/react";
import darkTheme from "./darkTheme";
import App from "./App";

describe("Demo", () => {
  it("should have display props", () => {
    render(
      <ThemeProvider theme={darkTheme}>
        <App />
      </ThemeProvider>
    );

    expect(screen.getByTestId("mobile")).toHaveStyle({ display: "none" });
    expect(screen.getByTestId("desktop")).toHaveStyle({ display: "block" });
  });
});

Test results

What is the correct way to test such Material UI props in React Testing Library?

Ballonet answered 22/2, 2022 at 8:3 Comment(0)
N
1

After running into the same problem I decided to write a temporary helper util to do assertions on the computed styles.

I found out that the computed sx styles are being applied to the head. If you run screen.debug() you wont be able to see it though... Running document.head.innerHTML however does the trick.

Please note that this util is far from perfect. It's just a quick way to continue with testing.

export const getElementStyleFromHead = (element: HTMLElement) => {
  const sourceElementClassNames = [...element.classList.values()]; // get all the classNames for source element
  const styleTags = new DOMParser().parseFromString(document.head.innerHTML, 'text/html').querySelectorAll('style'); // parse all style tags in head
  const styleTagsArray = [...styleTags.values()]; // convert NodeList to array
  const elementStyles = styleTagsArray.filter((style) => sourceElementClassNames.some((className) => style.innerHTML?.includes(className))); // filter all matching classNames between sourceElementClassNames and the style tag
  return elementStyles.map(item => item.innerHTML).join(''); // join all classes and parse it as one css string
};

Page:

<Box sx={{ 
  display: 'flex', 
  justifyContent: centerContent ? 'center' : 'flex-start'
 }}>
  {'dummy content'}
</Box>

Use it:

    expect(getElementStyleFromHead(customScreen.getByText('dummy content'))).toContain('justify-content:center')

EDIT: I found out that dynamic styles are not always cleaned in time. My use case was that when I scroll in my content pane a scrolling indicator (shadow) should be added to the top and bottom of the pane using :after and :before. My tests were failing running in sequence but were passing when ran in isolation. I've added this line to the beforeEach to make sure the head was always cleared. Not sure if this has any other side effects though...

beforeEach(() => {
    document.head.innerHTML = '';
});
Ningsia answered 2/6, 2022 at 12:46 Comment(0)
Y
-2

If you want to test your screen size you may have to move the logic to detect your screen size.

If you use windows.innerWidth and then display your your element base on this.


const mobileScreenSize = someNumberYouPick
const screenSize = window.innerWidth;

const mobile = () => { return screenSize - mobileScreenSize =< 0 ? true : false}

{ mobile() && {
<AppBar
    data-testid="mobile"
    ...
  >
    MOBILE
  </AppBar>
}}

{ !mobile() && {
<AppBar
    data-testid="desktop"
    ...
  >
    DESKTOP
  </AppBar>
}}

Then in the test, you can set your windows.innerwidth and set the value you want.

describe("Demo", () => {
  it("should have display props", () => {

Object.defineProperty(window, 'innerWidth', {
        writable: true,
        configurable: true,
        value: 150,
      });

    render(
      <ThemeProvider theme={darkTheme}>
        <App />
      </ThemeProvider>
    );
expect(screen.getByTestId("mobile")).not.toBeVisible() 
expect(screen.getByTestId("desktop")).toBeVisible()

  });
});

That should do the trick. I wonder if you need to change your code logic if you set your test with this:

Object.defineProperty(window, 'innerWidth', {
        writable: true,
        configurable: true,
        value: 150,
      });
Yorgen answered 10/11, 2022 at 22:4 Comment(2)
And how should I test for the opposite, that it is visible only on mobile? The complicated thing is to tell jest or material UI (mui), that I want to test this on mobile or on desktop.Aeromedical
I updated my answer based on your comment. Sorry I missed the issueYorgen

© 2022 - 2024 — McMap. All rights reserved.