├── .babelrc ├── .cache └── 325c8f456729b912b0d2134054eb7448-dfeeb2271cc2857eb0a45a5003c8bbee ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── .storybook ├── addons.js └── config.js ├── .travis.yml ├── README.md ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ └── App.e2e.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── dist └── index.html ├── package-lock.json ├── package.json ├── src ├── App.js ├── App.snapshot.js ├── App.spec.js ├── Checkbox │ ├── index.js │ └── stories.js ├── __image_snapshots__ │ ├── app-snapshot-js-storyshots-checkbox-with-checked-1-snap.png │ ├── app-snapshot-js-storyshots-checkbox-with-iphone-6-plus-1-snap.png │ ├── app-snapshot-js-storyshots-checkbox-with-medium-1-snap.png │ └── app-snapshot-js-storyshots-checkbox-with-unchecked-1-snap.png ├── __snapshots__ │ └── App.snapshot.js.snap └── index.js ├── test ├── dom.js ├── helpers.js ├── jest.config.json └── jest.setup.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "env": { 7 | "test": { 8 | "plugins": ["require-context-hook"] 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /.cache/325c8f456729b912b0d2134054eb7448-dfeeb2271cc2857eb0a45a5003c8bbee: -------------------------------------------------------------------------------- 1 | {"value":{"success":true,"data":{"latest":{"version":"4.0.0","info":{"plain":"- upgrade webpack & babel to latest\n- new addParameters and third argument to .add to pass data to addons\n- added the ability to theme storybook\n- improved ui for mobile devices\n- improved performance of addon-knobs"}}},"time":1541076861531},"type":"Object"} -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rwieruch 4 | patreon: # rwieruch 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | import '@storybook/addon-actions/register'; 3 | import '@storybook/addon-viewport/register'; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/react'; 2 | import { withKnobs } from '@storybook/addon-knobs'; 3 | import { configureViewport } from '@storybook/addon-viewport'; 4 | 5 | // pick all stories.js files within the src/ folder 6 | const req = require.context('../src', true, /stories\.js$/); 7 | 8 | addDecorator(withKnobs); 9 | 10 | const viewports = { 11 | small: { 12 | name: 'small', 13 | styles: { 14 | width: '320px', 15 | height: '240px', 16 | }, 17 | }, 18 | medium: { 19 | name: 'medium', 20 | styles: { 21 | width: '800px', 22 | height: '600px', 23 | }, 24 | }, 25 | large: { 26 | name: 'large', 27 | styles: { 28 | width: '1280px', 29 | height: '1024px', 30 | }, 31 | }, 32 | }; 33 | 34 | configureViewport({ 35 | viewports, 36 | }); 37 | 38 | function loadStories() { 39 | req.keys().forEach(filename => req(filename)); 40 | } 41 | 42 | configure(loadStories, module); 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | script: 10 | - npm run test:unit -- --coverage && npm run test:cypress 11 | 12 | after_script: 13 | - COVERALLS_REPO_TOKEN=$coveralls_repo_token npm run coveralls -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # visual-regression-testing-react-storybook 2 | 3 | [![Build Status](https://travis-ci.org/rwieruch/visual-regression-testing-react-storybook.svg?branch=master)](https://travis-ci.org/rwieruch/visual-regression-testing-react-storybook) [![Coverage Status](https://coveralls.io/repos/github/rwieruch/visual-regression-testing-react-storybook/badge.svg?branch=master)](https://coveralls.io/github/rwieruch/visual-regression-testing-react-storybook?branch=master) [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) [![Greenkeeper badge](https://badges.greenkeeper.io/rwieruch/visual-regression-testing-react-storybook.svg)](https://greenkeeper.io/) 4 | 5 | A React application which implements visual regression tests by using Jest and React Storybook. Read more about it: [Visual Regression Testing and React Storybook](https://www.robinwieruch.de/visual-regression-testing-react-storybook/) 6 | 7 | ## Features 8 | 9 | * React 16 10 | * Webpack 4 11 | * Babel 12 | * Staging ES Next Features 13 | * Hot Module Replacement 14 | * Testing 15 | * Mocha 16 | * Chai 17 | * Enzyme 18 | * Sinon 19 | * Jest 20 | * Unit/Integratio Test (Chai, Enzyme) Watcher 21 | * Snapshot Test (Jest) Watcher 22 | * Travis CI 23 | * Coveralls.io 24 | * Storybook 4 25 | * Knobs 26 | * Actions 27 | * Storyshots 28 | * Imageshots 29 | * Viewport 30 | 31 | ## Installation 32 | 33 | * `git clone git@github.com:rwieruch/visual-regression-testing-react-storybook.git` 34 | * cd visual-regression-testing-react-storybook 35 | * npm install 36 | * npm start 37 | * visit `http://localhost:8080/` 38 | 39 | ## Running Storybook 40 | 41 | * npm run storybook 42 | 43 | ## Running Visual Regression Tests 44 | 45 | * npm run storybook 46 | * npm run test:snapshot 47 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "video": false, 3 | "baseUrl": "http://localhost:8080" 4 | } -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/integration/App.e2e.js: -------------------------------------------------------------------------------- 1 | describe('App E2E', () => { 2 | it('should have a header', () => { 3 | cy.visit('/'); 4 | 5 | cy.get('h1') 6 | .should('have.text', 'My Counter'); 7 | }); 8 | 9 | it('should increment and decrement the counter', () => { 10 | cy.visit('/'); 11 | 12 | cy.get('p') 13 | .should('have.text', '0'); 14 | 15 | cy.contains('Increment').click(); 16 | cy.get('p') 17 | .should('have.text', '1'); 18 | 19 | cy.contains('Increment').click(); 20 | cy.get('p') 21 | .should('have.text', '2'); 22 | 23 | cy.contains('Decrement').click(); 24 | cy.get('p') 25 | .should('have.text', '1'); 26 | }); 27 | }); -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The Minimal React Webpack Babel Setup 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visual-regression-testing-react-storybook", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config ./webpack.config.js --mode development", 8 | "test:unit": "mocha --require @babel/register --require ./test/helpers.js --require ./test/dom.js --require ignore-styles 'src/**/*.spec.js'", 9 | "test:unit:watch": "npm run test:unit -- --watch", 10 | "test:snapshot": "jest --config ./test/jest.config.json", 11 | "test:snapshot:watch": "npm run test:snapshot -- --watch", 12 | "test:cypress": "start-server-and-test start http://localhost:8080 cypress", 13 | "cypress": "cypress run", 14 | "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", 15 | "storybook": "start-storybook -p 9001 -c .storybook" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@babel/core": "^7.1.2", 22 | "@babel/node": "^7.0.0", 23 | "@babel/preset-env": "^7.1.0", 24 | "@babel/preset-react": "^7.0.0", 25 | "@babel/register": "^7.0.0", 26 | "@storybook/addon-actions": "^4.0.2", 27 | "@storybook/addon-knobs": "^4.0.2", 28 | "@storybook/addon-storyshots": "^4.0.2", 29 | "@storybook/addon-storyshots-puppeteer": "^4.0.2", 30 | "@storybook/addon-viewport": "^4.0.2", 31 | "@storybook/react": "^4.0.2", 32 | "babel-core": "^7.0.0-bridge.0", 33 | "babel-jest": "^23.6.0", 34 | "babel-loader": "^8.0.4", 35 | "babel-plugin-require-context-hook": "^1.0.0", 36 | "chai": "^4.2.0", 37 | "coveralls": "^3.0.2", 38 | "cypress": "^3.1.0", 39 | "enzyme": "^3.7.0", 40 | "enzyme-adapter-react-16": "^1.6.0", 41 | "ignore-styles": "^5.0.1", 42 | "jest": "^23.6.0", 43 | "jsdom": "^13.0.0", 44 | "mocha": "^5.2.0", 45 | "react-hot-loader": "^4.3.12", 46 | "react-test-renderer": "^16.6.0", 47 | "sinon": "^7.1.1", 48 | "start-server-and-test": "^1.7.7", 49 | "webpack": "^4.23.1", 50 | "webpack-cli": "^3.1.2", 51 | "webpack-dev-server": "^3.1.10" 52 | }, 53 | "dependencies": { 54 | "axios": "^0.18.0", 55 | "react": "^16.6.0", 56 | "react-dom": "^16.6.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | 4 | export const doIncrement = (prevState) => ({ 5 | counter: prevState.counter + 1, 6 | }); 7 | 8 | export const doDecrement = (prevState) => ({ 9 | counter: prevState.counter - 1, 10 | }); 11 | 12 | class App extends Component { 13 | constructor() { 14 | super(); 15 | 16 | this.state = { 17 | counter: 0, 18 | asyncCounters: null, 19 | }; 20 | 21 | this.onIncrement = this.onIncrement.bind(this); 22 | this.onDecrement = this.onDecrement.bind(this); 23 | } 24 | 25 | componentDidMount() { 26 | axios.get('http://mydomain/counter') 27 | .then(counter => this.setState({ asyncCounters: counter })) 28 | .catch(error => console.log(error)); 29 | } 30 | 31 | onIncrement() { 32 | this.setState(doIncrement); 33 | } 34 | 35 | onDecrement() { 36 | this.setState(doDecrement); 37 | } 38 | 39 | render() { 40 | const { counter } = this.state; 41 | 42 | return ( 43 |
44 |

My Counter

45 | 46 | 47 | 53 | 54 | 60 |
61 | ); 62 | } 63 | } 64 | 65 | export const Counter = ({ counter }) => 66 |

