├── .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 | [](https://travis-ci.org/rwieruch/visual-regression-testing-react-storybook) [](https://coveralls.io/github/rwieruch/visual-regression-testing-react-storybook?branch=master) [](https://slack-the-road-to-learn-react.wieruch.com/) [](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 | };
--------------------------------------------------------------------------------