├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.png
└── index.html
└── src
├── components
├── App
│ ├── App.css
│ ├── App.jsx
│ ├── App.spec.js
│ └── __snapshots__
│ │ └── App.spec.js.snap
├── Calculator
│ ├── Calculator.css
│ ├── Calculator.jsx
│ ├── Calculator.spec.js
│ └── __snapshots__
│ │ └── Calculator.spec.js.snap
├── Display
│ ├── Display.css
│ ├── Display.jsx
│ ├── Display.spec.js
│ └── __snapshots__
│ │ └── Display.spec.js.snap
├── Key
│ ├── Key.css
│ ├── Key.jsx
│ ├── Key.spec.js
│ └── __snapshots__
│ │ └── Key.spec.js.snap
└── Keypad
│ ├── Keypad.css
│ ├── Keypad.jsx
│ ├── Keypad.spec.js
│ └── __snapshots__
│ └── Keypad.spec.js.snap
├── index.css
├── index.js
└── setupTests.js
/.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 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TDD React
2 |
3 | A simple Calculator app created for a blog post at [testdriven.io](https://testdriven.io/blog/tdd-with-react-jest-and-enzyme-part-one/).
4 |
5 | ## Local Setup
6 |
7 | ```sh
8 | $ git clone git@github.com:calebpollman/react-calculator.git
9 | ```
10 |
11 | ```sh
12 | $ cd react-calculator
13 | ```
14 |
15 | ```sh
16 | $ npm install
17 | ```
18 |
19 | ## Run Locally
20 |
21 | ```sh
22 | $ npm start
23 | ```
24 |
25 | ## Run Tests
26 |
27 | ```sh
28 | $ npm test
29 | ```
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tdd-react-calculator",
3 | "author": "caleb pollman",
4 | "version": "0.1.0",
5 | "private": true,
6 | "engines": {
7 | "node": "14.16.1",
8 | "npm": "6.14.12"
9 | },
10 | "dependencies": {
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2"
13 | },
14 | "devDependencies": {
15 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
16 | "enzyme": "^3.11.0",
17 | "enzyme-to-json": "^3.6.2",
18 | "prettier": "^2.2.1",
19 | "react-scripts": "4.0.3",
20 | "react-test-renderer": "^17.0.2"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test --env=jsdom",
26 | "eject": "react-scripts eject"
27 | },
28 | "browserslist": [
29 | ">0.2%",
30 | "not dead",
31 | "not ie <= 11",
32 | "not op_mini all"
33 | ],
34 | "jest": {
35 | "snapshotSerializers": [
36 | "enzyme-to-json/serializer"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/calebpollman/react-calculator/f6b109f2c2b194c8881047e918516282edd40a11/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Calcutronic 5000
10 |
11 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/App/App.css:
--------------------------------------------------------------------------------
1 | .app-container {
2 | align-items: center;
3 | display: flex;
4 | height: 100vh;
5 | justify-content: center;
6 | width: 100vw;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/App/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Calculator from '../Calculator/Calculator';
3 | import './App.css';
4 |
5 | const App = () => (
6 |
7 |
8 |
9 | );
10 |
11 | export default App;
12 |
--------------------------------------------------------------------------------
/src/components/App/App.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import App from './App';
4 | import Calculator from '../Calculator/Calculator';
5 |
6 | describe('App', () => {
7 | let wrapper;
8 | beforeEach(() => (wrapper = shallow()));
9 |
10 | it('should render correctly', () => expect(wrapper).toMatchSnapshot());
11 |
12 | it('should render a ', () => {
13 | expect(wrapper.find('div').length).toEqual(1);
14 | });
15 |
16 | it('should render the Calculator Component', () => {
17 | expect(wrapper.containsMatchingElement()).toEqual(true);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/App/__snapshots__/App.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`App should render correctly 1`] = `
4 |
7 |
8 |
9 | `;
10 |
--------------------------------------------------------------------------------
/src/components/Calculator/Calculator.css:
--------------------------------------------------------------------------------
1 | .calculator-container {
2 | background-color: var(--calculator-background-color);
3 | height: var(--calculator-height);
4 | width: var(--calculator-width);
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/Calculator/Calculator.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Calculator.css';
3 |
4 | import Display from '../Display/Display';
5 | import Keypad from '../Keypad/Keypad';
6 | import './Calculator.css';
7 |
8 | class Calculator extends Component {
9 | state = {
10 | displayValue: '0',
11 | numbers: ['9', '8', '7', '6', '5', '4', '3', '2', '1', '.', '0', 'ce'],
12 | operators: ['/', 'x', '-', '+'],
13 | selectedOperator: '',
14 | storedValue: '',
15 | };
16 |
17 | componentDidMount = () => {
18 | document.addEventListener('keydown', this.handleKeyPress);
19 | };
20 |
21 | componentWillUnmount = () => {
22 | document.removeEventListener('keydown', this.handleKeyPress);
23 | };
24 |
25 | callOperator = () => {
26 | let { displayValue, selectedOperator, storedValue } = this.state;
27 | const updateStoredValue = displayValue;
28 |
29 | displayValue = parseInt(displayValue, 10);
30 | storedValue = parseInt(storedValue, 10);
31 |
32 | switch (selectedOperator) {
33 | case '+':
34 | displayValue = storedValue + displayValue;
35 | break;
36 | case '-':
37 | displayValue = storedValue - displayValue;
38 | break;
39 | case 'x':
40 | displayValue = storedValue * displayValue;
41 | break;
42 | case '/':
43 | displayValue = storedValue / displayValue;
44 | break;
45 | default:
46 | displayValue = '0';
47 | }
48 |
49 | displayValue = displayValue.toString();
50 | selectedOperator = '';
51 | if (displayValue === 'NaN' || displayValue === 'Infinity') displayValue = '0';
52 |
53 | this.setState({ displayValue, selectedOperator, storedValue: updateStoredValue });
54 | };
55 |
56 | handleKeyPress = (event) => {
57 | const { numbers, operators } = this.state;
58 |
59 | if (event.key === 'Backspace') this.updateDisplay('ce');
60 | if (event.key === 'Enter' || event.key === '=') this.callOperator();
61 |
62 | numbers.forEach((number) => {
63 | if (event.key === number) this.updateDisplay(number);
64 | });
65 |
66 | operators.forEach((operator) => {
67 | if (event.key === operator) this.setOperator(operator);
68 | });
69 | };
70 |
71 | setOperator = (value) => {
72 | let { displayValue, selectedOperator, storedValue } = this.state;
73 |
74 | if (selectedOperator === '') {
75 | storedValue = displayValue;
76 | displayValue = '0';
77 | selectedOperator = value;
78 | } else {
79 | selectedOperator = value;
80 | }
81 |
82 | this.setState({ displayValue, selectedOperator, storedValue });
83 | };
84 |
85 | updateDisplay = (value) => {
86 | let { displayValue } = this.state;
87 |
88 | if (value === '.' && displayValue.includes('.')) value = '';
89 |
90 | if (value === 'ce') {
91 | displayValue = displayValue.substr(0, displayValue.length - 1);
92 | if (displayValue === '') displayValue = '0';
93 | } else {
94 | displayValue === '0' ? (displayValue = value) : (displayValue += value);
95 | }
96 |
97 | this.setState({ displayValue });
98 | };
99 |
100 | render() {
101 | const { displayValue, numbers, operators } = this.state;
102 |
103 | return (
104 |
105 |
106 |
114 |
115 | );
116 | }
117 | }
118 |
119 | export default Calculator;
120 |
--------------------------------------------------------------------------------
/src/components/Calculator/Calculator.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, shallow } from 'enzyme';
3 | import Calculator from './Calculator';
4 | import Display from '../Display/Display';
5 | import Keypad from '../Keypad/Keypad';
6 |
7 | describe('Calculator', () => {
8 | let wrapper;
9 | beforeEach(() => (wrapper = shallow()));
10 |
11 | it('should render correctly', () => expect(wrapper).toMatchSnapshot());
12 |
13 | it('should render a ', () => {
14 | expect(wrapper.find('div').length).toEqual(1);
15 | });
16 |
17 | it('should render the Display and Keypad Components', () => {
18 | expect(
19 | wrapper.containsAllMatchingElements([
20 | ,
21 | ,
29 | ])
30 | ).toEqual(true);
31 | });
32 |
33 | describe('updateDisplay', () => {
34 | it('updates displayValue', () => {
35 | wrapper.instance().updateDisplay('5');
36 | expect(wrapper.state('displayValue')).toEqual('5');
37 | });
38 |
39 | it('concatenates displayValue', () => {
40 | wrapper.instance().updateDisplay('5');
41 | wrapper.instance().updateDisplay('0');
42 | expect(wrapper.state('displayValue')).toEqual('50');
43 | });
44 |
45 | it('removes leading "0" from displayValue', () => {
46 | wrapper.instance().updateDisplay('0');
47 | expect(wrapper.state('displayValue')).toEqual('0');
48 | wrapper.instance().updateDisplay('5');
49 | expect(wrapper.state('displayValue')).toEqual('5');
50 | });
51 |
52 | it('prevents multiple leading "0"s from displayValue', () => {
53 | wrapper.instance().updateDisplay('0');
54 | wrapper.instance().updateDisplay('0');
55 | expect(wrapper.state('displayValue')).toEqual('0');
56 | });
57 |
58 | it('removes last char of displayValue', () => {
59 | wrapper.instance().updateDisplay('5');
60 | wrapper.instance().updateDisplay('0');
61 | wrapper.instance().updateDisplay('ce');
62 | expect(wrapper.state('displayValue')).toEqual('5');
63 | });
64 |
65 | it('prevents multiple instances of "." in displayValue', () => {
66 | wrapper.instance().updateDisplay('.');
67 | wrapper.instance().updateDisplay('.');
68 | expect(wrapper.state('displayValue')).toEqual('.');
69 | });
70 |
71 | it('will set displayValue to "0" if displayValue is equal to an empty string', () => {
72 | wrapper.instance().updateDisplay('ce');
73 | expect(wrapper.state('displayValue')).toEqual('0');
74 | });
75 | });
76 |
77 | describe('setOperator', () => {
78 | it('updates the value of selectedOperator', () => {
79 | wrapper.instance().setOperator('+');
80 | expect(wrapper.state('selectedOperator')).toEqual('+');
81 | wrapper.instance().setOperator('/');
82 | expect(wrapper.state('selectedOperator')).toEqual('/');
83 | });
84 |
85 | it('updates the value of storedValue to the value of displayValue', () => {
86 | wrapper.setState({ displayValue: '5' });
87 | wrapper.instance().setOperator('+');
88 | expect(wrapper.state('storedValue')).toEqual('5');
89 | });
90 |
91 | it('updates the value of displayValue to "0"', () => {
92 | wrapper.setState({ displayValue: '5' });
93 | wrapper.instance().setOperator('+');
94 | expect(wrapper.state('displayValue')).toEqual('0');
95 | });
96 |
97 | it('selectedOperator is not an empty string, does not update storedValue', () => {
98 | wrapper.setState({ displayValue: '5' });
99 | wrapper.instance().setOperator('+');
100 | expect(wrapper.state('storedValue')).toEqual('5');
101 | wrapper.instance().setOperator('-');
102 | expect(wrapper.state('storedValue')).toEqual('5');
103 | });
104 | });
105 |
106 | describe('callOperator', () => {
107 | it('updates displayValue to the sum of storedValue and displayValue', () => {
108 | wrapper.setState({ storedValue: '3' });
109 | wrapper.setState({ displayValue: '2' });
110 | wrapper.setState({ selectedOperator: '+' });
111 | wrapper.instance().callOperator();
112 | expect(wrapper.state('displayValue')).toEqual('5');
113 | });
114 |
115 | it('updates displayValue to the difference of storedValue and displayValue', () => {
116 | wrapper.setState({ storedValue: '3' });
117 | wrapper.setState({ displayValue: '2' });
118 | wrapper.setState({ selectedOperator: '-' });
119 | wrapper.instance().callOperator();
120 | expect(wrapper.state('displayValue')).toEqual('1');
121 | });
122 |
123 | it('updates displayValue to the product of storedValue and displayValue', () => {
124 | wrapper.setState({ storedValue: '3' });
125 | wrapper.setState({ displayValue: '2' });
126 | wrapper.setState({ selectedOperator: 'x' });
127 | wrapper.instance().callOperator();
128 | expect(wrapper.state('displayValue')).toEqual('6');
129 | });
130 |
131 | it('updates displayValue to the quotient of storedValue and displayValue', () => {
132 | wrapper.setState({ storedValue: '3' });
133 | wrapper.setState({ displayValue: '2' });
134 | wrapper.setState({ selectedOperator: '/' });
135 | wrapper.instance().callOperator();
136 | expect(wrapper.state('displayValue')).toEqual('1.5');
137 | });
138 |
139 | it('updates displayValue to "0" if operation results in "NaN"', () => {
140 | wrapper.setState({ storedValue: '3' });
141 | wrapper.setState({ displayValue: 'string' });
142 | wrapper.setState({ selectedOperator: '/' });
143 | wrapper.instance().callOperator();
144 | expect(wrapper.state('displayValue')).toEqual('0');
145 | });
146 |
147 | it('updates displayValue to "0" if operation results in "Infinity"', () => {
148 | wrapper.setState({ storedValue: '7' });
149 | wrapper.setState({ displayValue: '0' });
150 | wrapper.setState({ selectedOperator: '/' });
151 | wrapper.instance().callOperator();
152 | expect(wrapper.state('displayValue')).toEqual('0');
153 | });
154 |
155 | it('updates displayValue to "0" if selectedOperator does not match cases', () => {
156 | wrapper.setState({ storedValue: '7' });
157 | wrapper.setState({ displayValue: '10' });
158 | wrapper.setState({ selectedOperator: 'string' });
159 | wrapper.instance().callOperator();
160 | expect(wrapper.state('displayValue')).toEqual('0');
161 | });
162 |
163 | it('updates displayValue to "0" if called with no value for storedValue or selectedOperator', () => {
164 | wrapper.setState({ storedValue: '' });
165 | wrapper.setState({ displayValue: '10' });
166 | wrapper.setState({ selectedOperator: '' });
167 | wrapper.instance().callOperator();
168 | expect(wrapper.state('displayValue')).toEqual('0');
169 | });
170 | });
171 | });
172 |
173 | describe('mounted Calculator', () => {
174 | let wrapper;
175 | beforeEach(() => (wrapper = mount()));
176 |
177 | it('calls updateDisplay when a number key is clicked', () => {
178 | const spy = jest.spyOn(wrapper.instance(), 'updateDisplay');
179 | wrapper.instance().forceUpdate();
180 | expect(spy).toHaveBeenCalledTimes(0);
181 | wrapper
182 | .find('.number-key')
183 | .first()
184 | .simulate('click');
185 | expect(spy).toHaveBeenCalledTimes(1);
186 | });
187 |
188 | it('calls setOperator when an operator key is clicked', () => {
189 | const spy = jest.spyOn(wrapper.instance(), 'setOperator');
190 | wrapper.instance().forceUpdate();
191 | expect(spy).toHaveBeenCalledTimes(0);
192 | wrapper
193 | .find('.operator-key')
194 | .first()
195 | .simulate('click');
196 | expect(spy).toHaveBeenCalledTimes(1);
197 | });
198 |
199 | it('calls callOperator when the submit key is clicked', () => {
200 | const spy = jest.spyOn(wrapper.instance(), 'callOperator');
201 | wrapper.instance().forceUpdate();
202 | expect(spy).toHaveBeenCalledTimes(0);
203 | wrapper.find('.submit-key').simulate('click');
204 | expect(spy).toHaveBeenCalledTimes(1);
205 | });
206 | });
207 |
--------------------------------------------------------------------------------
/src/components/Calculator/__snapshots__/Calculator.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Calculator should render correctly 1`] = `
4 |
7 |
10 |
40 |
41 | `;
42 |
--------------------------------------------------------------------------------
/src/components/Display/Display.css:
--------------------------------------------------------------------------------
1 | .display-container {
2 | align-items: center;
3 | background: var(--display-background-color);
4 | display: flex;
5 | height: var(--display-height);
6 | padding: 0 4%;
7 | width: var(--display-width);
8 | }
9 |
10 | .display-value {
11 | color: var(--display-text-color);
12 | font-size: var(--display-text-size);
13 | font-family: var(--main-font);
14 | font-weight: var(--display-text-weight);
15 | margin-left: auto;
16 | overflow: hidden;
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Display/Display.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Display.css';
4 |
5 | const Display = ({ displayValue }) => (
6 |
9 | );
10 |
11 | Display.propTypes = { displayValue: PropTypes.string.isRequired };
12 |
13 | Display.defaultProps = { displayValue: 'default' };
14 |
15 | export default Display;
16 |
--------------------------------------------------------------------------------
/src/components/Display/Display.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import Display from './Display';
4 |
5 | const MOCK_DISPLAY_VALUE = 'a display value';
6 |
7 | describe('Display', () => {
8 | let wrapper;
9 | beforeEach(() => (wrapper = shallow()));
10 |
11 | it('should render correctly', () => expect(wrapper).toMatchSnapshot());
12 |
13 | it('should render a ', () => {
14 | expect(wrapper.find('div').length).toEqual(1);
15 | });
16 |
17 | it('renders the value of displayValue', () => {
18 | expect(wrapper.text()).toEqual(MOCK_DISPLAY_VALUE);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/components/Display/__snapshots__/Display.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Display should render correctly 1`] = `
4 |
7 |
10 | a display value
11 |
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/src/components/Key/Key.css:
--------------------------------------------------------------------------------
1 | .key-container {
2 | align-items: center;
3 | display: flex;
4 | height: 25%;
5 | justify-content: center;
6 | transition: background-color 0.3s linear;
7 | }
8 |
9 | .key-container:hover {
10 | cursor: pointer;
11 | }
12 |
13 | .operator-key {
14 | background-color: var(--action-key-color);
15 | width: 100%;
16 | }
17 |
18 | .operator-key:hover {
19 | background-color: var(--action-key-color-hover);
20 | }
21 |
22 | .number-key {
23 | background-color: var(--number-key-color);
24 | width: calc(100% / 3);
25 | }
26 |
27 | .number-key:hover {
28 | background-color: var(--number-key-color-hover);
29 | }
30 |
31 | .submit-key {
32 | background-color: var(--submit-key-color);
33 | height: 100%;
34 | width: 100%;
35 | }
36 |
37 | .submit-key:hover {
38 | background-color: var(--submit-key-color-hover);
39 | }
40 |
41 | .key-value {
42 | color: var(--key-text-color);
43 | font-family: var(--main-font);
44 | font-size: var(--key-text-size);
45 | font-weight: var(--key-text-weight);
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Key/Key.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Key.css';
4 |
5 | const Key = ({ handleKeyPress, keyAction, keyType, keyValue }) => {
6 | const keyClass = `key-container ${keyType}`;
7 |
8 | return (
9 | keyAction(keyValue)} onKeyPress={event => handleKeyPress(event)}>
10 |
{keyValue}
11 |
12 | );
13 | };
14 |
15 | Key.propTypes = {
16 | handleKeyPress: PropTypes.func.isRequired,
17 | keyAction: PropTypes.func.isRequired,
18 | keyType: PropTypes.string.isRequired,
19 | keyValue: PropTypes.string.isRequired,
20 | };
21 |
22 | Key.defaultProps = {
23 | keyType: 'default',
24 | keyAction: 'default',
25 | };
26 |
27 | export default Key;
28 |
--------------------------------------------------------------------------------
/src/components/Key/Key.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import Key from './Key';
4 |
5 | const MOCK_HANDLE_KEY_PRESS = jest.fn();
6 | const MOCK_KEY_ACTION = jest.fn();
7 | const MOCK_KEY_TYPE = 'number-key';
8 | const MOCK_KEY_VALUE = '7';
9 |
10 | describe('Key', () => {
11 | let wrapper;
12 | beforeEach(() => {
13 | wrapper = shallow(
14 |
20 | );
21 | });
22 |
23 | it('should render correctly', () => expect(wrapper).toMatchSnapshot());
24 |
25 | it('should render a ', () => {
26 | expect(wrapper.find('div').length).toEqual(1);
27 | });
28 |
29 | it('should render the value of keyValue', () => {
30 | wrapper.setProps({ keyValue: 'test' });
31 | expect(wrapper.text()).toEqual('test');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/components/Key/__snapshots__/Key.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Key should render correctly 1`] = `
4 |
15 | `;
16 |
--------------------------------------------------------------------------------
/src/components/Keypad/Keypad.css:
--------------------------------------------------------------------------------
1 | .keypad-container {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: wrap;
5 | height: var(--keypad-height);
6 | padding: 2%;
7 | width: var(--keypad-width);
8 | }
9 |
10 | .numbers-container {
11 | display: flex;
12 | flex-direction: row;
13 | flex-wrap: wrap;
14 | height: 80%;
15 | width: 75%;
16 | }
17 |
18 | .operators-container {
19 | display: flex;
20 | flex-direction: column;
21 | height: 80%;
22 | width: 25%;
23 | }
24 |
25 | .submit-container {
26 | height: 20%;
27 | width: 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Keypad/Keypad.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Keypad.css';
4 |
5 | import Key from '../Key/Key';
6 | import './Keypad.css';
7 |
8 | const Keypad = ({ operators, callOperator, handleKeyPress, numbers, setOperator, updateDisplay }) => {
9 | const numberKeys = numbers.map((number, iterator) => (
10 |
17 | ));
18 |
19 | const operatorKeys = operators.map((operator, iterator) => (
20 |
27 | ));
28 |
29 | return (
30 |
31 |
{numberKeys}
32 |
{operatorKeys}
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | Keypad.propTypes = {
41 | callOperator: PropTypes.func.isRequired,
42 | handleKeyPress: PropTypes.func.isRequired,
43 | numbers: PropTypes.array.isRequired,
44 | operators: PropTypes.array.isRequired,
45 | setOperator: PropTypes.func.isRequired,
46 | updateDisplay: PropTypes.func.isRequired,
47 | };
48 |
49 | Keypad.defaultProps = {
50 | numbers: [],
51 | operators: [],
52 | };
53 |
54 | export default Keypad;
55 |
--------------------------------------------------------------------------------
/src/components/Keypad/Keypad.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount, shallow } from 'enzyme';
3 | import Keypad from './Keypad';
4 |
5 | const MOCK_CALL_OPERATOR = jest.fn();
6 | const MOCK_HANDLE_KEY_PRESS = jest.fn();
7 | const MOCK_NUMBERS = ['7', '13'];
8 | const MOCK_OPERATORS = ['+', '-'];
9 | const MOCK_SET_OPERATOR = jest.fn();
10 | const MOCK_UPDATE_DISPLAY = jest.fn();
11 |
12 | describe('Keypad', () => {
13 | let wrapper;
14 | beforeEach(() => {
15 | wrapper = shallow(
16 |
24 | );
25 | });
26 |
27 | it('should render correctly', () => expect(wrapper).toMatchSnapshot());
28 |
29 | it("should render 4 's", () => {
30 | expect(wrapper.find('div').length).toEqual(4);
31 | });
32 |
33 | it('should render an instance of the Key component for each index of the numbers and operators props, and the submit Key', () => {
34 | const submitKeyValue = 1;
35 | const keyTotal = MOCK_NUMBERS.length + MOCK_OPERATORS.length + submitKeyValue;
36 | expect(wrapper.find('Key').length).toEqual(keyTotal);
37 | });
38 | });
39 |
40 | describe('mounted Keypad', () => {
41 | let wrapper;
42 | beforeEach(() => {
43 | wrapper = mount(
44 |
52 | );
53 | });
54 |
55 | it('renders the values of the numbers prop to the DOM', () => {
56 | const numbers = MOCK_NUMBERS.join('');
57 | expect(wrapper.find('.numbers-container').text()).toEqual(numbers);
58 | });
59 |
60 | it('renders the values of the operators prop to the DOM', () => {
61 | const operators = MOCK_OPERATORS.join('');
62 | expect(wrapper.find('.operators-container').text()).toEqual(operators);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/components/Keypad/__snapshots__/Keypad.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Keypad should render correctly 1`] = `
4 |
7 |
10 |
17 |
24 |
25 |
28 |
35 |
42 |
43 |
46 |
52 |
53 |
54 | `;
55 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /*
2 | app variables
3 | */
4 |
5 | :root {
6 | /* background colors */
7 | --action-key-color: #545454;
8 | --action-key-color-hover: #2a2a2a;
9 | --calculator-background-color: #696969;
10 | --display-background-color: #1d1f1f;
11 | --number-key-color: #696969;
12 | --number-key-color-hover: #3f3f3f;
13 | --submit-key-color: #d18800;
14 | --submit-key-color-hover: #aa6e00;
15 |
16 | /* font */
17 | --main-font: 'Orbitron', sans-serif;
18 |
19 | /* font colors */
20 | --display-text-color: #23e000;
21 | --key-text-color: #d3d3d3;
22 |
23 | /* font sizes */
24 | --display-text-size: 4em;
25 | --key-text-size: 3em;
26 |
27 | /* font weights */
28 | --display-text-weight: 400;
29 | --key-text-weight: 700;
30 |
31 | /* calculator dimensions */
32 | --calculator-height: 72%;
33 | --calculator-width: 36%;
34 |
35 | /* display dimensions */
36 | --display-height: 24%;
37 | --display-width: 92%;
38 |
39 | /* keypad dimensions */
40 | --keypad-height: 72%;
41 | --keypad-width: 96%;
42 | }
43 |
44 | @media screen and (max-width: 1024px) {
45 | :root {
46 | /* font sizes */
47 | --display-text-size: 6em;
48 | --key-text-size: 3em;
49 |
50 | /* calculator dimensions */
51 | --calculator-height: 100%;
52 | --calculator-width: 100%;
53 | }
54 | }
55 |
56 | /*
57 | app CSS reset
58 | */
59 |
60 | body,
61 | div,
62 | p {
63 | margin: 0;
64 | padding: 0;
65 | }
66 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App/App';
4 | import './index.css';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------