{counter}

67 | 68 | export default App; -------------------------------------------------------------------------------- /src/App.snapshot.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import App, { Counter } from './App'; 5 | 6 | describe('App Snapshot', () => { 7 | test('renders', () => { 8 | const component = renderer.create( 9 | 10 | ); 11 | let tree = component.toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | }); 15 | 16 | describe('Counter Snapshot', () => { 17 | test('renders', () => { 18 | const component = renderer.create( 19 | 20 | ); 21 | let tree = component.toJSON(); 22 | expect(tree).toMatchSnapshot(); 23 | }); 24 | }); -------------------------------------------------------------------------------- /src/App.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import App, { doIncrement, doDecrement, Counter } from './App'; 4 | 5 | describe('Local State', () => { 6 | it('should increment the counter in state', () => { 7 | const state = { counter: 0 }; 8 | const newState = doIncrement(state); 9 | 10 | expect(newState.counter).to.equal(1); 11 | }); 12 | 13 | it('should decrement the counter in state', () => { 14 | const state = { counter: 0 }; 15 | const newState = doDecrement(state); 16 | 17 | expect(newState.counter).to.equal(-1); 18 | }); 19 | }); 20 | 21 | describe('App Component', () => { 22 | const result = [3, 5, 9]; 23 | const promise = Promise.resolve(result); 24 | 25 | before(() => { 26 | sinon.stub(axios, 'get').withArgs('http://mydomain/counter').returns(promise); 27 | }); 28 | 29 | after(() => { 30 | axios.get.restore(); 31 | }); 32 | 33 | it('renders the Counter wrapper', () => { 34 | const wrapper = shallow(); 35 | expect(wrapper.find(Counter)).to.have.length(1); 36 | }); 37 | 38 | it('passes all props to Counter wrapper', () => { 39 | const wrapper = shallow(); 40 | let counterWrapper = wrapper.find(Counter); 41 | 42 | expect(counterWrapper.props().counter).to.equal(0); 43 | 44 | wrapper.setState({ counter: -1 }); 45 | 46 | counterWrapper = wrapper.find(Counter); 47 | expect(counterWrapper.props().counter).to.equal(-1); 48 | }); 49 | 50 | it('increments the counter', () => { 51 | const wrapper = shallow(); 52 | 53 | wrapper.setState({ counter: 0 }); 54 | wrapper.find('button').at(0).simulate('click'); 55 | 56 | expect(wrapper.state().counter).to.equal(1); 57 | }); 58 | 59 | it('decrements the counter', () => { 60 | const wrapper = shallow(); 61 | 62 | wrapper.setState({ counter: 0 }); 63 | wrapper.find('button').at(1).simulate('click'); 64 | 65 | expect(wrapper.state().counter).to.equal(-1); 66 | }); 67 | 68 | it('calls componentDidMount', () => { 69 | sinon.spy(App.prototype, 'componentDidMount'); 70 | 71 | const wrapper = mount(); 72 | expect(App.prototype.componentDidMount.calledOnce).to.equal(true); 73 | }); 74 | 75 | it('fetches async counters', () => { 76 | const wrapper = shallow(); 77 | 78 | expect(wrapper.state().asyncCounters).to.equal(null); 79 | 80 | promise.then(() => { 81 | expect(wrapper.state().asyncCounters).to.equal(result); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Checkbox extends Component { 4 | constructor(props) { 5 | super(props); 6 | 7 | this.handleChange = this.handleChange.bind(this); 8 | } 9 | 10 | handleChange(event) { 11 | this.props.onCheckboxChange(event.target.checked); 12 | }; 13 | 14 | render() { 15 | const { value, children } = this.props; 16 | 17 | return ( 18 | 22 | ); 23 | } 24 | } 25 | 26 | export default Checkbox; 27 | -------------------------------------------------------------------------------- /src/Checkbox/stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { text } from '@storybook/addon-knobs'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { Viewport } from '@storybook/addon-viewport'; 6 | import Checkbox from './'; 7 | 8 | class CheckboxStateful extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | value: this.props.value, 14 | }; 15 | 16 | this.onCheckboxChange = this.onCheckboxChange.bind(this); 17 | } 18 | 19 | onCheckboxChange(value) { 20 | this.setState({ value }); 21 | 22 | this.props.onCheckboxChange(value); 23 | }; 24 | 25 | render() { 26 | return ( 27 | 31 | {this.props.children} 32 | 33 | ); 34 | } 35 | } 36 | 37 | storiesOf('Checkbox', module) 38 | .add('with medium', () => { 39 | const value = true; 40 | const children = text('label', 'My Checkbox Label'); 41 | const onCheckboxChange = action('toggle'); 42 | 43 | return ( 44 | 45 | 46 | {children} 47 | 48 | 49 | ); 50 | }) 51 | .add('with iphone6 Plus', () => { 52 | const value = true; 53 | const children = text('label', 'My Checkbox Label'); 54 | const onCheckboxChange = action('toggle'); 55 | 56 | return ( 57 | 58 | 59 | {children} 60 | 61 | 62 | ); 63 | }) 64 | .add('with checked', () => { 65 | const value = true; 66 | const children = text('label', 'My Checkbox Label'); 67 | const onCheckboxChange = action('toggle'); 68 | 69 | return ( 70 | 71 | {children} 72 | 73 | ); 74 | }) 75 | .add('with unchecked', () => { 76 | const value = false; 77 | const children = text('label', 'My Checkbox Label'); 78 | const onCheckboxChange = action('toggle'); 79 | 80 | return ( 81 | 82 | {children} 83 | 84 | ); 85 | }); 86 | -------------------------------------------------------------------------------- /src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-checked-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/visual-regression-testing-react-storybook/c3597eda868de2c392ca8545f1e1316fbdb1cdc1/src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-checked-1-snap.png -------------------------------------------------------------------------------- /src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-iphone-6-plus-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/visual-regression-testing-react-storybook/c3597eda868de2c392ca8545f1e1316fbdb1cdc1/src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-iphone-6-plus-1-snap.png -------------------------------------------------------------------------------- /src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-medium-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/visual-regression-testing-react-storybook/c3597eda868de2c392ca8545f1e1316fbdb1cdc1/src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-medium-1-snap.png -------------------------------------------------------------------------------- /src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-unchecked-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/visual-regression-testing-react-storybook/c3597eda868de2c392ca8545f1e1316fbdb1cdc1/src/__image_snapshots__/app-snapshot-js-storyshots-checkbox-with-unchecked-1-snap.png -------------------------------------------------------------------------------- /src/__snapshots__/App.snapshot.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App Snapshot renders 1`] = ` 4 |
5 |

6 | My Counter 7 |

8 |

9 | 0 10 |

11 | 17 | 23 |
24 | `; 25 | 26 | exports[`Counter Snapshot renders 1`] = ` 27 |

28 | 1 29 |

30 | `; 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('app') 9 | ); 10 | 11 | module.hot.accept(); -------------------------------------------------------------------------------- /test/dom.js: -------------------------------------------------------------------------------- 1 | import { JSDOM } from 'jsdom'; 2 | 3 | const { window } = new JSDOM(''); 4 | 5 | function copyProps(src, target) { 6 | const props = Object.getOwnPropertyNames(src) 7 | .filter(prop => typeof target[prop] === 'undefined') 8 | .reduce((result, prop) => ({ 9 | ...result, 10 | [prop]: Object.getOwnPropertyDescriptor(src, prop), 11 | }), {}); 12 | Object.defineProperties(target, props); 13 | } 14 | 15 | global.window = window; 16 | global.document = window.document; 17 | global.navigator = { 18 | userAgent: 'node.js', 19 | }; 20 | 21 | copyProps(window, global); -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | import { expect } from 'chai'; 3 | import { mount, render, shallow, configure} from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | 6 | configure({ adapter: new Adapter() }); 7 | 8 | global.expect = expect; 9 | 10 | global.sinon = sinon; 11 | 12 | global.mount = mount; 13 | global.render = render; 14 | global.shallow = shallow; -------------------------------------------------------------------------------- /test/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRegex": "((\\.|/*.)(snapshot))\\.js?$", 3 | "rootDir": "..", 4 | "setupTestFrameworkScriptFile": "/test/jest.setup.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/jest.setup.js: -------------------------------------------------------------------------------- 1 | import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; 2 | import initStoryshots from '@storybook/addon-storyshots'; 3 | import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; 4 | 5 | registerRequireContextHook(); 6 | initStoryshots({ 7 | suite: 'Storyshots', 8 | test: imageSnapshot({ 9 | storybookUrl: 'http://localhost:9001', 10 | }), 11 | }); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: [ 5 | 'react-hot-loader/patch', 6 | './src/index.js' 7 | ], 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.(js|jsx)$/, 12 | exclude: /node_modules/, 13 | use: ['babel-loader'] 14 | } 15 | ] 16 | }, 17 | resolve: { 18 | extensions: ['*', '.js', '.jsx'] 19 | }, 20 | output: { 21 | path: __dirname + '/dist', 22 | publicPath: '/', 23 | filename: 'bundle.js' 24 | }, 25 | plugins: [ 26 | new webpack.HotModuleReplacementPlugin() 27 | ], 28 | devServer: { 29 | contentBase: './dist', 30 | hot: true 31 | } 32 | }; --------------------------------------------------------------------------------