Jest-styled-components: Using toHaveStyleRule on nested classes

Created on 9 Aug 2017  路  14Comments  路  Source: styled-components/jest-styled-components

I havn't been able to find any documentation on using toHaveStyleRule for checking properties on nested classes.

Let's say I have a stylesheet for a styled component that looks like this:

export default styled.div`
  position: relative;
  line-height: 24px;
  width: ${props => props.width};
  height: 72px;

  label {
    color: ${props => {
      if (!props.hasValue && !props.meta.active) {
        return props.theme.lightGrey
      }
      if (props.meta.touched && props.meta.error) {
        return props.theme.errorColor
      }
      if (props.meta.touched && props.meta.warning) {
        return props.theme.warningColor
      }
      return props.theme.brandColor
    }};
  }

  .placeholder {
    color: ${props => props.theme.lightGrey};
  }
`

How would I check for a style rule on label or .placeholder?

enhancement

Most helpful comment

@duro This could be possible with a little more modification of using modifiers.
(Awaiting merge on https://github.com/styled-components/jest-styled-components/pull/60 馃槀 )

@MicheleBertoli I experimented and could get this test passed based on PR https://github.com/styled-components/jest-styled-components/pull/60 code

test('nested', () => {
  const Wrapper = styled.div`
    margin: auto;
    label {
      color: black;
      ${props => props.green && 'color: green;'}
    }
    .flex {
      display: flex;
    }
  `

  toHaveStyleRule(<Wrapper />, 'color', 'black', { modifier: ' label' })
  toHaveStyleRule(<Wrapper green />, 'color', 'green', { modifier: ' label' })
  toHaveStyleRule(<Wrapper green />, 'display', 'flex', { modifier: ' .flex' })
})

When using nested selectors, the rules classnames has a gap like
[ '.lhCfhI label' ] [ '.hLYBzJ .flex' ] so the modifier value follows that gap.

If not adding too much complexity, I think the selectors that finds the classnames to match in hasClassNames could simply remove the gaps for matching?

Also, if this feature is added, should this option be called nested innerSelector 馃槀 ? or use the same modifier so the code doesn't have more branches (I'm not sure if make sense to users) ?

Also this might get closer to testing styles when referring to other components, I've been experimenting something like

test('contexted', () => {
  const Item = styled.li`padding: 10px 20px;`

  const List = styled.ul`
    margin: auto;
    ${Item} {padding: 8px 16px;}
  `

  const Component = mount(
    <List>
      <Item />
      <Item />
    </List>
  )

  toHaveStyleRule(Component.find(Item), 'padding', '8px 16px')
})

It's just need to modify the test file to handle more complex mounts.

All 14 comments

Interesting question, thanks @duro.
At the moment it's not possible, but it would be a very interesting addition.
cc @yanawaro

@duro This could be possible with a little more modification of using modifiers.
(Awaiting merge on https://github.com/styled-components/jest-styled-components/pull/60 馃槀 )

@MicheleBertoli I experimented and could get this test passed based on PR https://github.com/styled-components/jest-styled-components/pull/60 code

test('nested', () => {
  const Wrapper = styled.div`
    margin: auto;
    label {
      color: black;
      ${props => props.green && 'color: green;'}
    }
    .flex {
      display: flex;
    }
  `

  toHaveStyleRule(<Wrapper />, 'color', 'black', { modifier: ' label' })
  toHaveStyleRule(<Wrapper green />, 'color', 'green', { modifier: ' label' })
  toHaveStyleRule(<Wrapper green />, 'display', 'flex', { modifier: ' .flex' })
})

When using nested selectors, the rules classnames has a gap like
[ '.lhCfhI label' ] [ '.hLYBzJ .flex' ] so the modifier value follows that gap.

If not adding too much complexity, I think the selectors that finds the classnames to match in hasClassNames could simply remove the gaps for matching?

Also, if this feature is added, should this option be called nested innerSelector 馃槀 ? or use the same modifier so the code doesn't have more branches (I'm not sure if make sense to users) ?

Also this might get closer to testing styles when referring to other components, I've been experimenting something like

test('contexted', () => {
  const Item = styled.li`padding: 10px 20px;`

  const List = styled.ul`
    margin: auto;
    ${Item} {padding: 8px 16px;}
  `

  const Component = mount(
    <List>
      <Item />
      <Item />
    </List>
  )

  toHaveStyleRule(Component.find(Item), 'padding', '8px 16px')
})

