• Stars
    star
    1,577
  • Rank 28,549 (Top 0.6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 7 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🔧 💅 Jest utilities for Styled Components

NPM version Join the community on Spectrum

Build Status tested with jest styled with prettier

Jest Styled Components

A set of utilities for testing Styled Components with Jest. This package improves the snapshot testing experience and provides a brand new matcher to make expectations on the style rules.

Quick Start

Installation

yarn add --dev jest-styled-components

Usage

import React from 'react'
import styled from 'styled-components'
import renderer from 'react-test-renderer'
import 'jest-styled-components'

const Button = styled.button`
  color: red;
`

test('it works', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toMatchSnapshot()
  expect(tree).toHaveStyleRule('color', 'red')
})

If you don't want to import the library in every test file, it's recommended to use the global installation method.

Table of Contents

Snapshot Testing

Jest snapshot testing is an excellent way to test React components (or any serializable value) and make sure things don't change unexpectedly. It works with Styled Components but there are a few problems that this package addresses and solves.

For example, suppose we create this styled Button:

import styled from 'styled-components'

const Button = styled.button`
  color: red;
`

Which we cover with the following test:

import React from 'react'
import renderer from 'react-test-renderer'

test('it works', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toMatchSnapshot()
})

When we run our test command, Jest generates a snapshot containing a few class names (which we didn't set) and no information about the style rules:

exports[`it works 1`] = `
<button
  className="sc-bdVaJa rOCEJ"
/>
`;

Consequently, changing the color to green:

const Button = styled.button`
  color: green;
`

Results in the following diff, where Jest can only tell us that the class names are changed. Although we can assume that if the class names are changed the style rules are also changed, this is not optimal (and is not always true).

- Snapshot
+ Received

 <button
-  className="sc-bdVaJa rOCEJ"
+  className="sc-bdVaJa hUzqNt"
 />

Here's where Jest Styled Components comes to rescue.

We import the package into our test file:

import 'jest-styled-components'

When we rerun the test, the output is different: the style rules are included in the snapshot, and the hashed class names are substituted with placeholders that make the diffs less noisy:

- Snapshot
+ Received

+.c0 {
+  color: green;
+}
+
 <button
-  className="sc-bdVaJa rOCEJ"
+  className="c0"
 />

This is the resulting snapshot:

exports[`it works 1`] = `
.c0 {
  color: green;
}

<button
  className="c0"
/>
`;

Now, suppose we change the color again to blue:

const Button = styled.button`
  color: blue;
`

Thanks to Jest Styled Components, Jest is now able to provide the exact information and make our testing experience even more delightful 💖:

- Snapshot
+ Received

 .c0 {
-  color: green;
+  color: blue;
 }

 <button
   className="c0"
 />

Enzyme

enzyme-to-json is necessary to generate snapshots using Enzyme's shallow or full DOM rendering.

yarn add --dev enzyme-to-json

It can be enabled globally in the package.json:

"jest": {
  "snapshotSerializers": [
    "enzyme-to-json/serializer"
  ]
}

Or imported in each test:

import toJson from 'enzyme-to-json'

// ...

expect(toJson(wrapper)).toMatchSnapshot()

Jest Styled Components works with shallow rendering:

import { shallow } from 'enzyme'

test('it works', () => {
  const wrapper = shallow(<Button />)
  expect(wrapper).toMatchSnapshot()
})

And full DOM rendering as well:

import { mount } from 'enzyme'

test('it works', () => {
  const wrapper = mount(<Button />)
  expect(wrapper).toMatchSnapshot()
})

react-testing-library

To generate snapshots with react-testing-library, you can follow the example below:

import { render } from '@testing-library/react'

test('it works', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toMatchSnapshot()
})

The snapshots will contain class instead of className because the snapshots are of DOM elements

Theming

In some scenarios, testing components that depend on a theme can be tricky, especially when using Enzyme's shallow rendering.

For example:

const Button = styled.button`
  color: ${props => props.theme.main};
`

const theme = {
  main: 'mediumseagreen',
}

The recommended solution is to pass the theme as a prop:

const wrapper = shallow(<Button theme={theme} />)

The following function might also help for shallow rendering:

