Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[클린코드 4기 이철환] 자판기 미션 STEP 1 #61

Open
wants to merge 44 commits into
base: publizm
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2e41697
temp: 세팅중
go-lani Dec 25, 2022
6164f6c
style: 상품 관리, 잔돈 충전 영역 UI 구현 및 템플릿화
go-lani Dec 28, 2022
740713a
Merge branch 'setting' into main
go-lani Dec 28, 2022
0f2be8a
fix: html class attribute 오타 수정
go-lani Jan 4, 2023
cc0e90e
feat: 아키텍쳐 세팅
go-lani Jan 4, 2023
b2ac1ce
feat: initial rendering 구현
go-lani Jan 5, 2023
97f99e3
style: 레이아웃 css 수정
go-lani Jan 6, 2023
e3d7715
fix: 메뉴 id 의미에 맞게 수정
go-lani Jan 6, 2023
d85f9b9
test: 메뉴 테스트 / 메뉴별 테스트 분리
go-lani Jan 6, 2023
a660a76
refactor: View에 대한 부모 클래스 생성 및 상속 적용
go-lani Jan 6, 2023
dfea8eb
refactor: 불필요한 View 모델 삭제 및 로직 제거
go-lani Jan 6, 2023
20bf3d0
refactor: Template 관련 변수 리팩토링
go-lani Jan 6, 2023
8ca2084
test: 메뉴 탭 관련 테스트코드 작성 완료
go-lani Jan 6, 2023
6d7be9f
refactor: 메뉴탭 클릭이벤트 validation 로직 수정
go-lani Jan 8, 2023
1d7057a
temp: submit 구현중
go-lani Jan 8, 2023
2fa9f45
test: 상품 관리 테스트 케이스 추가
go-lani Jan 10, 2023
d859a21
feat: 상품 추가하기 로직 추가 및 테스트코드 작성
go-lani Jan 10, 2023
6b639e4
feat: 상품 현황 관련 로직 및 테스트 코드 추가
go-lani Jan 11, 2023
a5e1764
test: 잔돈 충전 메뉴 테스트 케이스 작성
go-lani Jan 11, 2023
90e5df7
test: 보유 동전 관련 테스트코드 작성
go-lani Jan 12, 2023
72fe279
refactor: input 입력값 타입 변경
go-lani Jan 12, 2023
ce25291
refactor: element 변수명 변경
go-lani Jan 13, 2023
86cf9bb
refactor: argument 타입 변경
go-lani Jan 13, 2023
6599d48
refactor: Dom Selector 변수명 컨벤션에 따른 수정
go-lani Jan 13, 2023
1b09fd4
refactor: 상수값 네이밍 유연하게 변경
go-lani Jan 13, 2023
c9efa8e
refactor: argument 타입 변경 #2
go-lani Jan 13, 2023
a3f069a
feat: 잔돈 충전 기능 구현 및 테스트 코드 추가
go-lani Jan 13, 2023
c648c35
style: 코드 정리
go-lani Jan 13, 2023
015f492
feat: localStorage 기능 추가
go-lani Jan 13, 2023
a63fc10
feat: 메뉴 관련 localStorage 기능 추가 및 리팩토링
go-lani Jan 13, 2023
0e23cee
Merge pull request #1 from go-lani/step1
go-lani Jan 13, 2023
18ca601
refactor: 테스트 케이스 문구 디테일 수정
go-lani Jan 31, 2023
bbee807
refactor: 중복 테스트 케이스 삭제
go-lani Jan 31, 2023
d059ffb
refactor: vendinMachine > submitChargerForm 메소드 dom 의존성 제거
go-lani Jan 31, 2023
876a9fd
refactor: 안 쓰는 멤버 제거
go-lani Jan 31, 2023
65473fe
refactor: vendingMachine controller initialize 메소드 추가 및 menuItem 관련 로…
go-lani Jan 31, 2023
792198d
refactor: handlers > submitFormHandlers 범용적인 변수명 수정
go-lani Jan 31, 2023
2eb9189
refactor: validate error 핸들링 수정 및 함수 리팩토링
go-lani Jan 31, 2023
b0a216d
refactor: 제품관리 추가 로직 수정 forEach > reduce
go-lani Jan 31, 2023
80fd4fe
refactor: validateMenu 메소드 매개변수명 수정 및 메뉴 여부 체크 로직 수정
go-lani Jan 31, 2023
b8fdddc
refactor: 직관적인 변수명으로 변경 changeView > initializeBasedOnChangedMenu
go-lani Jan 31, 2023
9e27779
refactor: clearForm 로직 공용 유틸함수로 분리
go-lani Jan 31, 2023
617e1ee
refactor: view initialize 로직 render와 update 분리
go-lani Feb 1, 2023
3d4b6ed
refactor: view에 대한 의존성 제거 및 view update method 추상화
go-lani Feb 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"env": {
"browser": true,
"es2022": true,
"node": true
},
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"extends": ["airbnb", "plugin:prettier/recommended", "plugin:cypress/recommended"],
"rules": {
"import/extensions": [
"off"
],
"max-depth": ["error", 2],
"lines-between-class-members": "off",
"class-methods-use-this": "off",
"import/prefer-default-export": "off",
"no-unused-expressions": "off",
"no-param-reassign": "off"
}
}
107 changes: 107 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port