It's just need to modify the test file to handle more complex mounts.

You are awesome @yanawaro, thanks!

As you know I love our minimalistic approach, but it seems wrong to me to ask developers to include the space in the "modifier". I'd rather implement little bit complex solution which can find the matches applying an optional space.

A nested or child option would work but I also love your last example where you pass the child to expect and then search for the style within the parent.

Any update on this @yanawaro?
I know you are super busy but it'd be great if you could push some code, so I might help you complete the feature.
Thank you very much in advance.

const ParagraphStyled = styled(Text)`
   color: #fff;
`;
const wrapper = shallow(<ParagraphStyled />);
expect(wrapper).toHaveStyleRule('color', '#fff');

When i try to test i get error 'Property not found: "color"', but if
const ParagraphStyled = styled.peverything will be ok. Is it possible to test const ParagraphStyled = styled(Text) ?

Hello @alex-shatalov, you test seems to pass.
Which version of the package are you using?
Thank you very much!

"jest-enzyme": "^4.0.1",
"enzyme": "^3.2.0",
"jest-styled-components": "^4.9.0"

@MicheleBertoli , my bad, i look again to my ParagraphStyled component and it is triple nested.
const ParagraphStyled = styled(Paragraph)
const Paragraph = styled(Text)
const Text= styled.p
If it runs like double you are right tests pass. But on triple same error.

I see @alex-shatalov, thanks for providing more information.

So the confusion comes from the way shallow rendering works with Styled Components.
If you run console.log(wrapper.debug()), you will see the following component:

<Styled(styled.p) className="sc-htpNat fNIFfJ" innerRef={[undefined]} />

which is not the one you are looking for.

What you should do instead is using the Enzyme's dive function, to go one level deeper.
So, If you change your test to (please note wrapper.dive() in the expectation):

test('dive', () => {
  const Text = styled.p``
  const Paragraph = styled(Text)``
  const ParagraphStyled = styled(Paragraph)`
    color: #fff;
  `
  const wrapper = shallow(<ParagraphStyled />)
  expect(wrapper.dive()).toHaveStyleRule('color', '#fff')
})

It finally works.

I hope this helps.

@MicheleBertoli thanks a lot, you helps me!

But i have another strange situation
My components is being exporting from files. And all of them have style color.
So i try to change your working example of test('dive', () just like this.

test('dive', () => {
  const Text = styled.p`
    color: green;
`
  const Paragraph = styled(Text)`
    color: blue;
`
  const ParagraphStyled = styled(Paragraph)`
    color: #fff;
  `
  const wrapper = shallow(<ParagraphStyled />)
  expect(wrapper.dive()).toHaveStyleRule('color', '#fff')
})

And yes it works. Test Suites: 1 passed, 1 total;

But when i take out component Text to another file and import it.

Text file looks like this

export const TextAnother = styled.p`
  color: blue;
`;

and test suite is now like this

import { TextAnother } from '../../atoms/Text';

test('diveText', () => {
  const Paragraph = styled(TextAnother)`
    color: blue;
`
  const ParagraphStyled = styled(Paragraph)`
    color: #fff;
  `
  const wrapper = shallow(<ParagraphStyled />)
  expect(wrapper.dive()).toHaveStyleRule('color', '#fff')
})

Tests fails.
Expected color to match: "#fff" Received: "blue"
The property color is from component Text but not from ParagraphStyled

So i look in to file toHaveStyleRule and found that test will pass only if i change isTagWithClassName and remove node type checking

const isTagWithClassName = node => node.prop('className');

and remove dive() in test

Hello @alex-shatalov, I'm glad to hear the first solution was helpful.
I tried your code but the second test (which imports TextAnother) works for me.
Could you please check and provide more information?
Thank you very much!

Hi @MicheleBertoli can we reopen this? I use a lot of @yanawaro's 2nd example where I style nested subcomponents. Will be happy to try to get a PR out for it soon unless @yanawaro already has something

Yep please @Joelkang - I would love to get this done.
I'm looking forward to receiving your PR.
Thank you very much!

@MicheleBertoli PR rendered; we should probably discuss how to handle nested styled components, but I think nested element/class/attribute selectors should be handled properly

This is awesome, @Joelkang.
Thank you very much!

Was this page helpful?
0 / 5 - 0 ratings