const shallowWithTheme = (tree, theme) => {
  const context = shallow(<ThemeProvider theme={theme} />)
    .instance()
    .getChildContext()
  return shallow(tree, { context })
}

const wrapper = shallowWithTheme(<Button />, theme)

and for full DOM rendering:

const mountWithTheme = (tree, theme) => {
  const context = shallow(<ThemeProvider theme={theme} />)
    .instance()
    .getChildContext()

  return mount(tree, {
    context,
    childContextTypes: ThemeProvider.childContextTypes,
  })
}

Preact

To generate snapshots of Preact components, add the following configuration:

"jest": {
  "moduleNameMapper": {
    "^react$": "preact-compat"
  }
}

And render the components with preact-render-to-json:

import React from 'react'
import styled from 'styled-components'
import render from 'preact-render-to-json'
import 'jest-styled-components'

const Button = styled.button`
  color: red;
`

test('it works', () => {
  const tree = render(<Button />)
  expect(tree).toMatchSnapshot()
})

The snapshots will contain class instead of className. Learn more.

Serializer

The serializer can be imported separately from jest-styled-components/serializer. This makes it possible to use this package with specific-snapshot and other libraries.

import React from 'react'
import styled from 'styled-components'
import renderer from 'react-test-renderer'
import { styleSheetSerializer } from "jest-styled-components/serializer"
import { addSerializer } from "jest-specific-snapshot"

addSerializer(styleSheetSerializer)

const Button = styled.button`
  color: red;
`

test('it works', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toMatchSpecificSnapshot("./Button.snap")
})

Serializer Options

The serializer can be configured to control the snapshot output.

import { render } from '@testing-library/react'
import { setStyleSheetSerializerOptions } from 'jest-styled-components/serializer'

setStyleSheetSerializerOptions({
  addStyles: false,
  classNameFormatter: (index) => `styled${index}`
});

test('it works', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toMatchSnapshot()
})

toHaveStyleRule

The toHaveStyleRule matcher is useful to test if a given rule is applied to a component. The first argument is the expected property, the second is the expected value which can be a String, RegExp, Jest asymmetric matcher or undefined. When used with a negated ".not" modifier the second argument is optional and can be omitted.

const Button = styled.button`
  color: red;
  border: 0.05em solid ${props => props.transparent ? 'transparent' : 'black'};
  cursor: ${props => !props.disabled && 'pointer'};
  opacity: ${props => props.disabled && '.65'};
`

test('it applies default styles', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toHaveStyleRule('color', 'red')
  expect(tree).toHaveStyleRule('border', '0.05em solid black')
  expect(tree).toHaveStyleRule('cursor', 'pointer')
  expect(tree).not.toHaveStyleRule('opacity') // equivalent of the following two
  expect(tree).not.toHaveStyleRule('opacity', expect.any(String))
  expect(tree).toHaveStyleRule('opacity', undefined)
})

test('it applies styles according to passed props', () => {
  const tree = renderer.create(<Button disabled transparent />).toJSON()
  expect(tree).toHaveStyleRule('border', expect.stringContaining('transparent'))
  expect(tree).toHaveStyleRule('cursor', undefined)
  expect(tree).toHaveStyleRule('opacity', '.65')
})

The matcher supports an optional third options parameter which makes it possible to search for rules nested within an At-rule (see media and supports) or to add modifiers to the class selector. This feature is supported in React only, and more options are coming soon.

const Button = styled.button`
  @media (max-width: 640px) {
    &:hover {
      color: red;
    }
  }
`

test('it works', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toHaveStyleRule('color', 'red', {
    media: '(max-width:640px)',
    modifier: ':hover',
  })
})

If a rule is nested within another styled-component, the modifier option can be used with the css helper to target the nested rule.

const Button = styled.button`
  color: red;
`

const ButtonList = styled.div`
  display: flex;

  ${Button} {
    flex: 1 0 auto;
  }
`

import { css } from 'styled-components';

test('nested buttons are flexed', () => {
  const tree = renderer.create(<ButtonList><Button /></ButtonList>).toJSON()
  expect(tree).toHaveStyleRule('flex', '1 0 auto', {
    modifier: css`${Button}`,
  })
})

You can take a similar approach when you have classNames that override styles

