diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..f350d16
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,15 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": ["airbnb-base"],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 12,
+ "sourceType": "module"
+ },
+ "plugins": ["@typescript-eslint"],
+ "ignorePatterns": ["dist/", "node_modules/"],
+ "rules": {}
+}
diff --git a/.gitignore b/.gitignore
index a1066ba..5af8560 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
# Created by https://www.toptal.com/developers/gitignore/api/vscode,node,intellij
# Edit at https://www.toptal.com/developers/gitignore?templates=vscode,node,intellij
+.vscode/
+
### macOS ###
# General
.DS_Store
@@ -249,7 +251,6 @@ typings/
# Nuxt.js build / generate output
.nuxt
-dist
# Gatsby files
.cache/
diff --git a/.prettierc.json b/.prettierc.json
new file mode 100644
index 0000000..0678f27
--- /dev/null
+++ b/.prettierc.json
@@ -0,0 +1,5 @@
+{
+ "parser": "typescript",
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/cypress.json b/cypress.json
new file mode 100644
index 0000000..f348ac0
--- /dev/null
+++ b/cypress.json
@@ -0,0 +1,3 @@
+{
+ "baseUrl": "http://127.0.0.1:5500"
+}
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
new file mode 100644
index 0000000..02e4254
--- /dev/null
+++ b/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/cypress/integration/counter.spec.js b/cypress/integration/counter.spec.js
new file mode 100644
index 0000000..37cf7a1
--- /dev/null
+++ b/cypress/integration/counter.spec.js
@@ -0,0 +1,192 @@
+const testInputClickEvent = (first, result) => {
+ for (let item of first) {
+ cy.get('.digit')
+ .contains(item)
+ .click();
+ }
+ cy.get('#total').should('have.text', result);
+};
+
+const testTwoInputCalculateEvent = (first, oper, second, result) => {
+ for (let item of first) {
+ cy.get('.digit')
+ .contains(item)
+ .click();
+ }
+ for (let item of oper) {
+ cy.get('.operations')
+ .contains(item)
+ .click();
+ }
+ for (let item of second) {
+ cy.get('.digit')
+ .contains(item)
+ .click();
+ }
+ cy.get('.operations')
+ .contains('=')
+ .click();
+ cy.get('#total').should('have.text', result);
+};
+
+describe('initial value', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('total value', () => {
+ cy.get('#total').should('have.text', '0');
+ });
+});
+
+describe('Render digit when button clicked', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 0', () => {
+ testInputClickEvent([0], '0');
+ });
+ it('button 1', () => {
+ testInputClickEvent([1], '1');
+ });
+ it('button 2', () => {
+ testInputClickEvent([2], '2');
+ });
+ it('button 3', () => {
+ testInputClickEvent([3], '3');
+ });
+ it('button 4', () => {
+ testInputClickEvent([4], '4');
+ });
+ it('button 5', () => {
+ testInputClickEvent([5], '5');
+ });
+ it('button 6', () => {
+ testInputClickEvent([6], '6');
+ });
+ it('button 7', () => {
+ testInputClickEvent([7], '7');
+ });
+ it('button 8', () => {
+ testInputClickEvent([8], '8');
+ });
+ it('button 9', () => {
+ testInputClickEvent([9], '9');
+ });
+});
+
+describe('Render max input length 3 when button click', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 2', () => {
+ testInputClickEvent([1, 2], '12');
+ });
+ it('button 4 2', () => {
+ testInputClickEvent([4, 2], '42');
+ });
+ it('button 0 2', () => {
+ testInputClickEvent([0, 2], '2');
+ });
+ it('button 1 2 3', () => {
+ testInputClickEvent([1, 2, 3], '123');
+ });
+ it('button 1 2 3 4', () => {
+ testInputClickEvent([1, 2, 3, 4], '123');
+ });
+});
+
+describe('Calculate two input when button click', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 + 2', () => {
+ testTwoInputCalculateEvent([1], ['+'], [2], '3');
+ });
+ it('button 1234567 + 1234567', () => {
+ testTwoInputCalculateEvent(
+ [1, 2, 3, 4, 5, 6, 7],
+ ['+'],
+ [1, 2, 3, 4, 5, 6, 7],
+ '246'
+ );
+ });
+ it('button 42424242 + 42424242', () => {
+ testTwoInputCalculateEvent(
+ [4, 2, 4, 2, 4, 2, 4, 2],
+ ['+'],
+ [4, 2, 4, 2, 4, 2, 4, 2],
+ '848'
+ );
+ });
+});
+
+describe('계산 결과를 표현할 때 소수점 이하는 버림한다.', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 / 3', () => {
+ testTwoInputCalculateEvent([1], ['/'], [3], '0');
+ });
+ it('button 3 / 2', () => {
+ testTwoInputCalculateEvent([3], ['/'], [2], '1');
+ });
+ it('button 5 / 2', () => {
+ testTwoInputCalculateEvent([5], ['/'], [2], '2');
+ });
+});
+
+describe('AC(All Clear) 버튼을 누를때 total을 0으로 변경한다.', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 / 3', () => {
+ testTwoInputCalculateEvent([5], ['/'], [2], '2');
+ cy.get('.modifier').click();
+ cy.get('#total').should('have.text', '0');
+ });
+ it('button 1234567 + 1234567', () => {
+ testTwoInputCalculateEvent([1, 2, 3, 4], ['+'], [1, 2, 3, 4], '246');
+ cy.get('.modifier').click();
+ cy.get('#total').should('have.text', '0');
+ });
+});
+
+describe('operator가 연속으로 나왔을때 한개만 적용되도록 처리', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 /// 3', () => {
+ testTwoInputCalculateEvent([5], ['/', '/', '/'], [2], '2');
+ cy.get('.modifier').click();
+ cy.get('#total').should('have.text', '0');
+ });
+ it('button 4 +++++ 2', () => {
+ testTwoInputCalculateEvent([4], ['+', '+', '+', '+', '+'], [2], '6');
+ cy.get('.modifier').click();
+ cy.get('#total').should('have.text', '0');
+ });
+});
+
+describe('operator가 2번이상 나왔을때 한개만 적용되도록 처리', () => {
+ beforeEach(() => {
+ cy.visit('/javascript-calculator/');
+ });
+ it('button 1 + 3 + =', () => {
+ cy.get('.digit')
+ .contains('4')
+ .click();
+ cy.get('.operations')
+ .contains('+')
+ .click();
+ cy.get('.digit')
+ .contains('2')
+ .click();
+ cy.get('.operations')
+ .contains('+')
+ .click();
+ cy.get('.operations')
+ .contains('=')
+ .click();
+ cy.get('#total').should('have.text', '6');
+ });
+});
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
new file mode 100644
index 0000000..59b2bab
--- /dev/null
+++ b/cypress/plugins/index.js
@@ -0,0 +1,22 @@
+///
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+// eslint-disable-next-line no-unused-vars
+module.exports = (on, config) => {
+ // `on` is used to hook into various events Cypress emits
+ // `config` is the resolved Cypress config
+}
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 0000000..119ab03
--- /dev/null
+++ b/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
diff --git a/cypress/support/index.js b/cypress/support/index.js
new file mode 100644
index 0000000..d68db96
--- /dev/null
+++ b/cypress/support/index.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/src/css/index.css b/dist/css/index.css
similarity index 100%
rename from src/css/index.css
rename to dist/css/index.css
diff --git a/src/images/calculator_icon.png b/dist/images/calculator_icon.png
similarity index 100%
rename from src/images/calculator_icon.png
rename to dist/images/calculator_icon.png
diff --git a/src/images/calculator_ui.png b/dist/images/calculator_ui.png
similarity index 100%
rename from src/images/calculator_ui.png
rename to dist/images/calculator_ui.png
diff --git a/dist/js/calculate-util.js b/dist/js/calculate-util.js
new file mode 100644
index 0000000..3af0449
--- /dev/null
+++ b/dist/js/calculate-util.js
@@ -0,0 +1,22 @@
+const calculateOperator = (total, operator, num) => {
+ let result = 0;
+ if (operator === '+')
+ result = total + num;
+ if (operator === '/')
+ result = total / num;
+ if (operator === '%')
+ result = total % num;
+ if (operator === '-')
+ result = total - num;
+ if (operator === 'X')
+ result = total * num;
+ return result;
+};
+const calculateResult = (total) => {
+ const result = total.match(new RegExp('(\\-?[\\d]{1,3})(X|\\-|\\+|\\/|\\=)(\\-?[\\d]{1,3})'));
+ if (result) {
+ return calculateOperator(Number(result[1]), result[2], Number(result[3]));
+ }
+ return 0;
+};
+export { calculateResult };
diff --git a/dist/js/check.js b/dist/js/check.js
new file mode 100644
index 0000000..acaea30
--- /dev/null
+++ b/dist/js/check.js
@@ -0,0 +1,16 @@
+export const checkValidInput = (total) => {
+ const result = total.match(new RegExp('(\\-?[\\d]{1,3})(X|\\-|\\+|\\/|\\=)?(\\-?[\\d]{1,3})?'));
+ if (!result) {
+ return '';
+ }
+ if (result[2] === undefined) {
+ if (result[1] === undefined) {
+ return '';
+ }
+ return result[1];
+ }
+ if (result[3] === undefined) {
+ return result[1] + result[2];
+ }
+ return result[1] + result[2] + result[3];
+};
diff --git a/dist/js/controller.js b/dist/js/controller.js
new file mode 100644
index 0000000..0dedc86
--- /dev/null
+++ b/dist/js/controller.js
@@ -0,0 +1,40 @@
+import { getCountOperationdataSet, setCountOperationdataSet } from './operation-data-set.js';
+import { checkValidInput } from './check.js';
+import { calculateResult } from './calculate-util.js';
+const digitClickEvent = (e) => {
+ const eventTarget = e.target;
+ const totalTarget = document.getElementById('total');
+ if (Number(totalTarget.innerText) === 0) {
+ document.getElementById('total').innerText = eventTarget.innerText;
+ }
+ else {
+ const result = checkValidInput(document.getElementById('total').innerText + eventTarget.innerText);
+ if (result === '') {
+ return;
+ }
+ document.getElementById('total').innerText = result;
+ }
+};
+const operatorEvent = (e) => {
+ const eventTarget = e.target;
+ const totalTarget = document.getElementById('total');
+ if (eventTarget.innerText === '=') {
+ setCountOperationdataSet('0');
+ document.getElementById('total').innerText = String(Math.floor(calculateResult(document.getElementById('total').innerText)));
+ return;
+ }
+ if (Number(totalTarget.innerText) === 0) {
+ return;
+ }
+ else if (!['/', '-', 'X', '+'].includes(totalTarget.innerText[totalTarget.innerText.length - 1])) {
+ if (getCountOperationdataSet() === '1') {
+ return;
+ }
+ setCountOperationdataSet('1');
+ document.getElementById('total').innerText += eventTarget.innerText;
+ }
+};
+const ACEvent = () => {
+ document.getElementById('total').innerText = '0';
+};
+export { digitClickEvent, operatorEvent, ACEvent };
diff --git a/dist/js/index.js b/dist/js/index.js
new file mode 100644
index 0000000..4119c7d
--- /dev/null
+++ b/dist/js/index.js
@@ -0,0 +1,19 @@
+import { init } from './init.js';
+import { digitClickEvent, operatorEvent, ACEvent } from './controller.js';
+export default function App() {
+ init();
+ document.getElementsByClassName('digits')[0].addEventListener('click', e => {
+ digitClickEvent(e);
+ });
+ document
+ .getElementsByClassName('operations')[0]
+ .addEventListener('click', e => {
+ operatorEvent(e);
+ });
+ document
+ .getElementsByClassName('modifier')[0]
+ .addEventListener('click', () => {
+ ACEvent();
+ });
+}
+App();
diff --git a/dist/js/init.js b/dist/js/init.js
new file mode 100644
index 0000000..b46420c
--- /dev/null
+++ b/dist/js/init.js
@@ -0,0 +1,4 @@
+import { setCountOperationdataSet } from './operation-data-set.js';
+export const init = () => {
+ setCountOperationdataSet('0');
+};
diff --git a/dist/js/operation-data-set.js b/dist/js/operation-data-set.js
new file mode 100644
index 0000000..50f426c
--- /dev/null
+++ b/dist/js/operation-data-set.js
@@ -0,0 +1,9 @@
+const getCountOperationdataSet = () => {
+ const operations = document.getElementsByClassName('operations')[0];
+ return operations.dataset['count'] ? operations.dataset['count'] : '';
+};
+const setCountOperationdataSet = (count) => {
+ const operations = document.getElementsByClassName('operations')[0];
+ operations.dataset['count'] = count;
+};
+export { getCountOperationdataSet, setCountOperationdataSet };
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..31a1558
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,29 @@
+## 기본 환경 세팅
+- [x] gitignore
+- [x] prettier
+- [x] eslint
+- [x] typescript
+- [x] cypress
+ - [x] tsconfig
+ - [x] cypress.json
+- [x] package.json
+
+## test case (BDD: Given, When, Then 구조를 정하자)
+
+- [x] cypress examples 둘러보기.
+
+**Given : 시나리오 진행에 필요한 값을 설정한다.**
+**When : 시나리오를 진행하는데 필요한 조건을 명시한다.**
+**Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다.**
+
+- [x] 초기 test case 작성
+
+## 기능 구현
+
+- [x] 2개의 숫자에 대해 덧셈이 가능하다.
+- [x] 2개의 숫자에 대해 뺄셈이 가능하다.
+- [x] 2개의 숫자에 대해 곱셈이 가능하다.
+- [x] 2개의 숫자에 대해 나눗셈이 가능하다.
+- [x] AC(All Clear)버튼을 누르면 0으로 초기화 한다.
+- [x] 숫자는 한번에 최대 3자리 수까지 입력 가능하다.
+- [x] 계산 결과를 표현할 때 소수점 이하는 버림한다.
diff --git a/index.html b/index.html
index 8bf48ab..f4af11d 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
Calculator
-
+
@@ -34,5 +34,6 @@
0
+