-
Notifications
You must be signed in to change notification settings - Fork 56
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
base: publizm
Are you sure you want to change the base?
Changes from 30 commits
2e41697
6164f6c
740713a
0f2be8a
cc0e90e
b2ac1ce
97f99e3
e3d7715
d85f9b9
a660a76
dfea8eb
20bf3d0
8ca2084
6d7be9f
1d7057a
2fa9f45
d859a21
6b639e4
a5e1764
90e5df7
72fe279
ce25291
86cf9bb
6599d48
1b09fd4
c9efa8e
a3f069a
c648c35
015f492
a63fc10
0e23cee
18ca601
bbee807
d059ffb
876a9fd
65473fe
792198d
2eb9189
b0a216d
80fd4fe
b8fdddc
9e27779
617e1ee
3d4b6ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
} | ||
} |
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 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"plugins": ["prettier-plugin-tailwindcss"], | ||
"tailwindConfig": "./tailwind.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: {}, | ||
}); |
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, | ||
}); | ||
}); | ||
|
||
it("최초 보유 금액은 0원이다.", () => { | ||
cy.get(HOLDING_AMOUNT_SELECTOR).should("have.text", "0"); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에 중복되는 테스트가 있는 것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엇..🤦 그렇네요 ㅎㅎ... 삭제했습니다 리뷰 반영 커밋 |
||
|
||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (마이너 코멘트) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Tanney-102 공식 문서에 type 메소드의 argument로 string 값을 받는 걸로 표기되어 있고, IDE에서 경고를 줘서 String 생성자 함수로 강제 변환을 시켰습니다! 혹시 type에서 number 타입으로 입력되더라도 string 값으로 타입변환이 되는걸까요?? 별도로 문구가 안보여가지구요! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]}개`); | ||
}); | ||
}); | ||
}); |
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"); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
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 밑에 에러를 보여주는지 테스트 케이스만 보고도 알 수 있었으면 합니다.
사용자가
어떤 행동(입력)
을 했을 때 프로그램이어떤 행동(출력)
을 하는지 구체적으로 서술해주세요.There was a problem hiding this comment.
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