├── .babelrc
├── .editorconfig
├── .eslintrc
├── .flowconfig
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── package.json
├── spec
└── support
│ └── jasmine.json
├── src
├── chai.js
├── expect.js
├── index.js
├── init.js
├── jasmine.js
├── jest.js
├── matchers
│ ├── toBeAGlobalStyle.js
│ ├── toHaveComponent.js
│ ├── toHaveKeyframeRule.js
│ ├── toHaveStyleRule.js
│ └── toNotHaveStyleRule.js
├── serializers
│ └── styleSheetSerializer.js
└── utils
│ ├── getCSS.js
│ ├── getClassNames.js
│ ├── getCodeBlock.js
│ ├── isOverV2.js
│ ├── isServer.js
│ └── styleSheet.js
├── test
├── chai.spec.js
├── expect.spec.js
├── jasmine.spec.js
├── jest.spec.js
├── matchers
│ ├── __snapshots__
│ │ └── toMatchSnapshot.spec.js.snap
│ ├── toBeAGlobalStyle.spec.js
│ ├── toHaveComponent.spec.js
│ ├── toHaveKeyframeRule.spec.js
│ ├── toHaveStyleRule.spec.js
│ ├── toMatchSnapshot.spec.js
│ └── toNotHaveStyleRule.js
├── setup.js
└── utils
│ ├── framework.js
│ └── getCodeBlock.spec.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "flow",
4 | "es2015",
5 | "stage-0",
6 | "react"
7 | ],
8 | "plugins": [
9 | [
10 | "transform-decorators-legacy"
11 | ],
12 | [
13 | "transform-es3-member-expression-literals"
14 | ],
15 | [
16 | "transform-es3-property-literals"
17 | ]
18 | ]
19 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain
2 | # consistent coding styles between different editors and IDEs.
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "jsx": true,
4 | "modules": true
5 | },
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "parser": "babel-eslint",
11 | "rules": {
12 | "linebreak-style": 0,
13 | "react/jsx-filename-extension": "off",
14 | "quotes": [
15 | 2,
16 | "single"
17 | ],
18 | "strict": [
19 | 2,
20 | "never"
21 | ],
22 | "react/jsx-uses-react": 2,
23 | "react/jsx-uses-vars": 2,
24 | "react/react-in-jsx-scope": 2
25 | },
26 | "plugins": [
27 | "flowtype",
28 | "react"
29 | ],
30 | "globals": {
31 | "describe": false,
32 | "test": false,
33 | "expect": false,
34 | "jasmine": false,
35 | "it": false,
36 | "before": false,
37 | "beforeEach": false,
38 | "beforeAll": false,
39 | "after": false,
40 | "afterEach": false
41 | },
42 | "extends": [
43 | "plugin:flowtype/recommended",
44 | "prettier",
45 | "prettier/flowtype",
46 | "prettier/react",
47 | "airbnb"
48 | ],
49 | "settings": {
50 | "flowtype": {
51 | "onlyFilesWithFlowAnnotation": true
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 | ./src
5 |
6 | [libs]
7 |
8 | [options]
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **General Information**
2 |
3 | - [ ] Bug
4 | - [ ] Improvement
5 | - [ ] Feature
6 | - [ ] Other
7 |
8 | **Description**
9 |
10 | (Add images if possible)
11 |
12 | **Steps to reproduce**
13 |
14 | (Add link to a demo on https://jsfiddle.net or similar if possible)
15 |
16 | **Versions**
17 |
18 | - styled-components-test-utils:
19 | - node:
20 |
21 | - jest:
22 | - expect:
23 | - chai:
24 | - jasmine:
25 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | - [ ] Pull request has tests / docs demo, and is linted.
2 | - [ ] Description explains the issue / use-case resolved, and auto-closes the related issue(s) (https://help.github.com/articles/closing-issues-via-commit-messages/).
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | *.log
4 | dist
5 | lib
6 | es
7 | coverage
8 | _book
9 | .nyc_output
10 | TODO.md
11 | package-lock.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | - "7"
5 | - "8"
6 | script:
7 | - npm run check:src
8 | - npm run build
9 | branches:
10 | only:
11 | - master
12 | cache:
13 | directories:
14 | - $HOME/.npm
15 | after_success: npm run test:cov
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 | Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/mbasso/styled-components-test-utils/releases) page.
5 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We are pleased to receive any contribution by the community. By contributing to styled-components-test-utils, you agree to abide by the [code of conduct](https://github.com/mbasso/styled-components-test-utils/blob/master/CODE_OF_CONDUCT.md).
4 |
5 | ## Issues
6 |
7 | Before opening an issue, please search the [issue tracker](https://github.com/mbasso/styled-components-test-utils/issues) to make sure your issue hasn’t already been reported.
8 | Please follow our guidelines when opening an issue, in that way we can understand your problem easily and we can help you faster.
9 |
10 | ### Bugs and Improvements
11 |
12 | Bugs and Improvements in general can be discussed on the [issue tracker](https://github.com/mbasso/styled-components-test-utils/issues), make sure that there aren't other issues with the same purpose.
13 |
14 | ## Development
15 |
16 | Visit the [issue tracker](https://github.com/mbasso/styled-components-test-utils/issues) to find a list of open issues that need attention.
17 |
18 | Fork, then clone the repo:
19 |
20 | ```
21 | git clone https://github.com/mbasso/styled-components-test-utils.git
22 | ```
23 |
24 | ### Building
25 |
26 | #### Building styled-components-test-utils
27 |
28 | Running the `build` task will create both a CommonJS module-per-module build and a UMD build.
29 | ```
30 | npm run build
31 | ```
32 |
33 | To create just a CommonJS module-per-module build:
34 |
35 | ```
36 | npm run build:commonjs
37 |
38 | ```
39 |
40 | The result will be in the `lib` folder.
41 |
42 | To create just a UMD build:
43 | ```
44 | npm run build:umd
45 | npm run build:umd:min
46 | ```
47 |
48 | The result will be in the `dist` folder.
49 |
50 | ### Testing and Linting
51 |
52 | To run both linting and testing at once, run the following:
53 |
54 | ```
55 | npm run check:src
56 | ```
57 |
58 | To only run linting:
59 |
60 | ```
61 | npm run lint
62 | ```
63 |
64 | To only run tests:
65 |
66 | ```
67 | npm run test
68 | ```
69 |
70 | To continuously watch and run tests, run the following:
71 |
72 | ```
73 | npm run test:watch
74 | ```
75 |
76 | ### Docs
77 |
78 | Improvements to the documentation are always welcome. Before submitting your changes, check that they respect all docs style.
79 | For example, use "-" for lists etc.
80 |
81 | ### Pull Request
82 |
83 | Pull requests are welcome, but make sure that your changes respect the 2 points in PR template:
84 |
85 | - Pull request has tests / docs demo, and is linted.
86 | - Description explains the issue / use-case resolved, and auto-closes the related issue(s)
87 |
88 | Thank you!
89 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright for portions of project styled-components-test-utils are held by Michele Bertoli, 2017 as part of project jest-styled-components. All other copyright for project styled-components-test-utils are held by Matteo Basso.
4 |
5 | Copyright (c) 2017 Matteo Basso
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # styled-components-test-utils
2 |
3 | [](https://travis-ci.org/mbasso/styled-components-test-utils)
4 | [](https://www.npmjs.com/package/styled-components-test-utils)
5 | [](https://www.npmjs.com/package/styled-components-test-utils)
6 | [](https://coveralls.io/github/mbasso/styled-components-test-utils?branch=master)
7 |
8 | > Test utils for styled-components compatible with jest, expect, chai and jasmine
9 |
10 | ## Table of Contents
11 |
12 | - [Motivation](#motivation)
13 | - [Installation](#installation)
14 | - [Jest](#jest)
15 | - [Expect](#expect)
16 | - [Chai](#chai)
17 | - [Jasmine](#jasmine)
18 | - [Api](#api)
19 | - [toHaveStyleRule](#tohavestylerule)
20 | - [toNotHaveStyleRule](#tonothavestylerule)
21 | - [toHaveKeyframeRule](#tohavekeyframerule)
22 | - [toHaveComponent](#tohavecomponent)
23 | - [toBeAGlobalStyle](#tobeaglobalstyle)
24 | - [toMatchSnapshot](#tomatchsnapshot)
25 | - [Change Log](#change-log)
26 | - [Authors](#authors)
27 | - [Copyright and License](#copyright-and-license)
28 |
29 | ## Motivation
30 | This project is based on one simple idea: write a powerful set of test-utils for styled-components compatible with a lot assertion libraries.
31 | This is born from the willing to use [jest-styled-components](https://github.com/styled-components/jest-styled-components) (a useful project on which this one is based) with [expect](https://github.com/mjackson/expect).
32 |
33 | ## Installation
34 |
35 | You can install styled-components-test-utils using [npm](https://www.npmjs.com/package/styled-components-test-utils):
36 |
37 | ```bash
38 | npm install --save-dev styled-components-test-utils
39 | ```
40 |
41 | and if you haven't `react-test-renderer`:
42 |
43 | ```bash
44 | npm install --save-dev react-test-renderer
45 | ```
46 |
47 | and following one of these: [Jest](#jest), [Expect](#expect), [Chai](#chai), [Jasmine](#jasmine)
48 |
49 | ### Jest
50 | To use styled-components-test-utils with jest, you simply have to import the following:
51 |
52 | ```js
53 | import 'styled-components-test-utils/lib/jest';
54 |
55 | // ...
56 |
57 | expect(component).toHaveStyleRule(property, value);
58 | ```
59 |
60 | ### Expect
61 | To use styled-components-test-utils with expect, you have to do the following:
62 |
63 | ```js
64 | import expect from 'expect';
65 | import injectStyledUtils from 'styled-components-test-utils/lib/expect';
66 |
67 | injectStyledUtils(expect);
68 |
69 | // ...
70 |
71 | expect(component).toHaveStyleRule(property, value);
72 | ```
73 |
74 | ### Chai
75 | To use styled-components-test-utils with chai, you simply have to import the following:
76 |
77 | ```js
78 | import 'styled-components-test-utils/lib/chai';
79 |
80 | // ...
81 |
82 | expect(component).toHaveStyleRule(property, value);
83 | ```
84 |
85 | ### Jasmine
86 | To use styled-components-test-utils with jasmine, you have to do the following:
87 |
88 | ```js
89 | import injectStyledUtils from 'styled-components-test-utils/lib/jasmine';
90 |
91 | describe('test', () => {
92 | beforeAll(() => {
93 | injectStyledUtils(jasmine);
94 | });
95 |
96 | // ...
97 |
98 | expect(component).toHaveStyleRule(property, value);
99 |
100 | // ...
101 | });
102 | ```
103 |
104 | ## Api
105 | Here is the list of the available APIs. Please, note that in the examples we are using `react-test-renderer` but this library works also with `react-test-renderer/shallow` and `enzyme's shallow`, `enzyme's mount` is not supported yet.
106 |
107 | ### toHaveStyleRule
108 | > expect(tree).toHaveStyleRule(selector, value)
109 | >
110 | > expect({ component, modifier, media }).toHaveStyleRule(selector, value)
111 | >
112 | > expect({ css, props, modifier, media }).toHaveStyleRule(selector, value)
113 |
114 | Asserts that `tree`, `component` or `css` has a rule `selector: value;`. You can also pass some additional parameters to test selectors and media queries, here is an example:
115 |
116 | ```js
117 | const Button = styled.button`
118 | color: blue;
119 |
120 | &:hover {
121 | color: green;
122 | }
123 |
124 | @media screen and (max-width: 600px) {
125 | &:hover {
126 | color: yellow;
127 | }
128 | }
129 | `;
130 | const component = renderer.create();
131 | expect(component).toHaveStyleRule('color', 'blue');
132 |
133 | expect({
134 | component,
135 | modifier: '&:hover', // works also with '> span' or 'html.test &' for example
136 | }).toHaveStyleRule('color', 'green');
137 |
138 | expect({
139 | component,
140 | modifier: '&:hover',
141 | media: 'screen and (max-width: 600px)', // search rule in the given media
142 | }).toHaveStyleRule('color', 'yellow');
143 |
144 | // you can also pass to media an object that should be thought of
145 | // as if it is the current state of a device/browser.
146 | // a type value must be specified, and it can not be "all".
147 | // The returned rule is the one applied under those conditions
148 | expect({
149 | component,
150 | modifier: '&:hover',
151 | media: {
152 | type: 'screen',
153 | width: '500px',
154 | },
155 | }).toHaveStyleRule('color', 'yellow');
156 |
157 | const style = css`
158 | color: ${props => props.primary ? props.theme.primary : 'white'}
159 | `;
160 |
161 | expect({
162 | css: style,
163 | props: {
164 | theme: {
165 | primary: 'purple',
166 | },
167 | },
168 | }).toHaveStyleRule('color', 'purple');
169 | ```
170 |
171 | ### toNotHaveStyleRule
172 | > expect(tree).toNotHaveStyleRule(selector)
173 | >
174 | > expect({ component, modifier, media }).toNotHaveStyleRule(selector)
175 | >
176 | > expect({ css, props, modifier, media }).toNotHaveStyleRule(selector)
177 |
178 | Asserts that `tree`, `component` or `css` has no rule `selector: value;`. You can also pass some additional parameters to test selectors and media queries, as you can do with [toHaveStyleRule](#tohavestylerule), here is an example:
179 |
180 | ```js
181 | const Button = styled.button`
182 | color: blue;
183 | `;
184 | const component = renderer.create();
185 | expect(component).toNotHaveStyleRule('background-color');
186 | ```
187 |
188 | ### toHaveKeyframeRule
189 | > expect(keyframe).toHaveKeyframeRule(keyframeSelector, selector, value)
190 |
191 | Asserts that `keyframe` has a rule `selector: value;` contained in `keyframeSelector`.
192 |
193 | ```js
194 | import steps from './animationSteps';
195 |
196 | const fadeIn = keyframes`
197 | ${steps.map(step => `
198 | ${step.perc}% {
199 | opacity: ${step.opacity};
200 | }
201 | `).join('')}
202 | `;
203 |
204 | expect(fadeIn).toHaveKeyframeRule('0%', 'opacity', '0');
205 | expect(fadeIn).toHaveKeyframeRule('100%', 'opacity', '1');
206 | ```
207 |
208 | ### toHaveComponent
209 | > expect(styledComponent).toHaveComponent(component)
210 |
211 | Asserts that `styledComponent` has component `component`.
212 |
213 | ```js
214 | import Foo from './Foo';
215 |
216 | const Button = styled.button``;
217 | expect(Button).toHaveComponent('button');
218 |
219 | const Bar = Button.withComponent(Foo);
220 | expect(Bar).toHaveComponent(Foo);
221 | ```
222 |
223 |
224 | ### toBeAGlobalStyle
225 | > expect(style).toBeAGlobalStyle()
226 |
227 | Asserts that `style` is a global style.
228 |
229 | ```js
230 | import fontFamily from './fontFamily';
231 |
232 | injectGlobal`
233 | input {
234 | font-family: ${fontFamily};
235 | }
236 | `;
237 |
238 | expect(`
239 | input {
240 | font-family: Roboto;
241 | }
242 | `).toBeAGlobalStyle();
243 | ```
244 |
245 | ### toMatchSnapshot
246 | :warning: Jest only :warning:
247 | > expect(tree).toMatchSnapshot()
248 |
249 | This matcher is used to assert complex selectors or to test your entire component in one go.
250 |
251 | ```js
252 | const tree = renderer.create().toJSON();
253 | expect(tree).toMatchSnapshot();
254 | ```
255 |
256 | If you want to use it with enzyme you also need to install [enzyme-to-json](https://www.npmjs.com/package/enzyme-to-json)
257 |
258 | ```bash
259 | npm install --save-dev enzyme-to-json
260 | ```
261 |
262 | and use it as follows
263 |
264 | ```js
265 | import { shallow } from 'enzyme';
266 | import toJSON from 'enzyme-to-json';
267 |
268 | test('with enzyme', () => {
269 | const wrapper = shallow();
270 | const tree = toJSON(wrapper);
271 | expect(tree).toMatchSnapshot();
272 | })
273 | ```
274 |
275 | or, you can enable it globally in your `package.json`:
276 |
277 | ```json
278 | "jest": {
279 | "snapshotSerializers": [
280 | "enzyme-to-json/serializer"
281 | ]
282 | }
283 | ```
284 |
285 | and use it as follows
286 |
287 | ```js
288 | import { shallow } from 'enzyme';
289 |
290 | test('with enzyme', () => {
291 | const tree = shallow();
292 | expect(tree).toMatchSnapshot();
293 | })
294 | ```
295 |
296 | ## Change Log
297 |
298 | This project adheres to [Semantic Versioning](http://semver.org/).
299 | Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/mbasso/styled-components-test-utils/releases) page.
300 |
301 | ## Authors
302 | **Matteo Basso**
303 | - [github/mbasso](https://github.com/mbasso)
304 | - [@teo_basso](https://twitter.com/teo_basso)
305 |
306 | ## Copyright and License
307 | Copyright for portions of project styled-components-test-utils are held by Michele Bertoli, 2017 as part of project jest-styled-components. All other copyright for project styled-components-test-utils are held by Matteo Basso.
308 |
309 | Copyright (c) 2017, Matteo Basso.
310 |
311 | styled-components-test-utils source code is licensed under the [MIT License](https://github.com/mbasso/styled-components-test-utils/blob/master/LICENSE.md).
312 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styled-components-test-utils",
3 | "version": "1.0.2",
4 | "description": "Test utils for styled-components compatible with jest, expect, chai and jasmine",
5 | "main": "lib/index.js",
6 | "jsnext:main": "es/index.js",
7 | "files": [
8 | "dist",
9 | "lib",
10 | "es",
11 | "src"
12 | ],
13 | "scripts": {
14 | "clean": "rimraf lib dist es .nyc_output",
15 | "test": "npm run test:jest && npm run test:jasmine",
16 | "test:jasmine": "jasmine",
17 | "test:jest": "cross-env BABEL_ENV=commonjs NODE_ENV=test jest --coverage",
18 | "test:watch": "npm run test:jest -- --watchAll",
19 | "test:cov": "nyc report --temp-directory=coverage --reporter=text-lcov | coveralls",
20 | "check:src": "npm run lint && npm run test",
21 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src -d lib",
22 | "build:es": "cross-env BABEL_ENV=es babel src -d es",
23 | "build:umd": "cross-env BABEL_ENV=commonjs NODE_ENV=development webpack --env.prod src/index.js dist/styled-components-test-utils.js",
24 | "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack --env.prod src/index.js dist/styled-components-test-utils.min.js",
25 | "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
26 | "prepublish": "npm run clean && npm run check:src && npm run build",
27 | "flow": "flow check",
28 | "lint": "eslint src test build"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/mbasso/styled-components-test-utils.git"
33 | },
34 | "keywords": [
35 | "react",
36 | "styled",
37 | "styled-components",
38 | "presentational components",
39 | "ui",
40 | "layout",
41 | "test"
42 | ],
43 | "author": "Matteo Basso (https://github.com/mbasso)",
44 | "license": "MIT",
45 | "bugs": {
46 | "url": "https://github.com/mbasso/styled-components-test-utils/issues"
47 | },
48 | "homepage": "https://github.com/mbasso/styled-components-test-utils",
49 | "devDependencies": {
50 | "babel-cli": "6.24.1",
51 | "babel-core": "6.24.1",
52 | "babel-eslint": "7.2.3",
53 | "babel-jest": "20.0.3",
54 | "babel-loader": "7.0.0",
55 | "babel-plugin-transform-decorators-legacy": "1.3.4",
56 | "babel-plugin-transform-es3-member-expression-literals": "6.22.0",
57 | "babel-plugin-transform-es3-property-literals": "6.22.0",
58 | "babel-preset-es2015": "6.24.1",
59 | "babel-preset-flow": "6.23.0",
60 | "babel-preset-react": "6.24.1",
61 | "babel-preset-stage-0": "6.24.1",
62 | "babel-register": "6.24.1",
63 | "chai": "4.0.2",
64 | "chalk": "1.1.3",
65 | "compression-webpack-plugin": "0.4.0",
66 | "coveralls": "2.13.1",
67 | "cross-env": "4.0.0",
68 | "enzyme": "3.1.0",
69 | "enzyme-adapter-react-16": "1.0.2",
70 | "enzyme-to-json": "3.1.4",
71 | "eslint": "3.19.0",
72 | "eslint-config-airbnb": "14.1.0",
73 | "eslint-config-prettier": "1.7.0",
74 | "eslint-plugin-flowtype": "2.32.1",
75 | "eslint-plugin-import": "2.2.0",
76 | "eslint-plugin-jsx-a11y": "4.0.0",
77 | "eslint-plugin-react": "6.10.3",
78 | "expect": "1.20.2",
79 | "flow-bin": "0.45.0",
80 | "istanbul": "0.4.5",
81 | "jasmine": "2.6.0",
82 | "jest": "20.0.4",
83 | "jest-diff": "20.0.3",
84 | "jest-snapshot": "20.0.3",
85 | "jsdom": "10.1.0",
86 | "nyc": "10.3.0",
87 | "prettier": "1.2.2",
88 | "prettier-eslint": "6.1.2",
89 | "react": "16.0.0",
90 | "react-dom": "16.0.0",
91 | "react-test-renderer": "16.0.0",
92 | "rimraf": "2.6.1",
93 | "strip-ansi": "3.0.1",
94 | "styled-components": "3.2.5",
95 | "webpack": "3.0.0"
96 | },
97 | "dependencies": {
98 | "css": "^2.2.1",
99 | "css-mediaquery": "0.1.2"
100 | },
101 | "peerDependencies": {
102 | "chalk": "^1.0.0 || ^2.0.0",
103 | "react-test-renderer": "^15.0.0 || ^16.0.0",
104 | "react": "^15.0.0 || ^16.0.0",
105 | "styled-components": "^2.0.0 || ^3.0.0"
106 | },
107 | "npmName": "styled-components-test-utils",
108 | "npmFileMap": [
109 | {
110 | "basePath": "/dist/",
111 | "files": [
112 | "*.js"
113 | ]
114 | }
115 | ],
116 | "browserify": {
117 | "transform": [
118 | "loose-envify"
119 | ]
120 | },
121 | "jest": {
122 | "snapshotSerializers": [
123 | "enzyme-to-json/serializer"
124 | ],
125 | "testPathIgnorePatterns": [
126 | "/node_modules/",
127 | "/jasmine.spec.js"
128 | ],
129 | "setupFiles": [
130 | "./test/setup.js"
131 | ]
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "test",
3 | "spec_files": [
4 | "**/jasmine.spec.js"
5 | ],
6 | "helpers": [
7 | "../node_modules/babel-register/lib/node.js",
8 | "helpers/**/*.js"
9 | ],
10 | "stopSpecOnExpectationFailure": false,
11 | "random": false
12 | }
13 |
--------------------------------------------------------------------------------
/src/chai.js:
--------------------------------------------------------------------------------
1 | import init from './init';
2 | // eslint-disable-next-line
3 | import chai from 'chai';
4 | import * as matchers from './';
5 |
6 | init();
7 |
8 | Object.keys(matchers).forEach((x) => {
9 | chai.Assertion.addMethod(x, function matcher(...params) {
10 | // eslint-disable-next-line
11 | const test = matchers[x](this._obj, ...params);
12 |
13 | this.assert(
14 | test.pass,
15 | test.message,
16 | );
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/expect.js:
--------------------------------------------------------------------------------
1 | import init from './init';
2 | import * as matchers from './';
3 |
4 | init();
5 |
6 | export default function injectStyledUtils(expect) {
7 | const extension = {};
8 | Object.keys(matchers).forEach((x) => {
9 | extension[x] = function matcher(...params) {
10 | const test = matchers[x](this.actual, ...params);
11 |
12 | expect.assert(
13 | test.pass,
14 | test.message,
15 | );
16 | return this;
17 | };
18 | });
19 |
20 | expect.extend(extension);
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export toBeAGlobalStyle from './matchers/toBeAGlobalStyle';
2 | export toHaveComponent from './matchers/toHaveComponent';
3 | export toHaveKeyframeRule from './matchers/toHaveKeyframeRule';
4 | export toHaveStyleRule from './matchers/toHaveStyleRule';
5 | export toNotHaveStyleRule from './matchers/toNotHaveStyleRule';
6 |
--------------------------------------------------------------------------------
/src/init.js:
--------------------------------------------------------------------------------
1 | import StyleSheet from './utils/styleSheet';
2 | import isOverV2 from './utils/isOverV2';
3 | import isServer from './utils/isServer';
4 |
5 | const init = () => {
6 | if (isOverV2()) {
7 | StyleSheet.reset(isServer());
8 | }
9 | };
10 |
11 | export default init;
12 |
--------------------------------------------------------------------------------
/src/jasmine.js:
--------------------------------------------------------------------------------
1 | import init from './init';
2 | import * as matchers from './';
3 |
4 | init();
5 |
6 | export default function injectStyledUtils(jasmine) {
7 | const extension = {};
8 | Object.keys(matchers).forEach((x) => {
9 | extension[x] = () => ({
10 | compare: matchers[x],
11 | });
12 | });
13 | jasmine.addMatchers(extension);
14 | }
15 |
--------------------------------------------------------------------------------
/src/jest.js:
--------------------------------------------------------------------------------
1 | import init from './init';
2 | import * as matchers from './';
3 | import styleSheetSerializer from './serializers/styleSheetSerializer';
4 |
5 | init();
6 |
7 | const extension = {};
8 | Object.keys(matchers).forEach((x) => {
9 | extension[x] = (...params) => matchers[x](...params);
10 | });
11 |
12 | expect.addSnapshotSerializer(styleSheetSerializer);
13 | expect.extend(extension);
14 |
--------------------------------------------------------------------------------
/src/matchers/toBeAGlobalStyle.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import getCSS from '../utils/getCSS';
3 |
4 | const removeSpaces = string => string.replace(/\s+/g, '');
5 |
6 | const toBeAGlobalStyle = (actual) => {
7 | const css = getCSS();
8 |
9 | return {
10 | message: `Expected global styles to contain:\n\t${chalk.red(actual)}`,
11 | pass: removeSpaces(css).indexOf(removeSpaces(actual)) !== -1,
12 | };
13 | };
14 |
15 | export default toBeAGlobalStyle;
16 |
--------------------------------------------------------------------------------
/src/matchers/toHaveComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import chalk from 'chalk';
3 | import ShallowRenderer from 'react-test-renderer/shallow';
4 |
5 | const getFunctionName = (fun) => {
6 | let ret = fun.toString();
7 | ret = ret.substr('function '.length);
8 | ret = ret.substr(0, ret.indexOf('('));
9 | return ret;
10 | };
11 |
12 | const getComponentName = (component) => {
13 | let name = component;
14 | if (typeof component !== 'string') {
15 | name = getFunctionName(component);
16 | }
17 | return name;
18 | };
19 |
20 | export default (Component, expected) => {
21 | const renderer = new ShallowRenderer();
22 | renderer.render();
23 | const component = renderer.getRenderOutput().type;
24 |
25 | return {
26 | pass: component === expected,
27 | message: `Expected styled-component to have component\n\t${chalk.green(getComponentName(expected))}\nreceived:\n\t${chalk.red(getComponentName(component))}`,
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/src/matchers/toHaveKeyframeRule.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import getCSS from '../utils/getCSS';
3 | import getCodeBlock from '../utils/getCodeBlock';
4 |
5 | const findKeyframeCode = (keyframe) => {
6 | const css = getCSS();
7 | const keyframeMatches = new RegExp(`@keyframes\\s*${keyframe}\\s*{(.*)`).exec(css);
8 |
9 | const trailingCode = keyframeMatches && keyframeMatches[0];
10 | if (!trailingCode) return '';
11 |
12 | return getCodeBlock(trailingCode);
13 | };
14 |
15 | const toHaveKeyframeRule = (received, keyframeSelector, selector, expected) => {
16 | const keyframeCode = findKeyframeCode(received);
17 |
18 | const getMessage = value =>
19 | `Expected keyframe to have ${keyframeSelector} ${selector} matching\n\t${chalk.green(expected)}\nreceived:\n\t${chalk.red(value)}`;
20 |
21 | const error = {
22 | pass: false,
23 | message: `Property not found: ${chalk.red(keyframeSelector)} ${chalk.red(selector)}`,
24 | };
25 |
26 | if (keyframeCode === '') return error;
27 |
28 | const styles = new RegExp(`(?:[^\\d]|^)${keyframeSelector}\\s*{([^}]*)`, 'g').exec(keyframeCode);
29 | const capture = new RegExp(`(?:[^\\-]|^)${selector}\\s*:\\s*([^;]+)`, 'g');
30 |
31 | const matches = styles && styles[1].match(capture);
32 | if (!matches) return error;
33 |
34 | const values = matches.map(r => r.replace(capture, '$1').trim());
35 | const val = values && values[0] && values[values.length - 1];
36 |
37 | return {
38 | message: getMessage(val),
39 | pass: values.some(
40 | v => (expected instanceof RegExp ? v.match(expected) : v === expected),
41 | ),
42 | };
43 | };
44 |
45 | export default toHaveKeyframeRule;
46 |
--------------------------------------------------------------------------------
/src/matchers/toHaveStyleRule.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable one-var, no-useless-escape, no-underscore-dangle */
2 |
3 | import React from 'react';
4 | import chalk from 'chalk';
5 | import ReactTestRenderer from 'react-test-renderer';
6 | import styled from 'styled-components';
7 | import mediaQuery from 'css-mediaquery';
8 | import getCSS from '../utils/getCSS';
9 |
10 | const findClassName = (received) => {
11 | let className = '';
12 |
13 | const component = received.component || received;
14 |
15 | if (typeof component.props === 'function') {
16 | // enzyme 3
17 | className = component.props().className;
18 | } else if (component.props && (component.props.class || component.props.className)) {
19 | // react-test-renderer/shallow
20 | className = component.props.class || component.props.className;
21 | } else if (!component.node && component.constructor && typeof component.toJSON === 'function') {
22 | // react-test-renderer
23 | className = component.toJSON().props.className;
24 | } else if (component.node) {
25 | // enzyme 2
26 | if (component.node.className) {
27 | className = component.node.className;
28 | } else if (component.node.props) {
29 | // enzyme's shallow
30 | className = component.node.props.className;
31 | } else {
32 | // enzyme's mount
33 | const rendered = component.node._reactInternalInstance._renderedComponent;
34 |
35 | className = rendered._instance && rendered._instance.state
36 | ? rendered._instance.state.generatedClassName
37 | : rendered._currentElement.props.className;
38 | }
39 | }
40 |
41 | if (!className) {
42 | className = '';
43 | }
44 | // styled components adds the className on the end.
45 | className = className.split(/\s+/).pop();
46 |
47 | if (received.modifier) {
48 | const modifier = received.modifier.trim();
49 | if (modifier.indexOf('>') === 0) {
50 | // descendant selector
51 | className += ` ${modifier}`;
52 | } else {
53 | // pseudo or contextual selector
54 | className = received.modifier.trim().replace('&', `.${className}`);
55 | }
56 | }
57 | return className;
58 | };
59 |
60 | const getCodeInMedia = (code, media) => {
61 | const newMedia = media.replace(/\(/g, '\\(')
62 | .replace(/\)/g, '\\)')
63 | .replace(/\s/g, '\\s*');
64 |
65 | let mediaMatches;
66 | const mediaRegExp = new RegExp(`@media\\s*${newMedia}\\s*{(.*)}`, 'g');
67 |
68 | const matches = [];
69 | do {
70 | mediaMatches = mediaRegExp.exec(code);
71 | if (mediaMatches && mediaMatches.length >= 2) matches.push(mediaMatches[1]);
72 | } while (mediaMatches);
73 |
74 | return matches.join();
75 | };
76 |
77 | const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
78 |
79 | const getStyleRule = (code, className, selector) => {
80 | const styles = new RegExp(`${escapeRegExp(className)}\\s*{([^}]*)`, 'g').exec(code);
81 | const capture = new RegExp(`(?:[^\\-]|^)${selector}\\s*:\\s*([^;]+)`, 'g');
82 |
83 | const matches = styles && styles[1].match(capture);
84 | if (!matches) return '';
85 |
86 | const values = matches.map(r => r.replace(capture, '$1').trim());
87 | const val = values && values[0] && values[values.length - 1];
88 |
89 | return val || '';
90 | };
91 |
92 | const toHaveStyleRule = (received, selector, expected) => {
93 | let className;
94 |
95 | if (received.css && received.props) {
96 | const Temp = styled.span`${received.css}`;
97 | const component = ReactTestRenderer.create();
98 | className = findClassName({
99 | ...received,
100 | component,
101 | });
102 | } else {
103 | className = findClassName(received);
104 | }
105 |
106 | const css = getCSS();
107 |
108 | const getMessage = value => `Expected ${selector} matching\n\t${chalk.green(expected)}\nreceived:\n\t${chalk.red(value)}`;
109 |
110 | const error = {
111 | pass: false,
112 | message: `Property not found: ${chalk.red(selector)}`,
113 | };
114 |
115 | let code = css;
116 | let val = '';
117 | const { media } = received;
118 |
119 | if (!media) {
120 | val = getStyleRule(code, className, selector);
121 | } else if (typeof media === 'string') {
122 | code = getCodeInMedia(code, media);
123 |
124 | if (code === '') return error;
125 | val = getStyleRule(code, className, selector);
126 | } else if (typeof media === 'object') {
127 | const mediaRegExp = new RegExp('@media([^{]*)?{', 'g');
128 | const medias = [];
129 | let match;
130 | // eslint-disable-next-line
131 | while ((match = mediaRegExp.exec(code)) !== null) {
132 | medias.push(match[1].trim());
133 | }
134 |
135 | let values = medias.filter(x => mediaQuery.match(x, media))
136 | .map(x => getCodeInMedia(code, x))
137 | .map(x => getStyleRule(x, className, selector));
138 |
139 | const mediaMatches = values.length;
140 | const classMatches = code.match(new RegExp(escapeRegExp(className), 'g'));
141 |
142 | if (mediaMatches < classMatches.length - 1) {
143 | // has a rule outside media (first match)
144 | values.unshift(getStyleRule(code, className, selector));
145 | }
146 |
147 | values = values.filter(x => x !== '');
148 |
149 | if (values.length > 0) {
150 | val = values[values.length - 1];
151 | }
152 | }
153 |
154 | if (val === '') return error;
155 |
156 | return {
157 | message: getMessage(val),
158 | pass: expected instanceof RegExp ? val.match(expected) : val === expected,
159 | value: val,
160 | };
161 | };
162 |
163 | export default toHaveStyleRule;
164 |
--------------------------------------------------------------------------------
/src/matchers/toNotHaveStyleRule.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import toHaveStyleRule from './toHaveStyleRule';
3 |
4 | const toNotHaveStyleRule = (received, selector) => {
5 | const { pass, message, value } = toHaveStyleRule(received, selector, '');
6 |
7 | return {
8 | pass: !pass && /^Property not found/.test(message),
9 | message: `Expected ${selector} to not exists but received:\n\t${chalk.red(value)}`,
10 | };
11 | };
12 |
13 | export default toNotHaveStyleRule;
14 |
--------------------------------------------------------------------------------
/src/serializers/styleSheetSerializer.js:
--------------------------------------------------------------------------------
1 | import css from 'css';
2 | import getCSS from '../utils/getCSS';
3 | import getClassNames from '../utils/getClassNames';
4 |
5 | const includesClassNames = (classNames, selectors) =>
6 | classNames.some(className =>
7 | selectors.some(selector => selector.indexOf(className) > -1),
8 | );
9 |
10 | const filterRules = classNames => rule =>
11 | rule.type === 'rule' &&
12 | includesClassNames(classNames, rule.selectors) &&
13 | rule.declarations.length;
14 |
15 | const getAtRules = (ast, filter) =>
16 | ast.stylesheet.rules
17 | .filter(rule => rule.type === 'media' || rule.type === 'supports')
18 | .reduce((acc, atRule) => {
19 | // eslint-disable-next-line
20 | atRule.rules = atRule.rules.filter(filter);
21 |
22 | if (atRule.rules.length) {
23 | return acc.concat(atRule);
24 | }
25 |
26 | return acc;
27 | }, []);
28 |
29 | const getStyle = (classNames) => {
30 | const ast = getCSS(true);
31 | const filter = filterRules(classNames);
32 | const rules = ast.stylesheet.rules.filter(filter);
33 | const atRules = getAtRules(ast, filter);
34 |
35 | ast.stylesheet.rules = rules.concat(atRules);
36 |
37 | return css.stringify(ast);
38 | };
39 |
40 | const replaceClassNames = (classNames, style, code) => {
41 | let index = 0;
42 | return classNames.reduce((acc, className) => {
43 | if (style.indexOf(className) > -1) {
44 | // eslint-disable-next-line
45 | return acc.replace(new RegExp(className, 'g'), `c${index++}`);
46 | }
47 |
48 | return acc.replace(
49 | new RegExp(`(className="[^"]*?)${className}\\s?([^"]*")`, 'g'),
50 | '$1$2',
51 | );
52 | }, `${style}${style ? '\n\n' : ''}${code}`);
53 | };
54 |
55 | const styleSheetSerializer = {
56 | test(val) {
57 | return (
58 | val && !val.withStyle && val.$$typeof === Symbol.for('react.test.json')
59 | );
60 | },
61 |
62 | print(val, print) {
63 | // eslint-disable-next-line
64 | val.withStyle = true;
65 |
66 | const classNames = getClassNames(val);
67 | const style = getStyle(classNames);
68 | const code = print(val);
69 |
70 | return classNames.length ? replaceClassNames(classNames, style, code) : code;
71 | },
72 | };
73 |
74 | export default styleSheetSerializer;
75 |
--------------------------------------------------------------------------------
/src/utils/getCSS.js:
--------------------------------------------------------------------------------
1 | import { ServerStyleSheet } from 'styled-components';
2 | import css from 'css';
3 |
4 | import StyleSheet from './styleSheet';
5 | import isOverV2 from './isOverV2';
6 | import isServer from './isServer';
7 |
8 | const STYLE_TAGS_REGEXP = /