├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── __tests__ │ ├── App.spec.js │ ├── Button.spec.js │ ├── Display.spec.js │ └── Panel.spec.js ├── components │ ├── Button │ │ ├── Button.css │ │ └── Button.js │ ├── Display │ │ ├── Display.css │ │ └── Display.js │ └── Panel │ │ ├── Panel.css │ │ └── Panel.js ├── index.css ├── index.js ├── logic │ ├── calculate.js │ └── operate.js └── setupTests.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # editor 13 | .vscode 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Testing 2 | 3 | This module builds upon the topic of client-side testing by extending it into the realm of React. 4 | 5 | ## Project Description 6 | 7 | The objectives of this module are the following: 8 | 9 | - To write tests for a React application using Jest and Enzyme. These two libraries are commonly used in production for testing React components and applications. 10 | - To practice reading over and understanding code which you did not write, but that you do need to test. 11 | 12 | For this exercise, you're given a complete React application. Your job is to: 13 | 14 | 1. Go through and inspect **_all_** of the different pieces of the code. 15 | 2. Write tests for **_all_** of the components in the `./src/components` directory. 16 | 3. Write these tests in the correspondingly-named files in the `./src/__tests__` directory. 17 | 18 | You'll be using [Jest](https://facebook.github.io/jest/docs/en/expect.html) and [Enzyme](http://airbnb.io/enzyme/) in order to test React components. 19 | 20 | ## Initialization and Setup 21 | 22 | You can run the complete application by installing Node modules with the `yarn` command, and then running `yarn start`. 23 | 24 | Run `yarn test` in order to run the test suite. Of course, this isn't going to actually test anything yet, since you haven't written the tests! 25 | 26 | ## Stretch Goals 27 | 28 | Add additional test files `calculate.test.js` and `operate.test.js` in the `tests` directory to test the files in the `./src/logic` folder. The files in this directory aren't React components, so for testing them you'll actually just be using Jest without Enzyme. 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calculator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.6.3", 7 | "react-dom": "^16.6.3", 8 | "react-scripts": "^2.1.1" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | }, 16 | "devDependencies": { 17 | "enzyme": "^3.7.0", 18 | "enzyme-adapter-react-16": "^1.7.0" 19 | }, 20 | "browserslist": [ 21 | ">0.2%", 22 | "not dead", 23 | "not ie <= 11", 24 | "not op_mini all" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloominstituteoftechnology/React-Testing/c4ba830ee962279228e0622035aac81e08a0c366/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .component-app { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: wrap; 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Display from './components/Display/Display'; 3 | import Panel from './components/Panel/Panel'; 4 | import calculate from './logic/calculate'; 5 | import './App.css'; 6 | 7 | class App extends Component { 8 | state = { 9 | total: '0', 10 | next: null, 11 | operation: null, 12 | }; 13 | 14 | handleClick = buttonName => { 15 | this.setState(calculate(this.state, buttonName)); 16 | }; 17 | 18 | render() { 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /src/__tests__/App.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import App from '../App'; 5 | 6 | describe('', () => { 7 | it('renders without crashing', () => { 8 | shallow(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/__tests__/Button.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Button from '../components/Button/Button'; 5 | 6 | describe(' 21 | 22 | ); 23 | } 24 | } 25 | 26 | Button.propTypes = { 27 | name: PropTypes.string, 28 | orange: PropTypes.bool, 29 | wide: PropTypes.bool, 30 | clickHandler: PropTypes.func, 31 | }; 32 | 33 | export default Button; -------------------------------------------------------------------------------- /src/components/Display/Display.css: -------------------------------------------------------------------------------- 1 | .component-display { 2 | background-color: #858694; 3 | color: white; 4 | text-align: right; 5 | font-weight: 200; 6 | flex: 0 0 auto; 7 | width: 100%; 8 | } 9 | 10 | .component-display > div { 11 | font-size: 20px; 12 | padding: 8px 4px 0 4px; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/components/Display/Display.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './Display.css'; 4 | 5 | const Display = ({ value }) => { 6 | return ( 7 |
8 |
9 | {value} 10 |
11 |
12 | ); 13 | }; 14 | 15 | Display.propTypes = { 16 | value: PropTypes.string 17 | }; 18 | 19 | export default Display; -------------------------------------------------------------------------------- /src/components/Panel/Panel.css: -------------------------------------------------------------------------------- 1 | .component-button-panel { 2 | background-color: #858694; 3 | display: flex; 4 | flex-direction: row; 5 | flex-wrap: wrap; 6 | flex: 1 0 auto; 7 | } 8 | 9 | .component-button-panel > div { 10 | width: 100%; 11 | margin-bottom: 1px; 12 | flex: 1 0 auto; 13 | display: flex; 14 | } -------------------------------------------------------------------------------- /src/components/Panel/Panel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Button from '../Button/Button'; 3 | import PropTypes from 'prop-types'; 4 | import './Panel.css'; 5 | 6 | class Panel extends Component { 7 | handleClick = (buttonName) => { 8 | this.props.clickHandler(buttonName); 9 | }; 10 | 11 | render() { 12 | return ( 13 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
38 |
39 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | Panel.propTypes = { 49 | clickHandler: PropTypes.func, 50 | }; 51 | 52 | export default Panel; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background-color: black; 7 | margin: 0; 8 | padding: 0; 9 | font-family: sans-serif; 10 | height: 100%; 11 | } 12 | 13 | #root { 14 | height: 100%; 15 | } 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /src/logic/calculate.js: -------------------------------------------------------------------------------- 1 | import operate from './operate'; 2 | 3 | /** 4 | * Given a button name and a calculator data object, return an updated 5 | * calculator data object. 6 | * 7 | * Calculator data object contains: 8 | * total:String the running total 9 | * next:String the next number to be operated on with the total 10 | * operation:String +, -, etc. 11 | */ 12 | 13 | function isNumber(x) { 14 | return !!x.match(/[0-9]+/); 15 | } 16 | 17 | function calculate(obj, buttonName) { 18 | if (buttonName === 'AC') { 19 | return { 20 | total: null, 21 | next: null, 22 | operation: null, 23 | }; 24 | } 25 | 26 | if (isNumber(buttonName)) { 27 | if (buttonName === '0' && obj.next === '0') { 28 | return {}; 29 | } 30 | 31 | if (obj.operation) { 32 | if (obj.next) { 33 | return { next: obj.next + buttonName }; 34 | } 35 | return { next: buttonName }; 36 | } 37 | if (obj.next) { 38 | return { 39 | next: obj.next + buttonName, 40 | total: null, 41 | }; 42 | } 43 | return { 44 | next: buttonName, 45 | total: null, 46 | }; 47 | } 48 | 49 | if (buttonName === '.') { 50 | if (obj.next) { 51 | if (obj.next.includes('.')) { 52 | return {}; 53 | } 54 | return { next: obj.next + '.' }; 55 | } 56 | if (obj.operation) { 57 | return { next: '0.' }; 58 | } 59 | if (obj.total) { 60 | if (obj.total.includes('.')) { 61 | return {}; 62 | } 63 | return { total: obj.total + '.' }; 64 | } 65 | return { total: '0.' }; 66 | } 67 | 68 | if (buttonName === '=') { 69 | if (obj.next && obj.operation) { 70 | return { 71 | total: operate(obj.total, obj.next, obj.operation), 72 | next: null, 73 | operation: null, 74 | }; 75 | } else { 76 | return {}; 77 | } 78 | } 79 | 80 | if (buttonName === '+/-') { 81 | if (obj.next) { 82 | return { next: (-1 * parseFloat(obj.next)).toString() }; 83 | } 84 | if (obj.total) { 85 | return { total: (-1 * parseFloat(obj.next)).toString() }; 86 | } 87 | return {}; 88 | } 89 | 90 | if (obj.operation) { 91 | return { 92 | total: operate(obj.total, obj.next, obj.operation), 93 | next: null, 94 | operation: buttonName, 95 | }; 96 | } 97 | 98 | if (!obj.next) { 99 | return { operation: buttonName }; 100 | } 101 | 102 | return { 103 | total: obj.next, 104 | next: null, 105 | operation: buttonName, 106 | }; 107 | } 108 | 109 | export default calculate; 110 | -------------------------------------------------------------------------------- /src/logic/operate.js: -------------------------------------------------------------------------------- 1 | import Big from 'big.js'; 2 | 3 | function operate(numberOne, numberTwo, operation) { 4 | const one = Big(numberOne); 5 | const two = Big(numberTwo); 6 | 7 | switch(operation) { 8 | case '+': 9 | return one.plus(two).toString(); 10 | case '-': 11 | return one.minus(two).toString(); 12 | case 'x': 13 | return one.times(two).toString(); 14 | case '÷': 15 | return one.div(two).toString(); 16 | case '%': 17 | return one.mod(two).toString(); 18 | default: 19 | throw Error(`Unknown operation ${operation}`); 20 | } 21 | } 22 | 23 | export default operate; -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | --------------------------------------------------------------------------------