├── .babelrc
├── README.md
├── lib
├── main.jsx
├── avatar.jsx
├── email.jsx
└── gravatar.jsx
├── test
├── helpers
│ └── browser.js
├── avatar.spec.js
├── email.spec.js
└── gravatar.spec.js
├── index.html
├── .gitignore
├── LICENSE
├── webpack.config.js
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["airbnb", "es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-testing-components-enzyme
2 | Sample repo for a blog post on testing react components with enzyme
3 |
4 | ## installation
5 | `npm i`
6 |
7 | ## start
8 | `npm run dev:hot`
9 |
10 | ## run tests
11 | `npm test`
--------------------------------------------------------------------------------
/lib/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render} from 'react-dom';
3 | import Gravatar from './gravatar';
4 |
5 | const appRoot = document.getElementById('root');
6 |
7 | render(
8 | ,
9 | appRoot
10 | );
11 |
--------------------------------------------------------------------------------
/lib/avatar.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react';
2 |
3 |
4 | export default class Avatar extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 | {this.props.email}
10 |
11 |

12 |
13 | );
14 | }
15 | }
16 |
17 | Avatar.propTypes = {
18 | email: PropTypes.string,
19 | src: PropTypes.string,
20 | };
21 |
--------------------------------------------------------------------------------
/test/helpers/browser.js:
--------------------------------------------------------------------------------
1 | require('babel-register')();
2 |
3 | var jsdom = require('jsdom').jsdom;
4 |
5 | var exposedProperties = ['window', 'navigator', 'document'];
6 |
7 | global.document = jsdom('');
8 | global.window = document.defaultView;
9 | Object.keys(document.defaultView).forEach((property) => {
10 | if (typeof global[property] === 'undefined') {
11 | exposedProperties.push(property);
12 | global[property] = document.defaultView[property];
13 | }
14 | });
15 |
16 | global.navigator = {
17 | userAgent: 'node.js'
18 | };
19 |
20 | documentRef = document;
21 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fun with react testing!
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Loading!
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/avatar.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, shallow } from 'enzyme';
3 | import {expect} from 'chai';
4 |
5 | import Avatar from '../lib/avatar';
6 |
7 | describe('', function () {
8 | it('should have an image to display the gravatar', function () {
9 | const wrapper = shallow();
10 | expect(wrapper.find('img')).to.have.length(1);
11 | });
12 |
13 | it('should have props for email and src', function () {
14 | const wrapper = shallow();
15 | expect(wrapper.props().email).to.be.defined;
16 | expect(wrapper.props().src).to.be.defined;
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/lib/email.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react';
2 |
3 | export default class Email extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 | render() {
8 | return (
9 |
10 |
13 |
14 |
15 | );
16 | }
17 | }
18 |
19 | Email.propTypes = {
20 | handleEmailChange: PropTypes.func,
21 | fetchGravatar: PropTypes.func,
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | #ignore dist
36 | dist
37 |
--------------------------------------------------------------------------------
/test/email.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, shallow } from 'enzyme';
3 | import {expect} from 'chai';
4 |
5 | import Email from '../lib/email';
6 |
7 | describe('', function () {
8 | it('should have an input for the email', function () {
9 | const wrapper = shallow();
10 | expect(wrapper.find('input')).to.have.length(1);
11 | });
12 |
13 | it('should have a button', function () {
14 | const wrapper = shallow();
15 | expect(wrapper.find('button')).to.have.length(1);
16 | });
17 |
18 | it('should have props for handleEmailChange and fetchGravatar', function () {
19 | const wrapper = shallow();
20 | expect(wrapper.props().handleEmailChange).to.be.defined;
21 | expect(wrapper.props().fetchGravatar).to.be.defined;
22 | });
23 |
24 | });
25 |
--------------------------------------------------------------------------------
/lib/gravatar.jsx:
--------------------------------------------------------------------------------
1 | import React, {propTypes} from 'react';
2 | import md5 from 'md5';
3 |
4 | import Avatar from './avatar';
5 | import Email from './email';
6 |
7 | export default class Gravatar extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | email: 'someone@example.com',
12 | src: 'http://placehold.it/200x200'
13 | }
14 | }
15 |
16 | updateGravatar() {
17 | this.setState({
18 | src: `http://gravatar.com/avatar/${md5(this.state.email)}?s=200`
19 | });
20 | }
21 |
22 | updateEmail(event) {
23 | this.setState({email: event.target.value});
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
Avatar for:
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Mark Thomas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const BellOnBundlerErrorPlugin = require('bell-on-bundler-error-plugin');
4 |
5 | // env
6 | const buildDirectory = './dist/';
7 |
8 | module.exports = {
9 | externals: {
10 | 'cheerio': 'window',
11 | 'react/lib/ExecutionEnvironment': true,
12 | 'react/lib/ReactContext': true,
13 | },
14 | entry: './lib/main.jsx',
15 | devServer: {
16 | hot: true,
17 | inline: true,
18 | port: 7700,
19 | historyApiFallback: true,
20 | },
21 | resolve: {
22 | extensions: ['', '.js', '.jsx'],
23 | },
24 | output: {
25 | path: path.resolve(buildDirectory),
26 | filename: 'app.js',
27 | publicPath: 'http://localhost:7700/dist',
28 | },
29 | module: {
30 | loaders: [{
31 | test: /\.jsx?$/,
32 | exclude: /bower_components/,
33 | loader: 'babel',
34 | query: {
35 | presets: ['react', 'es2015', 'stage-0'],
36 | },
37 | }],
38 | },
39 | plugins: [
40 | new BellOnBundlerErrorPlugin(),
41 | new webpack.optimize.OccurenceOrderPlugin(true),
42 | // new webpack.optimize.DedupePlugin(),
43 | // new webpack.optimize.UglifyJsPlugin(),
44 | ],
45 | };
46 |
--------------------------------------------------------------------------------
/test/gravatar.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, shallow } from 'enzyme';
3 | import {expect} from 'chai';
4 | import md5 from 'md5';
5 |
6 | import Gravatar from '../lib/gravatar';
7 | import Avatar from '../lib/avatar';
8 | import Email from '../lib/email';
9 |
10 | describe('', () => {
11 | it('contains an component', function () {
12 | const wrapper = mount();
13 | expect(wrapper.find(Avatar)).to.have.length(1);
14 | });
15 |
16 | it('contains an component', function () {
17 | const wrapper = mount();
18 | expect(wrapper.find(Email)).to.have.length(1);
19 | });
20 |
21 | it('should have an initial email state', function () {
22 | const wrapper = mount();
23 | expect(wrapper.state().email).to.equal('someone@example.com');
24 | });
25 |
26 | it('should have an initial src state', function () {
27 | const wrapper = mount();
28 | expect(wrapper.state().src).to.equal('http://placehold.it/200x200');
29 | });
30 |
31 | it('should have an initial src state', function () {
32 | const wrapper = mount();
33 | expect(wrapper.state().src).to.equal('http://placehold.it/200x200');
34 | });
35 |
36 | it('should update the src state on clicking fetch', function () {
37 | const wrapper = mount();
38 | wrapper.setState({ email: 'markthethomas@gmail.com' });
39 | wrapper.find('button').simulate('click');
40 |
41 | expect(wrapper.state('email')).to.equal('markthethomas@gmail.com');
42 | expect(wrapper.state('src')).to.equal(`http://gravatar.com/avatar/${md5('markthethomas@gmail.com')}?s=200`);
43 | });
44 |
45 | });
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-testing-components-enzyme",
3 | "version": "1.0.0",
4 | "description": "Sample repo for a blog post on testing react components with enzyme",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha -w test/helpers/browser.js test/*.spec.js",
8 | "dev:hot": "webpack-dev-server --hot --inline --progress --colors --watch --display-error-details --display-cached --content-base ./"
9 | },
10 | "ava": {
11 | "require": [
12 | "./test/helpers/setup-browser-env.js"
13 | ]
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/markthethomas/react-testing-components-enzyme.git"
18 | },
19 | "author": "Mark Thomas ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/markthethomas/react-testing-components-enzyme/issues"
23 | },
24 | "homepage": "https://github.com/markthethomas/react-testing-components-enzyme#readme",
25 | "devDependencies": {
26 | "ava": "^0.12.0",
27 | "babel-core": "^6.6.4",
28 | "babel-loader": "^6.2.4",
29 | "babel-preset-airbnb": "^1.1.1",
30 | "babel-preset-es2015": "^6.6.0",
31 | "babel-preset-react": "^6.5.0",
32 | "babel-preset-stage-0": "^6.5.0",
33 | "bell-on-bundler-error-plugin": "^1.0.8",
34 | "chai": "^3.5.0",
35 | "enzyme": "^2.0.0",
36 | "jsdom": "^8.1.0",
37 | "mocha": "^3.0.2",
38 | "react-addons-test-utils": "^0.14.7",
39 | "sinon": "^1.17.3",
40 | "webpack": "^1.12.14",
41 | "webpack-dev-server": "^1.14.1"
42 | },
43 | "dependencies": {
44 | "axios": "^0.9.1",
45 | "md5": "^2.0.0",
46 | "react": "^0.14.7",
47 | "react-dom": "^0.14.7"
48 | },
49 | "engines" : {
50 | "node" : ">=6.2.2"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------