styled-components / jest-styled-components

🔧 💅 Jest utilities for Styled Components
MIT License
1.58k stars 144 forks source link

toHaveStyleRule with @media rule breaks after styled-components update #430

Open jkettmann opened 11 months ago

jkettmann commented 11 months ago

We recently updated styled-components to v6 and something caused some of our tests to fail. I was able to reproduce the issue in this repository (you can also run it here on Replit). Here's the example test:

import 'jest-styled-components';
import { render, screen } from '@testing-library/react';
import { styled } from 'styled-components';

const StyledDiv = styled.div`
  @media (min-width: 400px) {
    color: red;
  }
`;

function MyComponent() {
  return <StyledDiv data-testid="styled-div" />;
}

describe('toHaveStyleRule media test', () => {
  test('has red color on larger devices', async () => {
    render(<MyComponent />);

    const styledDiv = screen.getByTestId('styled-div');
    expect(styledDiv).toHaveStyleRule('color', 'red', {
      media: '(min-width: 400px)',
    });
  });
});

This is the error I'm getting:

CleanShot 2023-07-31 at 17 34 29

The problem seems to be that this check in toHaveStyleRule rule[option] === options[option].replace(/:\s/g, ":") is not working anymore.

const getAtRules = (ast, options) => {
  return Object.keys(options)
    .map((option) =>
      ast.stylesheet.rules
        .filter((rule) => rule.type === option && rule[option] === options[option].replace(/:\s/g, ":"))
        .map((rule) => rule.rules)
        .reduce((acc, rules) => acc.concat(rules), [])
    )
    .reduce((acc, rules) => acc.concat(rules), []);
};

Before rule.option didn't have a whitespace. I'm not sure what changed but now it looks like this:

CleanShot 2023-07-31 at 17 28 58

I'm happy to create a PR but as I said, I'm not sure what the original cause of the whitespace not being removed is.

Edit: To run the tests in the Replit simply run npm run test.

jkettmann commented 11 months ago

Removing the whitespace from the ast rule as well should be a simple and backwards compatible change:

const getAtRules = (ast, options) => {
  return Object.keys(options)
    .map((option) =>
      ast.stylesheet.rules
        .filter((rule) => rule.type === option && rule[option].replace(/:\s/g, ":") === options[option].replace(/:\s/g, ":"))
        .map((rule) => rule.rules)
        .reduce((acc, rules) => acc.concat(rules), [])
    )
    .reduce((acc, rules) => acc.concat(rules), []);
};
jkettmann commented 11 months ago

If someone else encounters the same problem before this issue is closed a simple workaround is to add a second whitespace as in this example:

describe('toHaveStyleRule media test', () => {
  test('has red color on larger devices', async () => {
    render(<MyComponent />);

    const styledDiv = screen.getByTestId('styled-div');
    expect(styledDiv).toHaveStyleRule('color', 'red', {
      media: '(min-width:  400px)',
    });
  });
});

Note the two whitespaces in min-width: 400px

hemerson-git commented 11 months ago

If someone else encounters the same problem before this issue is closed a simple workaround is to add a second whitespace as in this example:

describe('toHaveStyleRule media test', () => {
  test('has red color on larger devices', async () => {
    render(<MyComponent />);

    const styledDiv = screen.getByTestId('styled-div');
    expect(styledDiv).toHaveStyleRule('color', 'red', {
      media: '(min-width:  400px)',
    });
  });
});

Note the two whitespaces in min-width: 400px

You saved my life, I'm about 2 days trying to fix that issue. Thank you so much.

jkettmann commented 11 months ago

@hemerson-git I'm glad this was helpful 🙂

caribou-code commented 6 months ago

Is there a plan to fix this in the package? In my case, media query strings are stored in a theme with a single whitespace after the colon, so this is a pain to manipulate the string every time I run a test like this.

odanielsantana commented 1 month ago

Same problem here.

If someone else encounters the same problem before this issue is closed a simple workaround is to add a second whitespace as in this example:

describe('toHaveStyleRule media test', () => {
  test('has red color on larger devices', async () => {
    render(<MyComponent />);

    const styledDiv = screen.getByTestId('styled-div');
    expect(styledDiv).toHaveStyleRule('color', 'red', {
      media: '(min-width:  400px)',
    });
  });
});

Note the two whitespaces in min-width: 400px

This helped so much broh! Thank you!