Skip to content
This repository has been archived by the owner on Mar 9, 2021. It is now read-only.

Commit

Permalink
Add initial structure and the start of the calculate function
Browse files Browse the repository at this point in the history
  • Loading branch information
davetayls committed Feb 17, 2020
0 parents commit 0d693c3
Show file tree
Hide file tree
Showing 20 changed files with 8,059 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2

[*.css]
indent_size = 2
tab_width = 2

[{*.ts,*.tsx,*.js}]
indent_size = 2
tab_width = 2

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
node_modules
dist
4 changes: 4 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trailingComma: "es5"
tabWidth: 2
semi: false
singleQuote: true
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Simple Calculator
=================

This project depends on having Node JS (https://nodejs.org/en/)
and Yarn (https://classic.yarnpkg.com/en/) installed
on the system.

Installation
------------

Install the necessary dependencies using yarn.

```sh
yarn
```

Development Server
------------------

```sh
yarn start
```

Production Build
----------------

This build the project into the `./dist` directory

```sh
NODE_ENV=production yarn build
```
5 changes: 5 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

declare module '*.svg' {
const ReactComponent: React.ComponentType<React.ImgHTMLAttributes<HTMLImageElement>>
export = ReactComponent
}
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "simple-calculator",
"version": "1.0.0",
"description": "Simple calculator in React",
"main": "src/index.tsx",
"scripts": {
"clean": "",
"start": "webpack-dev-server",
"build": "webpack",
"test": "jest"
},
"author": "Dave Taylor <[email protected]>",
"license": "GPL-3.0",
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"@svgr/webpack": "^5.1.0",
"@types/jest": "^25.1.2",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"html-webpack-plugin": "^3.2.0",
"jest": "^25.1.0",
"prettier": "^1.19.1",
"ts-jest": "^25.2.0",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"typescript": "^3.7.5",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}
35 changes: 35 additions & 0 deletions src/__tests__/calculate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { calculate, isOperand, splitIntoCalculationParts } from '../calculate'
import { split } from 'ts-node'

describe('calculate', () => {
describe('isOperand', () => {
it.each(['/', '*', '+', '-'])('should see %s as a valid operand', (operand: string) => {
expect(isOperand(operand)).toEqual(true)
})
it.each(['a', '"', '_', 'x'])('should see %s as an invalid operand', (operand: string) => {
expect(isOperand(operand)).toEqual(false)
})
})

describe('splitIntoCalculationParts', () => {
it('should split the calculation into numer and operand parts', function() {
expect(splitIntoCalculationParts('2 / 2 * 2 + 2 - 2')).toEqual([2, '/', 2, '*', 2, '+', 2, '-', 2])
})
it('should not be affected by whitespace', function() {
expect(splitIntoCalculationParts('2*2-1')).toEqual([2, '*', 2, '-', 1])
})
})
describe('Single operator calculations', () => {
it.each([
['2 + 2', 4],
['2+2', 4],
['2+ 2', 4],
['2 +2', 4],
['2 - 2', 0],
['2 * 2', 4],
['2 / 2', 1],
])('should calculate %s to equal %s', function(calculation, expected) {
expect(calculate(calculation)).toEqual(expected)
})
})
})
47 changes: 47 additions & 0 deletions src/calculate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface ICalculateResult {
result?: number
error?: string
}

export type TOperand = '/' | '*' | '+' | '-'
export type TCalculationPart = number | TOperand

export const isOperand = (char: string): char is TOperand => /^[\/*+\-]$/.test(char)
const isValid = (calculation: string) => /^[0-9\s\/\-+*]+[0-9]$/g.test(calculation)

const getNumber = (withoutSpaces: string) => (lastOperandIndex: number, currentIndex?: number) => {
const lastNumberString = withoutSpaces.substring(lastOperandIndex + 1, currentIndex)
return parseFloat(lastNumberString)
}

export const splitIntoCalculationParts = (calculation: string): any[] => {
const withoutSpaces = calculation.replace(/\s/g, '')
const getLastNumber = getNumber(withoutSpaces)
const parts: TCalculationPart[] = []
let lastOperandIndex = -1
for (let i = 0; i < withoutSpaces.length; i += 1) {
const char = withoutSpaces[i]
if (isOperand(char)) {
parts.push(getLastNumber(lastOperandIndex, i))
parts.push(char)
lastOperandIndex = i
}
}
if (lastOperandIndex < withoutSpaces.length) {
parts.push(getLastNumber(lastOperandIndex))
}
return parts
}

const findAndCalculate = (parts: TCalculationPart[], operand: TOperand): TCalculationPart[] => {}

export const calculate = (calculation: string) => {
if (isValid(calculation)) {
const parts = splitIntoCalculationParts(calculation)
// find first operator and replace numbers with result
const div = findAndCalculate(parts, '/')
return { result: 10 }
} else {
return { error: 'Only supports simple calculation' }
}
}
7 changes: 7 additions & 0 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { CSSProperties } from 'react'

const buttonStyle: CSSProperties = {}

export const Button = (props: React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return <button style={buttonStyle} {...props} />
}
19 changes: 19 additions & 0 deletions src/components/CalculatorApp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { CSSProperties } from 'react'
import { Header } from '../Header/index'
import { CalculatorForm } from '../CalculatorForm/index'
import { EQUAL_EXPERTS_BLUE } from '../../tokens'

const appStyle: CSSProperties = {
maxWidth: '300px',
margin: '0 auto',
border: `solid 1px ${EQUAL_EXPERTS_BLUE}`,
}

export const CalculatorApp = () => {
return (
<div style={appStyle}>
<Header />
<CalculatorForm />
</div>
)
}
74 changes: 74 additions & 0 deletions src/components/CalculatorForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { CSSProperties, FormEvent, RefObject, useRef, useState } from 'react'
import { PROXIMITY_1 } from '../../tokens'
import { calculate, ICalculateResult } from '../../calculate'
import { Button } from '../Button/index'

const formStyle: CSSProperties = {
padding: PROXIMITY_1,
display: 'flex',
flexDirection: 'column',
}

const labelStyle: CSSProperties = {
padding: `${PROXIMITY_1} 0 ${PROXIMITY_1} 0`,
}

const inputStyle: CSSProperties = {
display: 'block',
}

const errorStyle: CSSProperties = {
color: '#900',
}

let buttonGridStyle: CSSProperties = {
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
}

export const CalculatorForm = () => {
const inputRef: RefObject<HTMLInputElement> = useRef<HTMLInputElement>(null)
const [{ result, error }, setResult] = useState<ICalculateResult>({})
const onSubmit = (e: FormEvent) => {
const calc = inputRef.current ? inputRef.current.value : ''
setResult(calculate(calc))
e.preventDefault()
}
const onButtonClick = (e: MouseEvent) => {}
return (
<form style={formStyle} onSubmit={onSubmit}>
<label style={labelStyle} htmlFor="calculation">
Expression:
</label>
<input style={inputStyle} ref={inputRef} id="calculation" name="calculation" type="text" />
{error && (
<label style={{ ...labelStyle, ...errorStyle }} htmlFor="calculation">
{error}
</label>
)}
{result && (
<label style={labelStyle} htmlFor="calculation">
`The result is: ${result}`
</label>
)}
<div style={buttonGridStyle}>
<Button>7</Button>
<Button>8</Button>
<Button>9</Button>
<Button>4</Button>
<Button>5</Button>
<Button>6</Button>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>0</Button>
</div>
<div style={buttonGridStyle}>
<Button>/</Button>
<Button>x</Button>
<Button>-</Button>
<Button>+</Button>
</div>
</form>
)
}
27 changes: 27 additions & 0 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { CSSProperties } from 'react'

import EqualExpertsLogo from './logo.svg'
import { EQUAL_EXPERTS_BLUE, PROXIMITY_1 } from '../../tokens'

const headerStyle: CSSProperties = {
display: 'grid',
gridTemplateColumns: '100px auto',
gridGap: PROXIMITY_1,
alignItems: 'center',
backgroundColor: EQUAL_EXPERTS_BLUE,
padding: PROXIMITY_1,
color: 'white',
}

const h1Style: CSSProperties = {
fontSize: '16px',
margin: '0',
padding: '0',
}

export const Header = () => (
<header style={headerStyle}>
<EqualExpertsLogo />
<h1 style={h1Style}>Calculator</h1>
</header>
)
43 changes: 43 additions & 0 deletions src/components/Header/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { CalculatorApp } from './components/CalculatorApp'

const reactElement = document.createElement('div')
document.body.appendChild(reactElement)
ReactDOM.render(<CalculatorApp />, reactElement)
4 changes: 4 additions & 0 deletions src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export const EQUAL_EXPERTS_BLUE = '#1795d4'

export const PROXIMITY_1 = '16px'
13 changes: 13 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"strict": true,
"module": "ESNext",
"target": "ESNext",
"jsx": "react",
"allowSyntheticDefaultImports": true
},
"include": [
"./global.d.ts",
"src/"
]
}
Loading

0 comments on commit 0d693c3

Please sign in to comment.