styled-components / jest-styled-components

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

v7.0.0 No Styles found on passed Component #294

Open LouiseReid opened 4 years ago

LouiseReid commented 4 years ago

I've just updated styled components to v5 and jest-styled-components to v7 and now any test that calls on toHaveStyleRule fails with the error No style rules found on passed Component.

Example test

  test("it renders correctly when disabled", () => {
    const { container } = render(<ControlButton disabled />);
    const button = container.querySelector("button");
    expect(button).toHaveStyleRule("opacity", "0.5");
  });

Tested component

const ControlButton = styled.button`
  background-color: white;
  border: none;
  font-size: 0;
  height: 20px;
  line-height: 0;
  margin: 0;
  padding: 0;
  user-select: none;
  width: 20px;

  ${props =>
    props.disabled &&
    css`
      opacity: 0.5;
      pointer-events: none;
    `}
`

This test passed using styled components v4.3.2 and jest-styled-components v6.3.3.

This is being tested with @testing-library/react v9.1.4

benbryant0 commented 4 years ago

I'm not very familiar with the inner workings of SC or this package, but I think I may have tracked the issue down to here: https://github.com/styled-components/jest-styled-components/commit/8c2ea4a0a8789e11707e7f18e76b811e0d70c4c0#diff-4eed74593d3d8efde6a0959c9c35119bR71

In my case the static classes have names of the format "Component-sc-hashstuff", but the filter expects them to start with 'sc-', so none are found. Then due to the way the some() calls are nested, hasClassNames returns false when I don't think it should.

visoft commented 4 years ago

My issue with this boiled down to 'babel-plugin-styled-components' being used. That plugin modifies the class names to include the component, removing that fixed the issue. See https://github.com/styled-components/jest-styled-components/issues/290

ryanirilli commented 4 years ago

We need the babel-plugin-styled-components plugin for server side rendering to have consistent hashing of classnames. Unless this is no longer needed in v5?

BatuhanW commented 4 years ago

I'm having the same problem with enzyme.

Undistraction commented 4 years ago

I'm seeing the same issue even with displayName set to false during tests:

[
  `babel-plugin-styled-components`,
  { ssr: false, displayName: false },
]

Test:

describe(`example`, () => {
  const Example = styled.div`
    color: red;
  `

  it(`has style rule`, () => {
    const tree = renderer.create(<Example />).toJSON()
    expect(tree).toHaveStyleRule(`color`, `red`)
  })
})

Tree:

type: 'div',
props: { className: 'sc-AykKC kWTpbh' },
children: null

Message:

No Styles found on passed Component

tcodes0 commented 4 years ago

I'm having this same issue styled 4.4.1, and 6.3.4 of this lib, not sure. For now I'm rolling with a local patch-package that seems to fix things. Hoping the update to 7 here and styled 5 fixes.

I'll following this repo and lmk if I can help in any way. :D

WayneEllery commented 4 years ago

We are having the same issue with styled-components 5.0.0 and jest-styled-components 7. To fix it we are currently using:

const plugins = [
  ['babel-plugin-styled-components', { ssr: !isTest, displayName: !isTest }],
];
stefee commented 4 years ago

This is a duplicate of #290

sombreroEnPuntas commented 4 years ago

Having a similar issue when using:

    "jest": "^25.1.0",
    "jest-styled-components": "^7.0.0",
    "@testing-library/jest-dom": "^4.0.0",
    "@testing-library/react": "^9.1.1",
    "babel-plugin-styled-components": "^1.10.7",
    "styled-components": "^5.0.1",

And configuring babelrc:

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "preprocess": false,
        "displayName": true
      }
    ],
  ]
}

Test is:

    it('should be visible', () => {
      const { getByTestId } = render(
        ...
      );

      const cta = getByTestId('cta-id');
      fireEvent.click(cta);

      expect(getByTestId('content')).toMatchSnapshot();
      expect(getByTestId('content')).toHaveStyleRule('visibility', 'visible');
    });

The snapshot clearly shows that styles are there:

