├── .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 | 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 | --------------------------------------------------------------------------------