const Button = styled.button`
  background-color: red;
  
  &.override {
    background-color: blue;
  }
`
const wrapper = mount(<Button className="override">I am a button!</Button>);

expect(wrapper).toHaveStyleRule('background-color', 'blue', {
  modifier: '&.override',
});

This matcher works with trees serialized with react-test-renderer, react-testing-library, or those shallow rendered or mounted with Enzyme. It checks the style rules applied to the root component it receives, therefore to make assertions on components further in the tree they must be provided separately (Enzyme's find might help).

Note: for react-testing-library, you'll need to pass the first child to check the top-level component's style. To check the styles of deeper components, you can use one of the getBy* methods to find the element (e.g. expect(getByTestId('styled-button')).toHaveStyleRule('color', 'blue'))

To use the toHaveStyleRule matcher with React Native, change the import statement to:

import 'jest-styled-components/native'

Global installation

It is possible to setup this package for all the tests. For Example: import the library once in the src/setupTests.js as follows:

import 'jest-styled-components'

...then add the following to test.config.js:

  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js']

Working with multiple packages

If Jest Styled Components is not working, it is likely caused by loading multiple instances of styled-components. This can happen especially when working with a Lerna monorepo. Starting with [email protected], a warning will be logged when multiple instances of it are being included and run as part of the Jest tests. Using [email protected] and lower with multiple instances will cause a silent error with unexpected results.

To debug and fix multiple instances of styled-components see the FAQ on "Why am I getting a warning about several instances of module on the page?".

Contributing

Please open an issue and discuss with us before submitting a PR.

More Repositories

1

styled-components

Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅
TypeScript
39,930
star
2

polished

A lightweight toolset for writing styles in JavaScript ✨
JavaScript
7,562
star
3

awesome-styled-components

A curated list of awesome styled-components resources 💅
3,283
star
4

xstyled

A utility-first CSS-in-JS framework built for React. 💅👩‍🎤⚡️
MDX
2,254
star
5

vue-styled-components

Visual primitives for the component age. A simple port for Vue of styled-components 💅
JavaScript
1,362
star
6

styled-theming

Create themes for your app using styled-components
JavaScript
1,168
star
7

css-to-react-native

Convert CSS text to a React Native stylesheet object
JavaScript
1,117
star
8

babel-plugin-styled-components

Improve the debugging experience and add server-side rendering support to styled-components
JavaScript
1,063
star
9

vscode-styled-components

Syntax highlighting for styled-components
JavaScript
911
star
10

stylelint-processor-styled-components

Lint your styled components with stylelint!
JavaScript
657
star
11

styled-components-website

The styled-components website and documentation
MDX
607
star
12

webstorm-styled-components

styled-components highlighting support in IntelliJ editors
Kotlin
377
star
13

vim-styled-components

Vim bundle for http://styled-components.com based javascript files.
Vim Script
300
star
14

comparison

Comparing different ways to style components
JavaScript
183
star
15

elm-styled

Styling your Html Elements with typed Css 💅
Elm
180
star
16

babel-plugin-polished

Compile polished helper functions at build time
JavaScript
138
star
17

styled-elements

Styled components for the DOM.
JavaScript
87
star
18

stylelint-config-styled-components

The shareable stylelint config for stylelint-processor-styled-components
JavaScript
71
star
19

spec

Design Requirements and Spec for a Component-oriented CSS Solution
61
star
20

color-schemer

A React app to help you select a color scheme, built with styled-components and polished
JavaScript
55
star
21

styled-components-codemods

Automatic codemods to upgrade your styled-components code to newer versions.
JavaScript
52
star
22

styled-components-experimentation

A place to play with things that shouldn't be in core
JavaScript
31
star
23

s-c.sh

The styled-components URL shortener!
30
star
24

styled-components-native-code-mod

JavaScript
28
star
25

styled-components.github.io

The styled-components homepage
JavaScript
22
star
26

brand

Logo and brand related materials
11
star
27

benchmark

JavaScript
9
star
28

todomvc

The Speedometer 2.0 React TodoMVC example rebuilt with styled-components
JavaScript
6
star
29

babel-plugin-styled-components-ssr

[EXPERIMENTAL]
JavaScript
2
star
30

use-styled-hook

tbd
1
star