.c0 {
  -webkit-transform: translateY(0px);
  -ms-transform: translateY(0px);
  transform: translateY(0px);
  -webkit-transition: all 200ms cubic-bezier(0.175,0.885,0.32,1.275);
  transition: all 200ms cubic-bezier(0.175,0.885,0.32,1.275);
}

.c1 {
  background-color: #FFFFFF;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 3px 7px 0 rgba(126,87,194,0.2);
  top: calc(100% + 0.5rem);
  position: absolute;
  padding: 0.5rem;
  min-width: 300px;
  right: 0;
  visibility: visible;
}

<div
  class="c0 c1"
  data-testid="content"
  y="0"
>
  Content Test
</div>
`;

But assertion says: No style rules found on passed Component

Undistraction commented 4 years ago

This is still broken for me, even with:

const plugins = [
  ['babel-plugin-styled-components', { ssr: false, displayName: false }],
];
fullstackzach commented 4 years ago

This is also preventing us from upgrading to v7.

fullstackzach commented 4 years ago

I saw that 7.0.2 was released, I'm still having trouble getting it to work with Styled components v5.1.0 and react-testing-library. I think it does have something to do with the class name used and the regex code in jest-styled-components to parse it. It looks like the regex is looking for "sc-" but my component is outputting a different pattern. TextField__StyledLabel-ramey7-1

Here's the test:

const rendered = render(<TextField />)
...
const label = rendered.getByTestId('field-label')
expect(label).toMatchSnapshot()
expect(label).toHaveStyleRule('width', '(100% / 0.75)')

here is a debug() output of the component:

  console.log node_modules/@testing-library/react/dist/pure.js:94
    <span
      class="TextField__StyledLabel-ramey7-1 jQMSNG"
      data-test-id="field-label"
    />

When I output the snapshot I see the style rule does exist.

exports[`TextField removes active state on blur 1`] = `
.c0 {
  padding: 0.5rem 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
  position: absolute;
  top: 1rem;
  -webkit-transform-origin: 0 0;
  -ms-transform-origin: 0 0;
  transform-origin: 0 0;
  -webkit-transition: color 0.4s cubic-bezier(0.25,0.8,0.25,1),-webkit-transform 0.4s cubic-bezier(0.25,0.8,0.25,1);
  -webkit-transition: color 0.4s cubic-bezier(0.25,0.8,0.25,1),transform 0.4s cubic-bezier(0.25,0.8,0.25,1);
  transition: color 0.4s cubic-bezier(0.25,0.8,0.25,1),transform 0.4s cubic-bezier(0.25,0.8,0.25,1);
  width: 100%;
  -webkit-transform: translateY(-50%) scale(0.75);
  -ms-transform: translateY(-50%) scale(0.75);
  transform: translateY(-50%) scale(0.75);    
  width: (100% / 0.75);  <-- style testing for
}

Test output shows:

● TextField › removes active state on blur

    No style rules found on passed Component

      159 |     // input has focus
    > 161 |     expect(label).toHaveStyleRule('width', '(100% / 0.75)')
          |                   ^

Dependencies:

    "@testing-library/jest-dom": "5.5.0",
    "@testing-library/react": "10.0.2",
    "babel-plugin-styled-components": "1.10.7",
    "jest-styled-components": "7.0.2",
    "styled-components": "5.1.0",
hyeunny commented 4 years ago

@fullstackzach having the same issue here

keubs commented 4 years ago

Adding to this: I'm seeing the same issue on the toHaveStyleRule() assertion. A little debugging appears to show some components loading the stylesheet via the getHTML() function via this function in utils.js (https://github.com/styled-components/jest-styled-components/blob/master/src/utils.js#L17) image

and other components are not image