.idea
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindConfig": "./tailwind.config.js"
}
6 changes: 6 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {},
});
148 changes: 148 additions & 0 deletions cypress/e2e/change-charger.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {
CHANGE_CHARGE_MENU_SELECTOR,
CHARGER_INPUT_SELECTOR,
COIN_AMOUNT_SELECTOR,
COIN_CHARGE_BUTTON_SELECTOR,
COIN_CHARGING_FORM_SELECTOR,
COIN_INVENTORY_SELECTOR,
COIN_UNIT_SELECTOR,
HOLDING_AMOUNT_SELECTOR,
PRODUCT_MANAGE_MENU_SELECTOR,
} from "../support/selectors.js";
import { ERROR_MESSAGE, MINIMUM_CHARGE_PRICE } from "../support/constants";
import { calculateCoinCount } from "../../src/js/utils/utils.js";

describe("잔돈 충전 테스트", () => {
beforeEach(() => {
cy.visit("/");
cy.get(CHANGE_CHARGE_MENU_SELECTOR).click();
});

context("잔돈 충전을 할 수 있다.", () => {
it("잔돈 충전 입력 폼이 보인다", () => {
cy.get(COIN_CHARGING_FORM_SELECTOR).should("exist");
});

it("잔돈을 입력할 수 있는 Input이 존재한다.", () => {
cy.get(CHARGER_INPUT_SELECTOR).should("exist");
});

it("최초 보유한 금액은 0원이다", () => {
cy.get(HOLDING_AMOUNT_SELECTOR).should("have.text", "0");
});

it("100원부터 충전이 가능하며 잘못 입력시 alert가 뜬다", () => {
cy.alert({
action: () => {
cy.get(CHARGER_INPUT_SELECTOR).type("50");
return cy.get(COIN_CHARGE_BUTTON_SELECTOR).click();
},
message: ERROR_MESSAGE.INVALID_AMOUNT,
});
});

it("잔돈은 10원 단위로 충전이 가능하다", () => {
cy.alert({
action: () => {
cy.get(CHARGER_INPUT_SELECTOR).type("101");
return cy.get(COIN_CHARGE_BUTTON_SELECTOR).click();
},
message: ERROR_MESSAGE.INVALID_UNIT,
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소하긴 하지만 테스트 케이스랑 실제 테스트 코드랑 조금 상이해 보여요.
코드는 구체적인 반면 테스트케이스 텍스트가 조금 모호한 것 같아요.
잔돈은 10원 단위로 충전이 가능하다라는 텍스트만 봐서는 실제 view에 대한 기획이 잘 안그려집니다.
사용자가 실제로 10원을 입력했을 때 정확히 어떤 일이 일어나나요? 혹은 11원을 입력했을 때는 어떤 일이 일어나나요?
그리고 에러는 alert로 보여주는지, 토스트와 같은 UI로 보여주는지, input 밑에 에러를 보여주는지 테스트 케이스만 보고도 알 수 있었으면 합니다.
사용자가 어떤 행동(입력)을 했을 때 프로그램이 어떤 행동(출력)을 하는지 구체적으로 서술해주세요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그렇네요.. 허허 .. 10원 단위로 충전하다를 context로 그룹핑해서 11원, 101원 1001원 이런식의 단위를 전부 테스트를 해야하나 고민했으나 그렇게한다면 어디까지 커버해야될지에 대한 의문이 들어서 테스트 케이스 타이틀만 수정했습니다!

리뷰 반영 커밋
18ca601


it("최초 보유 금액은 0원이다.", () => {
cy.get(HOLDING_AMOUNT_SELECTOR).should("have.text", "0");
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에 중복되는 테스트가 있는 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇..🤦 그렇네요 ㅎㅎ... 삭제했습니다

리뷰 반영 커밋
bbee807


it("잔돈 입력 후 Enter키를 눌러서 충전할 수 있다", () => {
cy.get(CHARGER_INPUT_SELECTOR).type(`${MINIMUM_CHARGE_PRICE}{enter}`);
cy.get(HOLDING_AMOUNT_SELECTOR).should("have.text", MINIMUM_CHARGE_PRICE);
});

it("잔돈 입력후 충전하기 버튼을 눌러서 충전할 수 있다", () => {
cy.get(CHARGER_INPUT_SELECTOR).type(String(MINIMUM_CHARGE_PRICE));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(마이너 코멘트)
String으로 캐스팅 안해도 type 메서드를 거치면 알아서 string으로 들어갈겁니다.
input value는 항상 string이니까요!

Copy link
Author

@go-lani go-lani Jan 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tanney-102 공식 문서에 type 메소드의 argument로 string 값을 받는 걸로 표기되어 있고, IDE에서 경고를 줘서 String 생성자 함수로 강제 변환을 시켰습니다!
image

image

혹시 type에서 number 타입으로 입력되더라도 string 값으로 타입변환이 되는걸까요?? 별도로 문구가 안보여가지구요!

Cypress type Link

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

피드백 후 반영하도록 하겠습니다!


cy.get(COIN_CHARGE_BUTTON_SELECTOR)
.click()
.then(() => {
cy.get(HOLDING_AMOUNT_SELECTOR).should(
"have.text",
MINIMUM_CHARGE_PRICE
);
});
});

it("잔돈은 누적하여 충전할 수 있다", () => {
const FIRST_CHARGE = MINIMUM_CHARGE_PRICE;
const SECOND_CHARGE = MINIMUM_CHARGE_PRICE * 2;

cy.get(CHARGER_INPUT_SELECTOR).type(String(FIRST_CHARGE));
cy.get(COIN_CHARGE_BUTTON_SELECTOR).click();

cy.get(CHARGER_INPUT_SELECTOR).type(String(SECOND_CHARGE));
cy.get(COIN_CHARGE_BUTTON_SELECTOR).click();

cy.get(HOLDING_AMOUNT_SELECTOR).should(
"have.text",
String(FIRST_CHARGE + SECOND_CHARGE)
);
});
});

context("보유한 동전을 갯수를 확인할 수 있다.", () => {
it("잔돈 현황을 확인할 수 있는 테이블이 보인다", () => {
cy.get(COIN_INVENTORY_SELECTOR).should("exist");
});

it("최초 보유한 동전의 갯수는 각각 0개이다", () => {
cy.get(COIN_AMOUNT_SELECTOR).each((amount) => {
expect(amount).text("0개");
});
});

it("500원, 100원, 50원, 10원 단위에 따른 동전의 갯수로 표시된다", () => {
const coinUnit = ["500", "100", "50", "10"];
cy.get(COIN_UNIT_SELECTOR).each((unit, index) => {
expect(unit).text(coinUnit[index]);
});
});

it("보유한 동전은 X개 형식으로 확인할 수 있다", () => {
const charge = 1000;
cy.get(CHARGER_INPUT_SELECTOR).type(`${charge}{enter}`);
const result = calculateCoinCount(charge);

cy.get(COIN_AMOUNT_SELECTOR).each((amount) => {
const id = amount.closest("tr").attr("id");
const unit = id.replace("coin-", "");

expect(amount).text(`${result[unit]}개`);
});
});
});

it("다른 메뉴로 이동 후 다시 돌아왔을 경우 값은 유지된다.", () => {
const charge = 1000;
cy.get(CHARGER_INPUT_SELECTOR).type(`${charge}{enter}`);
const result = calculateCoinCount(charge);

cy.get(COIN_AMOUNT_SELECTOR).each((amount) => {
const id = amount.closest("tr").attr("id");
const unit = id.replace("coin-", "");

expect(amount).text(`${result[unit]}개`);
});

cy.get(PRODUCT_MANAGE_MENU_SELECTOR).click();

cy.get(CHANGE_CHARGE_MENU_SELECTOR).click();

cy.get(COIN_AMOUNT_SELECTOR).each((amount) => {
const id = amount.closest("tr").attr("id");
const unit = id.replace("coin-", "");

expect(amount).text(`${result[unit]}개`);
});
});
});
56 changes: 56 additions & 0 deletions cypress/e2e/menu.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
CHANGE_CHARGE_MENU_SELECTOR,
COIN_CHARGING_CONTAINER_SELECTOR,
MENU_SELECTOR,
PRODUCT_MANAGE_MENU_SELECTOR,
PRODUCT_MANAGER_CONTAINER_SELECTOR,
PRODUCT_PURCHASE_CONTAINER_SELECTOR,
PRODUCT_PURCHASE_MENU_SELECTOR,
} from "../support/selectors.js";

describe("메뉴 탭 UI 테스트", () => {
beforeEach(() => {
cy.visit("/");
});
it("메뉴 탭 리스트 UI가 화면에 존재한다.", () => {
cy.get(MENU_SELECTOR).should("exist");
});

it("메뉴는 상품 관리, 잔돈 충전, 상품 구매가 있다.", () => {
cy.get(`${MENU_SELECTOR} button`).should(($menuButton) => {
expect($menuButton).to.have.length(3);

const menuTexts = $menuButton.map((_, el) => el.innerText);

expect(menuTexts.get()).to.deep.equal([
"상품 관리",
"잔돈 충전",
"상품 구매",
]);
});
});

context("각 메뉴 클릭시 해당 메뉴화면이 노출된다.", () => {
it("잔돈 충전 메뉴 클릭시 해당 화면이 노출된다.", () => {
cy.get(CHANGE_CHARGE_MENU_SELECTOR)
.click()
.then(() => {
cy.get(COIN_CHARGING_CONTAINER_SELECTOR).should("exist");
});
});
it("상품 구매 메뉴 클릭시 해당 화면이 노출된다.", () => {
cy.get(PRODUCT_PURCHASE_MENU_SELECTOR)
.click()
.then(() => {
cy.get(PRODUCT_PURCHASE_CONTAINER_SELECTOR).should("exist");
});
});
it("상품 관리 메뉴 클릭시 해당 화면이 노출된다.", () => {
cy.get(PRODUCT_MANAGE_MENU_SELECTOR)
.click()
.then(() => {
cy.get(PRODUCT_MANAGER_CONTAINER_SELECTOR).should("exist");
});
});
});
});
Loading