├── .babelrc
├── index.js
├── setup.js
├── README.md
├── package.json
├── components.js
└── components.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {BeerListContainer} from './components';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.querySelector('#root'));
8 |
--------------------------------------------------------------------------------
/setup.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with TDD in React
2 |
3 | This is the companion repository for the Getting Started with TDD in React tutorial at [Semaphore CI](https://semaphoreci.com/).
4 |
5 | Follow along with the tutorial for maximum benefit, and clone this repo to check your work.
6 |
7 | To get started:
8 |
9 | * Clone this repo.
10 | * Run `npm install`
11 | * Run `npm install -g quik`
12 | * Run `npm install -g mocha`
13 |
14 | Then:
15 |
16 | * `npm test` to run the tests
17 | * `quik` to run the app
18 |
19 | The commit history follows the flow of the tutorial. If you get stuck at any point, run `git log` and then `git checkout ` the commit closest to where you are.
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tdd-demo",
3 | "version": "1.0.0",
4 | "description": "Getting Started with TDD in React",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha --require setup.js *.spec.js",
8 | "test:watch": "npm run test -- --watch"
9 | },
10 | "keywords": [
11 | "react",
12 | "tdd"
13 | ],
14 | "author": "Dave Ceddia",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "babel-core": "^6.8.0",
18 | "babel-preset-es2015": "^6.6.0",
19 | "babel-preset-react": "^6.5.0",
20 | "chai": "^3.5.0",
21 | "enzyme": "^2.3.0",
22 | "sinon": "^1.17.4"
23 | },
24 | "dependencies": {
25 | "react": "^15.0.2",
26 | "react-addons-test-utils": "^15.0.2",
27 | "react-dom": "^15.0.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/components.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export class BeerListContainer extends Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | beers: []
8 | };
9 | this.addItem = this.addItem.bind(this);
10 | }
11 |
12 | addItem(name) {
13 | this.setState({
14 | beers: [].concat(this.state.beers).concat([name])
15 | });
16 | }
17 |
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | export class InputArea extends Component {
29 | constructor(props) {
30 | super(props);
31 | this.state = {
32 | text: ''
33 | };
34 | this.setText = this.setText.bind(this);
35 | this.handleClick = this.handleClick.bind(this);
36 | }
37 |
38 | setText(event) {
39 | this.setState({text: event.target.value});
40 | }
41 |
42 | handleClick() {
43 | this.props.onSubmit(this.state.text);
44 | }
45 |
46 | render() {
47 | return (
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export class BeerList extends Component {
57 | render() {
58 | return (
59 |
60 | {this.props.items.map((item, index) => (
61 | - {item}
62 | ))}
63 |
64 | );
65 | }
66 | }
67 | BeerList.defaultProps = { items: [] };
68 |
--------------------------------------------------------------------------------
/components.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { expect } from 'chai';
3 | import { spy } from 'sinon';
4 | import { shallow, mount } from 'enzyme';
5 | import { BeerListContainer } from './components';
6 | import { InputArea, BeerList } from './components';
7 |
8 | describe('BeerListContainer', () => {
9 | it('should render InputArea and BeerList', () => {
10 | const wrapper = shallow();
11 | expect(wrapper.containsAllMatchingElements([
12 | ,
13 |
14 | ])).to.equal(true);
15 | });
16 |
17 | it('should start with an empty list', () => {
18 | const wrapper = shallow();
19 | expect(wrapper.state('beers')).to.eql([]);
20 | });
21 |
22 | it('adds items to the list', () => {
23 | const wrapper = shallow();
24 | wrapper.instance().addItem('Sam Adams');
25 | expect(wrapper.state('beers')).to.eql(['Sam Adams']);
26 | });
27 |
28 | it('passes addItem to InputArea', () => {
29 | const wrapper = shallow();
30 | const inputArea = wrapper.find(InputArea);
31 | const addItem = wrapper.instance().addItem;
32 | expect(inputArea.prop('onSubmit')).to.eql(addItem);
33 | });
34 |
35 | it('passes a bound addItem function to InputArea', () => {
36 | const wrapper = shallow();
37 | const inputArea = wrapper.find(InputArea);
38 | inputArea.prop('onSubmit')('Sam Adams');
39 | expect(wrapper.state('beers')).to.eql(['Sam Adams']);
40 | });
41 |
42 | it('renders the items', () => {
43 | const wrapper = mount();
44 | wrapper.instance().addItem('Sam Adams');
45 | wrapper.instance().addItem('Resin');
46 | expect(wrapper.find('li').length).to.equal(2);
47 | });
48 | });
49 |
50 | describe('InputArea', () => {
51 | it('should contain an input and a button', () => {
52 | const wrapper = shallow();
53 | expect(wrapper.containsAllMatchingElements([
54 | ,
55 |
56 | ])).to.equal(true);
57 | });
58 |
59 | it('should accept input', () => {
60 | const wrapper = mount();
61 | const input = wrapper.find('input');
62 | input.simulate('change', {target: { value: 'Resin' }});
63 | expect(wrapper.state('text')).to.equal('Resin');
64 | expect(input.prop('value')).to.equal('Resin');
65 | });
66 |
67 | it('should call onSubmit when Add is clicked', () => {
68 | const addItemSpy = spy();
69 | const wrapper = shallow();
70 | wrapper.setState({text: 'Octoberfest'});
71 | const addButton = wrapper.find('button');
72 | addButton.simulate('click');
73 | expect(addItemSpy.calledOnce).to.equal(true);
74 | expect(addItemSpy.calledWith('Octoberfest')).to.equal(true);
75 | });
76 | });
77 |
78 | describe('BeerList', () => {
79 | it('should render zero items', () => {
80 | const wrapper = shallow();
81 | expect(wrapper.find('li')).to.have.length(0);
82 | });
83 |
84 | it('should render undefined items', () => {
85 | const wrapper = shallow();
86 | expect(wrapper.find('li')).to.have.length(0);
87 | });
88 |
89 | it('should render the items', () => {
90 | const items = ['Sam Adams', 'Resin', 'Octoberfest'];
91 | const wrapper = shallow();
92 | expect(wrapper.find('li')).to.have.length(3);
93 | });
94 | });
95 |
--------------------------------------------------------------------------------