masterSheet is a module imported via __PRIVATE__ (https://github.com/styled-components/jest-styled-components/blob/master/src/utils.js#L2) when styled-components is imported. I can't wrap my head around how these modules are constructed in styled-components, so this is where my trail goes cold. Should I reach out to the styled components team about this?

solimant commented 4 years ago

Probably similar?

Bug.test.tsx

import styled from "styled-components";
import React from "react";
import {mount} from "enzyme";

import 'jest-styled-components';

const Foo = () => (
  <div>Bar</div>
);

const StyledFoo = styled(Foo)`
  color: mediumspringgreen;
`;

describe('StyledFoo', () => {
  it('has a mediumspringgreen color', () => {
    const styledFoo = mount(<StyledFoo />);
    expect(styledFoo).toHaveStyleRule('color', 'mediumspringgreen');
  });
});

Snapshot:

exports[`StyledFoo has a mediumspringgreen color 1`] = `
.c0 {
  color: mediumspringgreen;
}

<Styled(Foo)>
  <Foo
    className="c0"    <<<<  it's there
  >
    <div>
      Bar
    </div>
  </Foo>
</Styled(Foo)>
`;

But I get No style rules found on passed Component.

However, if I introduce className, it works:

const Foo = ({className}: {className?: string}) => (
  <div className={className}>Bar</div>
);

Snapshot:

exports[`StyledFoo has a mediumspringgreen color 1`] = `
.c0 {
  color: mediumspringgreen;
}

<Styled(Foo)>
  <Foo
    className="c0"
  >
    <div
      className="c0"
    >
      Bar
    </div>
  </Foo>
</Styled(Foo)>
`;

Is this expected?

Tokimon commented 4 years ago

So... I had similar issue and I have already pointed out the problem in this comment https://github.com/styled-components/jest-styled-components/issues/297#issuecomment-639362675

In short the algorithm is built so that you HAVE TO have the sc- prefix when testing (ssr: true), but in my case (for some strange unknown reason) not all my components class names get the prefix, even with ssr: true, so I am stuck with upgrading until this is fixed.

I have already proposed to fix it, but as I am not sure how the procedure work I won't start any correction before I get a green light to do so.

tobilen commented 4 years ago

I got it working for me with the new namespace option of babel-plugin-styled-components, which enables us to pass the sc prefix by hand now. This is my babel config now:

module.exports = {
  // ...
  env: {
    test: {
      plugins: [
        [
          'babel-plugin-styled-components',
          { ssr: false, displayName: false, namespace: 'sc' },
        ],
      ],
    },
  },
};
oguzgelal commented 4 years ago

Simply adding the package react-test-renderer as a dependency fixed this issue for me:

yarn add react-test-renderer --dev
npm install react-test-renderer --save-dev

# for ts projects
yarn add @types/react-test-renderer --dev
npm install @types/react-test-renderer --save-dev

Also, here's my babel config:

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}
Fi1osof commented 4 years ago

Simply adding the package react-test-renderer as a dependency fixed this issue for me:

@oguzgelal , react-test-renderer installed, but problem still exists.

jest-styled-components checking is hardcoded.

const isStyledClass = className =>
  /^(\w+(-|_))?sc-/.test(className);

styles should masker with "sc-".

Snapshot from another my project with working:

<div
  class="indextest__DivStyled-sc-1wt13as-0 hViIRb"
>
  Text 
</div>

"sc-" in classname exists.

But on broken project not exists:

<div
  class="indextest__DivStyled-p00oe-0 jAwXaO"
>
  Text 
</div>

To fix i have to add namespace to babel config.

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        // In some projects missed namespace for styled-components
        // По какой-то причине в styled пустой неймспейс и в тестах не проходит маска
        // /^(\w+(-|_))?sc-/.test(className)
        "namespace": "sc-",
        "ssr": true
      }
    ]
  ]
}
nielswijers commented 3 years ago

i got the same problem. i saw that there where multiple versions of styled components included (v5.1 and v5.2).

removing v5.2 did the trick for me

tiagomnferreira-zz commented 3 years ago

I got it working for me with the new namespace option of babel-plugin-styled-components, which enables us to pass the sc prefix by hand now. This is my babel config now:

module.exports = {
  // ...
  env: {
    test: {
      plugins: [
        [
          'babel-plugin-styled-components',
          { ssr: false, displayName: false, namespace: 'sc' },
        ],
      ],
    },
  },
};

This worked for me! Using nx.dev

stonebk commented 3 years ago

Using Jest testEnvironment "node" seemed to be one of the issues for me. When I change it back to the default "jsdom", the issue went away.

kylorhall commented 3 years ago

Maybe another slight piece to someone's puzzle. I've had to solve this a half dozen times and this is yet another scenario for me.

Both specificity and the modifier option are very key. Regardless of babel-plugin-styled-components configuration, you can still easily encounter this error for a lot of other reasons.

:x: Broken No style rules found on passed Component:

import styled from 'styled-components';
import { mount } from 'enzyme';

const Checkbox = styled.input`
  input[type='checkbox']& {
    height: 20px;
  }
`;

test('fails', () => {
  expect(mount(<Checkbox />)).toHaveStyleRule('height', '20px');
});

test('fails w/ modifier', () => {
  expect(mount(<Checkbox />)).toHaveStyleRule('height', '20px', {
    modifier: "input[type='checkbox']",   // no combination of this worked here like it does in other tests…
  });
});

🎉 I got it passing by changing the component itself, adding &, to the specificity. This would be breaking in other places, but in here it works 🤞…

import styled from 'styled-components';
import { mount } from 'enzyme';

const Checkbox = styled.input`
  &,
  input[type='checkbox']& {
    height: 20px;
  }
`;

test('passes', () => {
  expect(mount(<Checkbox />)).toHaveStyleRule('height', '20px');
});

I hope no one's really using this specific pattern scenario, but it might highlight specificity issues to people debugging.

lucasiori commented 3 years ago

I'm receiving an error No style rules found on passed Component with toHaveStyleRule. It started after update jest to 27x, I tried to configure the babel-plugin-styled-components with { ssr: false } but it doesn't work. Any idea to fix it?

Setup:

"jest": "^27.0.6"
"jest-styled-components": "^7.0.5",
"styled-components": "^5.3.1",
tobilen commented 3 years ago

I'm receiving an error No style rules found on passed Component with toHaveStyleRule. It started after update jest to 27x, I tried to configure the babel-plugin-styled-components with { ssr: false } but it doesn't work. Any idea to fix it?

Setup:

"jest": "^27.0.6"
"jest-styled-components": "^7.0.5",
"styled-components": "^5.3.1",

try with { ssr: false, displayName: false, namespace: "sc" }.

our babel config:

const defaultConfig = {
  presets: ["next/babel"],
  plugins: [],
};

module.exports = ({ env }) => {
  if (env("test")) {
    return {
      ...defaultConfig,
      plugins: [
        ...defaultConfig.plugins,
        [
          "babel-plugin-styled-components",
          { ssr: false, displayName: false, namespace: "sc" },
        ],
      ],
    };
  }

  return {
    ...defaultConfig,
    plugins: [
      ...defaultConfig.plugins,
      ["babel-plugin-styled-components", { ssr: true, namespace: "sc" }],
    ],
  };
};
moatorres commented 3 years ago

Just a heads-up: none of the above seems to work for me, either with enzyme's mount function or react-test-renderer's renderer.create.

By "none of the above" I mean any { ssr: false, displayName: false, namespace: "sc" } suggested solutions.

Versions:

"jest-styled-components": "7.0.5",
"styled-components": "5.3.3"
"react-test-renderer": "17.0.2"
"enzyme": "3.11.0",
"babel-plugin-styled-components": "1.13.3",

My current workaround (this gist):

test-helpers.ts

import renderer, { ReactTestRendererJSON } from 'react-test-renderer'

export function render(component: React.ReactElement) {
  return (
    renderer
      .create(component)
      .toJSON() as ReactTestRendererJSON
  )
}

export function renderClasses(component: React.ReactElement): string[] {
  const {
    props: { className },
  } = render(component)
  return className ? className.trim().split(' ') : []
}

type ComputedStyles = Record<string, string | Record<string, string>>

export function getComputedStyles(className: string) {
  const div = document.createElement('div')
  div.className = className

  const computed: ComputedStyles = {}

  for (const sheet of document.styleSheets) {
    for (const rule of sheet.cssRules) {
      if (rule instanceof CSSMediaRule) readMedia(rule)
      else if (rule instanceof CSSStyleRule) readRule(rule, computed)
    }
  }

  return computed

  function matchesSafe(node: HTMLDivElement, selector: string) {
    if (!selector) return false

    try {
      return node.matches(selector)
    } catch (error) {
      return false
    }
  }

  function readRule(rule: CSSStyleRule, dest: ComputedStyles) {
    if (matchesSafe(div, rule.selectorText)) {
      const { style } = rule
      for (let i = 0; i < style.length; i++) {
        const prop = style[i]
        dest[prop] = style.getPropertyValue(prop)
      }
    }
  }

  function readMedia(mediaRule: CSSMediaRule) {
    const key = `@media ${mediaRule.media[0]}`
    const dest = {}
    for (const rule of mediaRule.cssRules) {
      if (rule instanceof CSSStyleRule) readRule(rule, dest)
    }

    if (Object.keys(dest).length > 0) computed[key] = dest
  }
}

/* Return styles from all classes applied merged into a single object. */
export function getStyles(comp: React.ReactElement) {
  return renderClasses(comp)
    .filter(c => !c.includes('sc'))
    .map(getComputedStyles)
    .reduce((result, current) => Object.assign(result, current), {})
}

Then we can use the getStyles function to check our styles directly with jest, like so:


import { getStyles } from './test-helpers'

test('extended components keep their styles', () => {
  const Box = styled.div`
    margin: 16px;
  `
  const Card = styled(Box)`
    color: tomato;
  `

  const styles = getStyles(<Card />)
  expect(styles).toEqual({ margin: '16px', color: 'tomato' })
})
mryechkin commented 2 years ago

try with { ssr: false, displayName: false, namespace: "sc" }

This worked for me 🥳 Thanks!

tenshiemi commented 2 years ago

None of the recommended solutions are working for me so far

nickjohngray commented 2 years ago

Adding the line below in babel.config.js fixed it for me: ['babel-plugin-styled-components', { ssr: process.env.NODE_ENV === 'test' }]

// our project originally had ssr as false , process.env.NODE_ENV === 'test' sets it as true when running tests.

when using:

"react-test-renderer": "^17.0.2" "jest-styled-components": "^7.0.8" "styled-components": "^5.1.1"

athammer commented 2 years ago

Seems to happen to me when switching from commonjs to esnext for me. None of the solutions worked above. Been to pretty much every GitHub/Stackoverflow thread related to this and nothing worked.

I'm guessing something is breaking due to the switch? No clue what package or how to fix it though

I'm using

    "@testing-library/react": "^12.1.2",
    "babel-plugin-styled-components": "^2.0.7",
    "jest-styled-components": "^7.0.4",
    "styled-components": "^5.3.3",

Reverting to the versions below fixes it even without reverting back to commonjs. This is super frustrating :(

    "jest-styled-components": "^6.3.4",
    "styled-components": "^4.4.1",

Code example

// Test File
expect(screen.getByTestId('payment-date-name')).toHaveStyleRule('color', '#aa2e23');

// Code
const $PaymentDateName = styled.div<{ isOverdue: boolean }>`
  color: ${fromTheme('clrGrey')};
  ${({ isOverdue }) =>
    isOverdue &&
    css`
      /* In Chrome only, without setting flex-start the item will stretch to fill up its container */
      align-items: flex-start;
      background-color: ${fromTheme('clrWarningLight')};
      color: ${fromTheme('clrWarning')};
      padding: 0 ${fromTheme('spacing8')};
    `};
`;
kolesker commented 1 year ago

To me none of the above fixes worked and I found why it was working for me in some test files but not in others:

I was creating the renderer INSIDE the DESCRIBE and it was like 'jest-styled-components' was not being applied to those test files. I moved the renderer creation to every single test (test/it) and worked:

// BEFORE
describe('Label', () => {
  const tree = renderer.create(<Label />).toJSON();
  it('should match snapshot', () => {
    expect(tree).toHaveStyleRule('display', 'block');
  });
});

// AFTER
describe('Label', () => {
  it('should match snapshot', () => {
    const tree = renderer.create(<Label />).toJSON();
    expect(tree).toHaveStyleRule('display', 'block');
  });
});

"styled-components": "^5.3.6", "jest-styled-components": "^7.1.1", "babel-plugin-styled-components": "^2.0.7",