diff --git a/javascript-vendingmachine-precourse/.gitignore b/javascript-vendingmachine-precourse/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/javascript-vendingmachine-precourse/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/javascript-vendingmachine-precourse/.npmrc b/javascript-vendingmachine-precourse/.npmrc new file mode 100644 index 0000000..c42da84 --- /dev/null +++ b/javascript-vendingmachine-precourse/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/javascript-vendingmachine-precourse/LICENSE b/javascript-vendingmachine-precourse/LICENSE new file mode 100644 index 0000000..2a91af0 --- /dev/null +++ b/javascript-vendingmachine-precourse/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 woowacourse + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/javascript-vendingmachine-precourse/README.md b/javascript-vendingmachine-precourse/README.md new file mode 100644 index 0000000..aebfcb9 --- /dev/null +++ b/javascript-vendingmachine-precourse/README.md @@ -0,0 +1,206 @@ +

+ +

+

자판기

+ +## πŸ” 진행방식 + +- λ―Έμ…˜μ€ **κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­, ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­, 과제 μ§„ν–‰ μš”κ΅¬μ‚¬ν•­** μ„Έ κ°€μ§€λ‘œ κ΅¬μ„±λ˜μ–΄ μžˆλ‹€. +- μ„Έ 개의 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±ν•˜κΈ° μœ„ν•΄ λ…Έλ ₯ν•œλ‹€. 특히 κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° 전에 κΈ°λŠ₯ λͺ©λ‘μ„ λ§Œλ“€κ³ , κΈ°λŠ₯ λ‹¨μœ„λ‘œ 컀밋 ν•˜λŠ” λ°©μ‹μœΌλ‘œ μ§„ν–‰ν•œλ‹€. +- κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­μ— κΈ°μž¬λ˜μ§€ μ•Šμ€ λ‚΄μš©μ€ 슀슀둜 νŒλ‹¨ν•˜μ—¬ κ΅¬ν˜„ν•œλ‹€. + + +## 🎯 κΈ°λŠ₯ μš”κ΅¬ 사항 +λ°˜ν™˜λ˜λŠ” 동전이 μ΅œμ†Œν•œμ΄ λ˜λŠ” 자판기λ₯Ό κ΅¬ν˜„ν•œλ‹€. +### 1) 곡톡 + +상단에 `νƒ­`메뉴가 μ‘΄μž¬ν•˜λ©° 각 탭에 따라 μ μ ˆν•œ κΈ°λŠ₯을 μˆ˜ν–‰ν•œλ‹€. + +- `μƒν’ˆ 관리`탭은 μžνŒκΈ°κ°€ λ³΄μœ ν•˜κ³  μžˆλŠ”Β **μƒν’ˆμ„ μΆ”κ°€**ν•˜λŠ” κΈ°λŠ₯을 μˆ˜ν–‰ν•œλ‹€. +- `μž”λˆ μΆ©μ „`탭은 **μžνŒκΈ°κ°€ λ³΄μœ ν•  κΈˆμ•‘μ„ μΆ©μ „**ν•˜λŠ” κΈ°λŠ₯을 μˆ˜ν–‰ν•œλ‹€. +- `μƒν’ˆ ꡬ맀`탭은 μ‚¬μš©μžκ°€Β **κΈˆμ•‘μ„ νˆ¬μž…**ν•  수 있으며, νˆ¬μž…ν•œ κΈˆμ•‘μ— 맞좰 **μƒν’ˆμ„ ꡬ맀**ν•˜κ³ , 남은 κΈˆμ•‘μ— λŒ€ν•΄μ„œλŠ”Β **μž”λˆμ„ λ°˜ν™˜**ν•˜λŠ” κΈ°λŠ₯을 μˆ˜ν–‰ν•œλ‹€. +- λ‹€λ₯Έ νƒ­μœΌλ‘œ μ΄λ™ν–ˆλ‹€ λŒμ•„μ™€λ„ κΈ°μ‘΄ νƒ­μ˜ μƒνƒœκ°€ μœ μ§€λ˜μ–΄μ•Ό ν•œλ‹€. +- localStorageλ₯Ό μ΄μš©ν•˜μ—¬, μƒˆλ‘œκ³ μΉ¨ν•˜λ”λΌλ„ κ°€μž₯ μ΅œκ·Όμ— μž‘μ—…ν•œ 정보듀을 뢈러올 수 μžˆλ„λ‘ ν•œλ‹€. + +### 2) μƒν’ˆ 관리 νƒ­ + +`μƒν’ˆ 관리`νƒ­μ—μ„œ, λ‹€μŒκ³Ό 같은 κ·œμΉ™μ„ λ°”νƒ•μœΌλ‘œ μƒν’ˆμ„ μΆ”κ°€ν•œλ‹€. + +- 졜초 μƒν’ˆ λͺ©λ‘μ€ λΉ„μ›Œμ§„ μƒνƒœμ΄λ‹€. +- μƒν’ˆλͺ…, 가격, μˆ˜λŸ‰μ„ μž…λ ₯ν•΄ μƒν’ˆμ„ μΆ”κ°€ν•  수 μžˆλ‹€. + - μƒν’ˆ 가격은 100원뢀터 μ‹œμž‘ν•˜λ©°, 10μ›μœΌλ‘œ λ‚˜λˆ„μ–΄ λ–¨μ–΄μ Έμ•Ό ν•œλ‹€. +- μ‚¬μš©μžλŠ” μΆ”κ°€ν•œ μƒν’ˆμ„ 확인할 수 μžˆλ‹€. + +### 3) μž”λˆ μΆ©μ „ νƒ­ (자판기 보유 동전) + +`μž”λˆ μΆ©μ „`Β νƒ­μ—μ„œ, λ‹€μŒκ³Ό 같은 κ·œμΉ™μœΌλ‘œ 자판기 보유 κΈˆμ•‘μ„ μΆ©μ „ν•œλ‹€. + +- `μž”λˆ μΆ©μ „`Β νƒ­μ—μ„œ 졜초 μžνŒκΈ°κ°€ λ³΄μœ ν•œ κΈˆμ•‘μ€ 0원이며, 각 λ™μ „μ˜ κ°œμˆ˜λŠ” 0κ°œμ΄λ‹€. +- μž”λˆ μΆ©μ „ μž…λ ₯ μš”μ†Œμ— μΆ©μ „ν•  κΈˆμ•‘μ„ μž…λ ₯ν•œ ν›„, `μΆ©μ „ν•˜κΈ°` λ²„νŠΌμ„ 눌러 자판기 보유 κΈˆμ•‘μ„ μΆ©μ „ν•  수 μžˆλ‹€. + - 자판기 보유 κΈˆμ•‘μ€Β `{κΈˆμ•‘}원`Β ν˜•μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έλ‹€. +- 자판기 보유 κΈˆμ•‘λ§ŒνΌμ˜ 동전이 λ¬΄μž‘μœ„λ‘œ μƒμ„±λœλ‹€. + - λ™μ „μ˜ κ°œμˆ˜λŠ”Β `{개수}개`Β ν˜•μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έλ‹€. +- 자판기 보유 κΈˆμ•‘μ„ λˆ„μ ν•˜μ—¬ μΆ©μ „ν•  수 μžˆλ‹€. μΆ”κ°€ μΆ©μ „ κΈˆμ•‘λ§ŒνΌμ˜ 동전이 λ¬΄μž‘μœ„λ‘œ μƒμ„±λ˜μ–΄ κΈ°μ‘΄ 동전듀에 더해진닀. +- μƒν’ˆ ꡬ맀 νƒ­μ—μ„œ νˆ¬μž…ν•œ κΈˆμ•‘μ€ 자판기 보유 κΈˆμ•‘μ— λ”ν•˜μ§€ μ•ŠλŠ”λ‹€. + +### 4) μƒν’ˆ ꡬ맀 νƒ­ + +`μƒν’ˆ ꡬ맀`νƒ­μ—μ„œ, λ‹€μŒκ³Ό 같은 κ·œμΉ™μ„ λ°”νƒ•μœΌλ‘œ κΈˆμ•‘μ„ μΆ©μ „ν•˜κ³ , μƒν’ˆμ„ κ΅¬λ§€ν•˜λ©°, μž”λˆμ„ λ°˜ν™˜ν•œλ‹€. + +- `μƒν’ˆ ꡬ맀`Β νŽ˜μ΄μ§€μ—μ„œ 졜초 μΆ©μ „ κΈˆμ•‘μ€ 0원이며, λ°˜ν™˜λœ 각 λ™μ „μ˜ κ°œμˆ˜λŠ” 0κ°œμ΄λ‹€. +- μ‚¬μš©μžλŠ” νˆ¬μž…ν•  κΈˆμ•‘ μž…λ ₯ μš”μ†Œμ— νˆ¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•œ ν›„, `νˆ¬μž…ν•˜κΈ°`λ²„νŠΌμ„ μ΄μš©ν•˜μ—¬ κΈˆμ•‘μ„ νˆ¬μž…ν•œλ‹€. + - κΈˆμ•‘μ€ 10μ›μœΌλ‘œ λ‚˜λˆ„μ–΄ λ–¨μ–΄μ§€λŠ” κΈˆμ•‘λ§Œ νˆ¬μž…ν•  수 μžˆλ‹€. + - μžνŒκΈ°κ°€ λ³΄μœ ν•œ κΈˆμ•‘μ€Β `{κΈˆμ•‘}원`Β ν˜•μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έλ‹€. +- κΈˆμ•‘μ€ λˆ„μ μœΌλ‘œ νˆ¬μž…ν•  수 μžˆλ‹€. +- ν’ˆμ ˆλœ μƒν’ˆμ˜ `κ΅¬λ§€ν•˜κΈ°` λ²„νŠΌμ€ disabled λ˜μ–΄μ•Ό ν•œλ‹€. +- μ‚¬μš©μžλŠ”Β `λ°˜ν™˜ν•˜κΈ°`Β λ²„νŠΌμ„ 톡해 μž”λˆμ„ λ°˜ν™˜ 받을 수 μžˆλ‹€. + +**μƒν’ˆ ꡬ맀 > μž”λˆ 계산 λͺ¨λ“ˆ** + +`μƒν’ˆ ꡬ맀`Β νƒ­μ—μ„œ μž”λˆ λ°˜ν™˜ μ‹œ λ‹€μŒκ³Ό 같은 κ·œμΉ™μ„ 톡해 μž”λˆμ„ λ°˜ν™˜ν•œλ‹€. + +- μž”λˆμ„ λŒλ €μ€„ λ•ŒλŠ” ν˜„μž¬ λ³΄μœ ν•œ μ΅œμ†Œ 개수의 λ™μ „μœΌλ‘œ μž”λˆμ„ λŒλ €μ€€λ‹€. +- 지폐λ₯Ό μž”λˆμœΌλ‘œ λ°˜ν™˜ν•˜λŠ” κ²½μš°λŠ” μ—†λ‹€κ³  κ°€μ •ν•œλ‹€. +- μž”λˆμ„ λ°˜ν™˜ν•  수 μ—†λŠ” 경우 μž”λˆμœΌλ‘œ λ°˜ν™˜ν•  수 μžˆλŠ” κΈˆμ•‘λ§Œ λ°˜ν™˜ν•œλ‹€. +- λ™μ „μ˜ 개수λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ •λ³΄λŠ”Β `{개수}개`Β ν˜•μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έλ‹€. + +--- + +### πŸ’» μ‹€ν–‰ κ²°κ³Ό μ˜ˆμ‹œ + +#### μƒν’ˆ 관리 + + +#### μž”λˆ μΆ©μ „ + + +#### μƒν’ˆ ꡬ맀 및 μž”λˆ λ°˜ν™˜ + + +--- + +## βœ… ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬ 사항 + +### DOM μ„ νƒμž +각 μš”μ†Œμ— μ•„λž˜μ™€ 같은 μ„ νƒμžλ₯Ό λ°˜λ“œμ‹œ μ§€μ •ν•œλ‹€. + +**νƒ­ 메뉴 λ²„νŠΌ** + +- `μƒν’ˆ ꡬ맀` νƒ­μœΌλ‘œ μ΄λ™ν•˜λŠ” 메뉴 λ²„νŠΌ idλŠ”Β `product-purchase-menu`이닀. +- `μž”λˆ μΆ©μ „`νƒ­μœΌλ‘œ μ΄λ™ν•˜λŠ” 메뉴 λ²„νŠΌ idλŠ”Β `vending-machine-manage-menu`이닀. +- `μƒν’ˆ 관리`νƒ­μœΌλ‘œ μ΄λ™ν•˜λŠ” 메뉴 λ²„νŠΌ idλŠ”Β `product-add-menu`이닀. + +**μƒν’ˆ 관리(μΆ”κ°€) 메뉴** + +- μƒν’ˆ μΆ”κ°€ μž…λ ₯ 폼의 μƒν’ˆλͺ… μž…λ ₯ μš”μ†Œμ˜ idλŠ”Β `product-name-input`이닀. +- μƒν’ˆ μΆ”κ°€ μž…λ ₯ 폼의 μƒν’ˆ 가격 μž…λ ₯ μš”μ†Œμ˜ idλŠ”Β `product-price-input`이닀. +- μƒν’ˆ μΆ”κ°€ μž…λ ₯ 폼의 μˆ˜λŸ‰ μž…λ ₯ μš”μ†Œμ˜ idλŠ”Β `product-quantity-input`이닀. +- μƒν’ˆ `μΆ”κ°€ν•˜κΈ°` λ²„νŠΌ μš”μ†Œμ˜ idλŠ”Β `product-add-button`이닀. +- μΆ”κ°€ν•œ 각 μƒν’ˆ μš”μ†Œμ˜ classλͺ…은 `product-manage-item`이며, ν•˜μœ„μ— μ•„λž˜ μš”μ†Œλ“€μ„ κ°–λŠ”λ‹€. + - μƒν’ˆλͺ…에 ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-manage-name`이닀. + - 가격에 ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-manage-price`이닀. + - μˆ˜λŸ‰μ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-manage-quantity`이닀. + +**μž”λˆ μΆ©μ „ (자판기 보유 동전) 메뉴** + +- μžνŒκΈ°κ°€ λ³΄μœ ν•  κΈˆμ•‘μ„ μΆ©μ „ν•  μš”μ†Œμ˜ idλŠ”Β `vending-machine-charge-input`이닀. +- `μΆ©μ „ν•˜κΈ°`Β λ²„νŠΌμ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ idλŠ”Β `vending-machine-charge-button`이닀. +- μΆ©μ „λœ κΈˆμ•‘μ„ ν™•μΈν•˜λŠ” μš”μ†Œμ˜ idλŠ”Β `vending-machine-charge-amount` 이닀. +- λ³΄μœ ν•œ 각 λ™μ „μ˜ κ°œμˆ˜μ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ idλŠ” λ‹€μŒκ³Ό κ°™λ‹€. + - 500원:Β `vending-machine-coin-500-quantity` + - 100원:Β `vending-machine-coin-100-quantity` + - 50원:Β `vending-machine-coin-50-quantity` + - 10원:Β `vending-machine-coin-10-quantity` + +**μƒν’ˆ ꡬ맀 메뉴** + +- νˆ¬μž… κΈˆμ•‘ μž…λ ₯ μš”μ†Œμ˜ idλŠ”Β `charge-input`이닀. +- νˆ¬μž…ν•˜κΈ° λ²„νŠΌ μš”μ†Œμ˜ idλŠ”Β `charge-button`이닀. +- νˆ¬μž…ν•œ κΈˆμ•‘μ„ ν™•μΈν•˜λŠ” μš”μ†Œμ˜ idλŠ”Β `charge-amount`이닀. +- λ°˜ν™˜ν•˜κΈ° λ²„νŠΌ μš”μ†Œμ˜ idλŠ”Β `coin-return-button`이닀. +- λ°˜ν™˜λœ 각 λ™μ „μ˜ κ°œμˆ˜μ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ idλŠ” λ‹€μŒκ³Ό κ°™λ‹€. + - 500원:Β `coin-500-quantity` + - 100원:Β `coin-100-quantity` + - 50원:Β `coin-50-quantity` + - 10원:Β `coin-10-quantity` +- 각 μƒν’ˆ μš”μ†Œμ˜ classλͺ…은 `product-purchase-item`이고, ν•˜μœ„μ— μ•„λž˜ μš”μ†Œλ“€μ„ κ°–λŠ”λ‹€. + - ꡬ맀 λ²„νŠΌμ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `purchase-button`이닀. + - μƒν’ˆλͺ…에 ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-purchase-name`이닀. + - 가격에 ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-purchase-price`이닀. + - μˆ˜λŸ‰μ— ν•΄λ‹Ήν•˜λŠ” μš”μ†Œμ˜ classλͺ…은 `product-purchase-quantity`이닀. + - μƒν’ˆλͺ…은 `dataset` 속성을 μ‚¬μš©ν•˜κ³ Β `data-product-name`Β ν˜•μ‹μœΌλ‘œ μ €μž₯ν•œλ‹€. + - 가격은 `dataset` 속성을 μ‚¬μš©ν•˜κ³ Β `data-product-price`Β ν˜•μ‹μœΌλ‘œ μ €μž₯ν•œλ‹€. + - μˆ˜λŸ‰μ€Β `dataset` 속성을 μ‚¬μš©ν•˜κ³ Β `data-product-quantity`Β ν˜•μ‹μœΌλ‘œ μ €μž₯ν•œλ‹€. + + +--- + +### 라이브러리 +- μž”λˆμ„ λ¬΄μž‘μœ„λ‘œ μƒμ„±ν•˜λŠ” κΈ°λŠ₯은 [`MissionUtils` 라이브러리](https://github.com/woowacourse-projects/javascript-mission-utils#mission-utils)의 `Random.pickNumberInList`λ₯Ό μ‚¬μš©ν•΄ κ΅¬ν•œλ‹€. + - `MissionUtils` 라이브러리 μŠ€ν¬λ¦½νŠΈλŠ” `index.html`에 이미 ν¬ν•¨λ˜μ–΄ μ „μ—­ 객체에 μΆ”κ°€λ˜μ–΄ μžˆμœΌλ―€λ‘œ, λ”°λ‘œ `import` ν•˜μ§€ μ•Šμ•„λ„ κ΅¬ν˜„ μ½”λ“œ μ–΄λ””μ—μ„œλ“  μ‚¬μš©ν•  수 μžˆλ‹€. + + ```javascript + // ex) + const randomNumber = Random.pickNumberInList([10, 50, 100, 500]); + ``` + +--- + +### 곡톡 μš”κ΅¬μ‚¬ν•­ + +- 슀크립트 μΆ”κ°€ 외에 μ£Όμ–΄μ§„Β `index.html`νŒŒμΌμ€ μˆ˜μ •ν•  수 μ—†λ‹€. + - μŠ€νƒ€μΌ(css)은 채점 μš”μ†Œκ°€ μ•„λ‹ˆλ‹€. +- λͺ¨λ“  μ˜ˆμ™Έ λ°œμƒ 상황은 `alert`λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜μ—¬ μ²˜λ¦¬ν•œλ‹€. +- μ™ΈλΆ€ 라이브러리(jQuery, Lodash λ“±)λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , 순수 Vanilla JS둜만 κ΅¬ν˜„ν•œλ‹€. +- **[μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œ μ»¨λ²€μ…˜](https://github.com/woowacourse/woowacourse-docs/tree/feature/styleguide/styleguide/javascript)을 μ§€ν‚€λ©΄μ„œ ν”„λ‘œκ·Έλž˜λ°** ν•œλ‹€. +- **indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό 3이 λ„˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€. 2κΉŒμ§€λ§Œ ν—ˆμš©**ν•œλ‹€. + - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. + - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. +- **ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 ν•˜λ„λ‘ μ΅œλŒ€ν•œ μž‘κ²Œ** λ§Œλ“€μ–΄λΌ. +- λ³€μˆ˜ μ„ μ–Έμ‹œ `var` λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€. `const` 와 `let` 을 μ‚¬μš©ν•œλ‹€. + - [const](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) + - [let](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) +- `import` 문을 μ΄μš©ν•΄ 슀크립트λ₯Ό λͺ¨λ“ˆν™”ν•˜κ³  뢈러올 수 있게 λ§Œλ“ λ‹€. + - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import) +- **ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)의 길이가 15라인을 λ„˜μ–΄κ°€μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€.** + - ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 잘 ν•˜λ„λ‘ κ΅¬ν˜„ν•œλ‹€. + +--- + +## πŸ“ 과제 μ§„ν–‰ μš”κ΅¬μ‚¬ν•­ +- λ―Έμ…˜μ€ [javascript-vendingmachine-precourse](https://github.com/woowacourse/javascript-vendingmachine-precourse/) μ €μž₯μ†Œλ₯Ό Fork/Cloneν•΄ μ‹œμž‘ν•œλ‹€. +- **κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° 전에 javascript-vendingmachine-precourse/docs/README.md νŒŒμΌμ— κ΅¬ν˜„ν•  κΈ°λŠ₯ λͺ©λ‘μ„ 정리**ν•΄ μΆ”κ°€ν•œλ‹€. +- **Git의 컀밋 λ‹¨μœ„λŠ” μ•ž λ‹¨κ³„μ—μ„œ README.md νŒŒμΌμ— μ •λ¦¬ν•œ κΈ°λŠ₯ λͺ©λ‘ λ‹¨μœ„**둜 μΆ”κ°€ν•œλ‹€. + - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) μ°Έκ³ ν•΄ commit logλ₯Ό 남긴닀. +- 과제 μ§„ν–‰ 및 제좜 방법은 [ν”„λ¦¬μ½”μŠ€ 과제 제좜 λ¬Έμ„œ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) λ₯Ό μ°Έκ³ ν•œλ‹€. + +## βœ‰οΈ λ―Έμ…˜ 제좜 방법 + +- λ―Έμ…˜ κ΅¬ν˜„μ„ μ™„λ£Œν•œ ν›„ GitHub을 톡해 μ œμΆœν•΄μ•Ό ν•œλ‹€. + - GitHub을 ν™œμš©ν•œ 제좜 방법은 [ν”„λ¦¬μ½”μŠ€ 과제 제좜 λ¬Έμ„œ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) λ₯Ό μ°Έκ³ ν•΄ μ œμΆœν•œλ‹€. +- GitHub에 λ―Έμ…˜μ„ μ œμΆœν•œ ν›„ [μš°μ•„ν•œν…Œν¬μ½”μŠ€ 지원 ν”Œλž«νΌ](https://apply.techcourse.co.kr) 에 μ ‘μ†ν•˜μ—¬ ν”„λ¦¬μ½”μŠ€ 과제λ₯Ό μ œμΆœν•œλ‹€. + - μžμ„Έν•œ 방법은 [링크](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제좜-κ°€μ΄λ“œ) λ₯Ό μ°Έκ³ ν•œλ‹€. + - **Pull Request만 보내고, 지원 ν”Œλž«νΌμ—μ„œ 과제λ₯Ό μ œμΆœν•˜μ§€ μ•ŠμœΌλ©΄ μ΅œμ’… μ œμΆœν•˜μ§€ μ•Šμ€ κ²ƒμœΌλ‘œ μ²˜λ¦¬λ˜λ‹ˆ μ£Όμ˜ν•œλ‹€.** + + +### 🚨 과제 제좜 μ „ 체크리슀트 - 0점 λ°©μ§€λ₯Ό μœ„ν•œ μ£Όμ˜μ‚¬ν•­ +- μš”κ΅¬μ‚¬ν•­μ— λͺ…μ‹œλœ 좜λ ₯κ°’ ν˜•μ‹μ„ μ§€ν‚€μ§€ μ•Šμ„ 경우 κΈ°λŠ₯ κ΅¬ν˜„μ„ λͺ¨λ‘ μ •μƒμ μœΌλ‘œ ν–ˆλ”λΌλ„ 0점으둜 μ²˜λ¦¬λœλ‹€. +- κΈ°λŠ₯ κ΅¬ν˜„μ„ μ™„λ£Œν•œ λ’€ μ•„λž˜ κ°€μ΄λ“œμ— 따라 ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν–ˆμ„ λ•Œ λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜λŠ” μ§€ ν™•μΈν•œλ‹€. **ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•  경우 0점으둜 μ²˜λ¦¬λ˜λ―€λ‘œ, λ°˜λ“œμ‹œ 확인 ν›„ μ œμΆœν•œλ‹€.** + +### βœ”οΈ ν…ŒμŠ€νŠΈ μ‹€ν–‰ κ°€μ΄λ“œ +- ν…ŒμŠ€νŠΈ 싀행에 ν•„μš”ν•œ νŒ¨ν‚€μ§€ μ„€μΉ˜λ₯Ό μœ„ν•΄ `Node.js` 버전 `14` 이상이 ν•„μš”ν•˜λ‹€. +- λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯ν•΄ νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ν•œλ‹€. +```bash +// {폴더 경둜}/javascript-vendingmachine-precourse/ μ—μ„œ +npm install +``` + +- μ„€μΉ˜κ°€ μ™„λ£Œλ˜μ—ˆλ‹€λ©΄, λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•œλ‹€. +```bash +// {폴더 경둜}/javascript-vendingmachine-precourse/ μ—μ„œ +npm run test +``` + +- μ•„λž˜μ™€ 같은 화면이 λ‚˜μ˜€λ©° λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ passν•œλ‹€λ©΄ 성곡! + +![ν…ŒμŠ€νŠΈ κ²°κ³Ό](./images/test_result.png) diff --git a/javascript-vendingmachine-precourse/cypress.json b/javascript-vendingmachine-precourse/cypress.json new file mode 100644 index 0000000..a78fa83 --- /dev/null +++ b/javascript-vendingmachine-precourse/cypress.json @@ -0,0 +1,9 @@ +{ + "integrationFolder": "test", + "testFiles": "*.spec.js", + "screenshotOnRunFailure": false, + "video": false, + "pluginsFile": false, + "supportFile": false, + "blockHosts": ["cdn.jsdelivr.net"] +} diff --git a/javascript-vendingmachine-precourse/images/beverage_icon.png b/javascript-vendingmachine-precourse/images/beverage_icon.png new file mode 100644 index 0000000..cfed9e2 Binary files /dev/null and b/javascript-vendingmachine-precourse/images/beverage_icon.png differ diff --git a/javascript-vendingmachine-precourse/images/demo_coin.gif b/javascript-vendingmachine-precourse/images/demo_coin.gif new file mode 100644 index 0000000..05457a2 Binary files /dev/null and b/javascript-vendingmachine-precourse/images/demo_coin.gif differ diff --git a/javascript-vendingmachine-precourse/images/demo_product.gif b/javascript-vendingmachine-precourse/images/demo_product.gif new file mode 100644 index 0000000..ce631e2 Binary files /dev/null and b/javascript-vendingmachine-precourse/images/demo_product.gif differ diff --git a/javascript-vendingmachine-precourse/images/demo_purchase.gif b/javascript-vendingmachine-precourse/images/demo_purchase.gif new file mode 100644 index 0000000..12e4470 Binary files /dev/null and b/javascript-vendingmachine-precourse/images/demo_purchase.gif differ diff --git a/javascript-vendingmachine-precourse/images/test_result.png b/javascript-vendingmachine-precourse/images/test_result.png new file mode 100644 index 0000000..c987571 Binary files /dev/null and b/javascript-vendingmachine-precourse/images/test_result.png differ diff --git a/javascript-vendingmachine-precourse/index.html b/javascript-vendingmachine-precourse/index.html new file mode 100644 index 0000000..f17c898 --- /dev/null +++ b/javascript-vendingmachine-precourse/index.html @@ -0,0 +1,12 @@ + + + + + 자판기 + + + + +
+ + diff --git a/javascript-vendingmachine-precourse/package-lock.json b/javascript-vendingmachine-precourse/package-lock.json new file mode 100644 index 0000000..20beeee --- /dev/null +++ b/javascript-vendingmachine-precourse/package-lock.json @@ -0,0 +1,1359 @@ +{ + "name": "javascript-vendingmachine-precourse", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@cypress/request": { + "version": "2.88.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", + "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@types/node": { + "version": "14.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", + "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==", + "dev": true + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", + "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", + "dev": true + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, + "@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, + "ci-info": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-table3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", + "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "optional": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cypress": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-8.7.0.tgz", + "integrity": "sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q==", + "dev": true, + "requires": { + "@cypress/request": "^2.88.6", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "^6.0.2", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.0", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.5", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "ramda": "~0.27.1", + "request-progress": "^3.0.0", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "url": "^0.11.0", + "yauzl": "^2.10.0" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eventemitter2": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", + "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "dev": true + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "requires": { + "pify": "^2.2.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "requires": { + "async": "^3.2.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "requires": { + "ci-info": "^3.2.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, + "listr2": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", + "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.4.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "dev": true + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "dev": true, + "requires": { + "tslib": "~2.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/javascript-vendingmachine-precourse/package.json b/javascript-vendingmachine-precourse/package.json new file mode 100644 index 0000000..5790c81 --- /dev/null +++ b/javascript-vendingmachine-precourse/package.json @@ -0,0 +1,28 @@ +{ + "name": "javascript-vendingmachine-precourse", + "version": "1.0.0", + "description": "μš°μ•„ν•œν…Œν¬μ½”μŠ€ ν”„λ¦¬μ½”μŠ€ 자판기 λ―Έμ…˜", + "dependencies": {}, + "devDependencies": { + "cypress": "8.7.0" + }, + "scripts": { + "test": "cypress run --browser chrome" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/woowacourse/javascript-vendingmachine-precourse.git" + }, + "keywords": [], + "author": "woowacourse", + "license": "MIT", + "bugs": { + "url": "https://github.com/woowacourse/javascript-vendingmachine-precourse/issues" + }, + "homepage": "https://github.com/woowacourse/javascript-vendingmachine-precourse#readme", + "engineStrict": true, + "engines": { + "npm": ">=6.0.0", + "node": ">=14.0.0" + } +} diff --git a/javascript-vendingmachine-precourse/src/js/app.js b/javascript-vendingmachine-precourse/src/js/app.js new file mode 100644 index 0000000..0c43771 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/app.js @@ -0,0 +1,102 @@ +import Component from "./core/Component.js"; +import { $ } from "./utils/dom.js"; +import { + ChangeFill, + NavButton, + ProductManage, + ProductPurchase, +} from "./components/index.js"; + +export default class App extends Component { + init() { + this.state = this.props; + } + + componentDidMount() { + new NavButton(this.domNode, { + changeCurrentTab: this.changeCurrentTab.bind(this), + }); + this.domNode.insertAdjacentHTML("beforeend", '
'); + + const $main = $(".main"); + + if (this.state.currentTab === "product-add-menu") { + new ProductManage($main, { + productList: this.state.productList, + addProduct: this.addProduct.bind(this), + }); + } else if (this.state.currentTab === "vending-machine-manage-menu") { + new ChangeFill($main, { + holdingMoney: this.state.holdingMoney, + coinList: this.state.coinList, + chargeChange: this.chargeChange.bind(this), + }); + } else { + new ProductPurchase($main, { + inputMoney: this.state.inputMoney, + productList: this.state.productList, + coinList: this.state.coinList, + payCoinList: this.state.payCoinList, + holdingMoney: this.state.holdingMoney, + changeInputMoney: this.changeInputMoney.bind(this), + chargeChange: this.chargeChange.bind(this), + purchaseProduct: this.purchaseProduct.bind(this), + returnChange: this.returnChange.bind(this), + }); + } + } + + changeCurrentTab(newTab) { + this.setState({ + ...this.state, + currentTab: newTab, + }); + } + + addProduct(name, price, quantity) { + this.setState({ + ...this.state, + productList: [...this.state.productList, { name, price, quantity }], + }); + } + + chargeChange(amount, newCoinList) { + this.setState({ + ...this.state, + holdingMoney: amount, + coinList: newCoinList, + }); + } + + changeInputMoney(amount) { + this.setState({ + ...this.state, + inputMoney: amount, + }); + } + + returnChange(newPayCoinList) { + this.setState({ + ...this.state, + payCoinList: newPayCoinList, + }); + } + + purchaseProduct(newProductList, cost) { + this.setState({ + ...this.state, + productList: newProductList, + inputMoney: this.state.inputMoney - cost, + }); + } + + setEvent() { + window.addEventListener("beforeunload", () => { + localStorage.setItem("vending-machine-state", JSON.stringify(this.state)); + }); + } + + // μƒνƒœκ΄€λ¦¬ 할것듀 + // 초기 μ–΄λ–€ 화면인지 ν‘œμ‹œν•˜λŠ” ν™”λ©΄(κΈ°λ³Έ, μƒν’ˆκ΄€λ¦¬, μž”λ™μΆ©μ „, μƒν’ˆκ΅¬λ§€) + // μƒν’ˆν˜„ν™©, 보유 κΈˆμ•‘, μƒν’ˆ ꡬ맀 +} diff --git a/javascript-vendingmachine-precourse/src/js/components/ChangeFill.js b/javascript-vendingmachine-precourse/src/js/components/ChangeFill.js new file mode 100644 index 0000000..87974de --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/components/ChangeFill.js @@ -0,0 +1,65 @@ +import Component from '../core/Component.js'; +import { $, generateCoinList, isMultipleOfTen, isPositiveInteger } from '../utils/index.js'; + +export default class ChangeFill extends Component { + template() { + return ` +
+

자판기 동전 μΆ”κ°€ν•˜κΈ°

+ + +

보유 κΈˆμ•‘: ${this.holdingMoney}원

+

동전 보유 ν˜„ν™©

+ + + + + + + ${Object.entries(this.coinList) + .map(([key, val]) => { + return ` + + + + + `; + }) + .reverse() + .join('')} + +
λ™μ „κ°œμˆ˜
${key}원${val}개
+
+ `; + } + + setEvent() { + const { chargeChange } = this.props; + + $('#vending-machine-charge-button').addEventListener('click', () => { + const amount = Number($('#vending-machine-charge-input').value); + + if (!isPositiveInteger(amount) || !isMultipleOfTen(amount)) { + alert('μΆ©μ „ κΈˆμ•‘μ€ μ–‘μˆ˜μ΄λ©΄μ„œ 10의 λ°°μˆ˜μ΄μ–΄μ•Ό ν•©λ‹ˆλ‹€.'); + return; + } + + const generatedCoinList = generateCoinList(amount); + const newCoinList = { ...this.coinList }; + + Object.entries(generatedCoinList).forEach(([key, val]) => { + newCoinList[key] += val; + }); + + chargeChange(this.holdingMoney + amount, newCoinList); + }); + } + + get holdingMoney() { + return this.props.holdingMoney; + } + + get coinList() { + return this.props.coinList; + } +} diff --git a/javascript-vendingmachine-precourse/src/js/components/NavButton.js b/javascript-vendingmachine-precourse/src/js/components/NavButton.js new file mode 100644 index 0000000..70c3005 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/components/NavButton.js @@ -0,0 +1,22 @@ +import Component from "../core/Component.js"; +import { $ } from "../utils/dom.js"; + +export default class NavButton extends Component { + template() { + return ` + + `; + } + + setEvent() { + const { changeCurrentTab } = this.props; + + $(".nav-button").addEventListener("click", ({ target }) => { + changeCurrentTab(target.id); + }); + } +} diff --git a/javascript-vendingmachine-precourse/src/js/components/ProductManage.js b/javascript-vendingmachine-precourse/src/js/components/ProductManage.js new file mode 100644 index 0000000..c8e23f6 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/components/ProductManage.js @@ -0,0 +1,67 @@ +import Component from "../core/Component.js"; +import { + $, + isMinimumPrice, + isMultipleOfTen, + isPositiveInteger, +} from "../utils/index.js"; + +export default class ProductManage extends Component { + template() { + const { productList } = this.props; + + return ` +
+

μƒν’ˆ μΆ”κ°€ν•˜κΈ°

+ + + + +

μƒν’ˆ ν˜„ν™©

+ + + + + + + + ${productList + .map(({ name, price, quantity }) => { + return ` + + + + + + `; + }) + .join("")} + +
μƒν’ˆλͺ…κ°€κ²©μˆ˜λŸ‰
${name}${price}${quantity}
+
+ `; + } + + setEvent() { + const { addProduct } = this.props; + + $("#product-add-button").addEventListener("click", () => { + const $price = Number($("#product-price-input").value); + const $quantity = Number($("#product-quantity-input").value); + + if (!isMinimumPrice($price) || !isMultipleOfTen($price)) { + alert( + "가격은 μ΅œμ†Œ 100원뢀터 μ‹œμž‘ν•˜λ©° 10μ›μœΌλ‘œ λ‚˜λˆ„μ–΄ λ–¨μ–΄μ Έμ•Ό ν•©λ‹ˆλ‹€." + ); + return; + } + + if (!isPositiveInteger($quantity)) { + alert("μ œλŒ€λ‘œλœ μˆ˜λŸ‰μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); + return; + } + + addProduct($("#product-name-input").value.trim(), $price, $quantity); + }); + } +} diff --git a/javascript-vendingmachine-precourse/src/js/components/ProductPurchase.js b/javascript-vendingmachine-precourse/src/js/components/ProductPurchase.js new file mode 100644 index 0000000..5227c90 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/components/ProductPurchase.js @@ -0,0 +1,150 @@ +import { INITIAL_COIN_LIST } from '../constant/index.js'; +import Component from '../core/Component.js'; +import { $ } from '../utils/dom.js'; +import { generatePayCoinList, isMultipleOfTen, isPositiveInteger } from '../utils/index.js'; + +export default class ProductPurchase extends Component { + template() { + return ` +
+

κΈˆμ•‘ νˆ¬μž…

+ + +

νˆ¬μž…ν•œ κΈˆμ•‘: ${this.inputMoney}원

+

ꡬ맀할 수 μžˆλŠ” μƒν’ˆ ν˜„ν™©

+ + + + + + + + + ${this.productList + .map(({ name, price, quantity }) => { + return ` + + + + + + + `; + }) + .join('')} + +
μƒν’ˆλͺ…κ°€κ²©μˆ˜λŸ‰κ΅¬λ§€
${name}${price}${quantity}
+

μž”λˆ

+ + + + + + + + ${Object.entries(this.payCoinList) + .map(([key, val]) => { + return ` + + + + + `; + }) + .reverse() + .join('')} + +
λ™μ „κ°œμˆ˜
${key}원${val}개
+
+ `; + } + + setEvent() { + const { + changeInputMoney, + purchaseProduct, + coinList, + holdingMoney, + chargeChange, + returnChange, + } = this.props; + + $('#charge-button').addEventListener('click', () => { + const $inputMoney = Number($('#charge-input').value); + + if (!isPositiveInteger($inputMoney) || !isMultipleOfTen($inputMoney)) { + alert('νˆ¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ΄λ©° 10μ›μœΌλ‘œ λ‚˜λˆ„μ–΄ λ–¨μ–΄μ§€λŠ” κΈˆμ•‘λ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.'); + return; + } + + changeInputMoney(this.inputMoney + $inputMoney); + }); + + $('table').addEventListener('click', ({ target }) => { + if (target.className !== 'purchase-button') return; + + const $purchaseTargetFirstElement = target.closest('tr').firstElementChild; + const productName = $purchaseTargetFirstElement.dataset.productName; + const productPrice = Number( + $purchaseTargetFirstElement.nextElementSibling.dataset.productPrice + ); + + if (this.inputMoney < productPrice) { + alert('κ΅¬λ§€ν•˜κΈ° μœ„ν•œ νˆ¬μž…κΈˆμ•‘μ΄ λΆ€μ‘±ν•©λ‹ˆλ‹€.'); + return; + } + + let cost = 0; + + const newProductList = this.productList.map(({ name, price, quantity }) => { + if (name === productName) { + cost = price; + return { name, price, quantity: quantity - 1 }; + } else { + return { name, price, quantity }; + } + }); + + purchaseProduct(newProductList, cost); + }); + + $('#coin-return-button').addEventListener('click', () => { + if (this.inputMoney <= 0) { + alert('λ°˜ν™˜ν•  νˆ¬μž…κΈˆμ•‘μ΄ μ—†μŠ΅λ‹ˆλ‹€.'); + return; + } + + // μž”λˆμ„ λ°˜ν™˜ν•  수 μ—†λŠ” 경우 μž”λˆμœΌλ‘œ λ°˜ν™˜ν•  수 μžˆλŠ” κΈˆμ•‘λ§Œ λ°˜ν™˜ + if (holdingMoney < this.inputMoney) { + changeInputMoney(0); + returnChange({ ...coinList }); + chargeChange(0, { ...INITIAL_COIN_LIST }); + } + + const payCoinList = generatePayCoinList(this.inputMoney, coinList); + const newCoinList = { ...coinList }; + + [10, 50, 100, 500].forEach(coin => { + newCoinList[coin] -= payCoinList[coin]; + }); + + changeInputMoney(0); + returnChange(payCoinList); + chargeChange(holdingMoney - this.inputMoney, newCoinList); + }); + } + + get inputMoney() { + return this.props.inputMoney; + } + + get productList() { + return this.props.productList; + } + + get payCoinList() { + return this.props.payCoinList; + } +} diff --git a/javascript-vendingmachine-precourse/src/js/components/index.js b/javascript-vendingmachine-precourse/src/js/components/index.js new file mode 100644 index 0000000..a8617cd --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/components/index.js @@ -0,0 +1,4 @@ +export { default as NavButton } from "./NavButton.js"; +export { default as ProductManage } from "./ProductManage.js"; +export { default as ChangeFill } from "./ChangeFill.js"; +export { default as ProductPurchase } from "./ProductPurchase.js"; diff --git a/javascript-vendingmachine-precourse/src/js/constant/constants.js b/javascript-vendingmachine-precourse/src/js/constant/constants.js new file mode 100644 index 0000000..a801780 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/constant/constants.js @@ -0,0 +1,14 @@ +export const INITIAL_CURRENT_TAB = "product-add-menu"; + +export const INITIAL_PRODUCT_LIST = []; + +export const INITIAL_HOLDING_MONEY = 0; + +export const INITIAL_COIN_LIST = Object.freeze({ + 500: 0, + 100: 0, + 50: 0, + 10: 0, +}); + +export const INITIAL_INPUT_MONEY = 0; diff --git a/javascript-vendingmachine-precourse/src/js/constant/index.js b/javascript-vendingmachine-precourse/src/js/constant/index.js new file mode 100644 index 0000000..548d69e --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/constant/index.js @@ -0,0 +1 @@ +export * from "./constants.js"; diff --git a/javascript-vendingmachine-precourse/src/js/core/Component.js b/javascript-vendingmachine-precourse/src/js/core/Component.js new file mode 100644 index 0000000..e2e11cf --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/core/Component.js @@ -0,0 +1,33 @@ +export default class Component { + domNode; + + state; + + props; + + constructor(domNode, props) { + this.domNode = domNode; + this.props = props; + this.init(); + this.render(); + this.setEvent(); + } + + init() {} + + template() {} + + componentDidMount() {} + + render() { + this.domNode.innerHTML = this.template(); + this.componentDidMount(); + } + + setEvent() {} + + setState(newState) { + this.state = newState; + this.render(); + } +} diff --git a/javascript-vendingmachine-precourse/src/js/index.js b/javascript-vendingmachine-precourse/src/js/index.js new file mode 100644 index 0000000..5536db7 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/index.js @@ -0,0 +1,21 @@ +import App from "./app.js"; +import { + INITIAL_COIN_LIST, + INITIAL_CURRENT_TAB, + INITIAL_HOLDING_MONEY, + INITIAL_INPUT_MONEY, + INITIAL_PRODUCT_LIST, +} from "./constant/index.js"; +import { $ } from "./utils/index.js"; + +new App( + $("#app"), + JSON.parse(localStorage.getItem("vending-machine-state")) || { + currentTab: INITIAL_CURRENT_TAB, + productList: INITIAL_PRODUCT_LIST, + holdingMoney: INITIAL_HOLDING_MONEY, + coinList: { ...INITIAL_COIN_LIST }, + inputMoney: INITIAL_INPUT_MONEY, + payCoinList: { ...INITIAL_COIN_LIST }, + } +); diff --git a/javascript-vendingmachine-precourse/src/js/utils/dom.js b/javascript-vendingmachine-precourse/src/js/utils/dom.js new file mode 100644 index 0000000..8df86b0 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/utils/dom.js @@ -0,0 +1 @@ +export const $ = (selector) => document.querySelector(selector); diff --git a/javascript-vendingmachine-precourse/src/js/utils/index.js b/javascript-vendingmachine-precourse/src/js/utils/index.js new file mode 100644 index 0000000..c89dc50 --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/utils/index.js @@ -0,0 +1,2 @@ +export * from "./dom.js"; +export * from "./math.js"; diff --git a/javascript-vendingmachine-precourse/src/js/utils/math.js b/javascript-vendingmachine-precourse/src/js/utils/math.js new file mode 100644 index 0000000..e61840a --- /dev/null +++ b/javascript-vendingmachine-precourse/src/js/utils/math.js @@ -0,0 +1,46 @@ +import { INITIAL_COIN_LIST } from "../constant/index.js"; + +export const isPositiveInteger = (number) => + Number.isInteger(number) && number > 0; + +export const isMinimumPrice = (number) => number >= 100; + +export const isMultipleOfTen = (number) => number % 10 === 0; + +export const generateCoinList = (restAmount) => { + const list = { ...INITIAL_COIN_LIST }; + + while (restAmount !== 0) { + const randomNumber = MissionUtils.Random.pickNumberInList([ + 10, 50, 100, 500, + ]); + if (restAmount < randomNumber) continue; + + restAmount -= randomNumber; + list[randomNumber] += 1; + } + + return list; +}; + +export const generatePayCoinList = (inputMoney, coinList) => { + const list = { ...coinList }; + + const payCoinList = { ...INITIAL_COIN_LIST }; + + Object.entries(list) + .reverse() + .forEach(([key, val]) => { + let coin = key; + let count = val; + // 코인이 이것보닀 μž‘κ±°λ‚˜ μΉ΄μš΄νŠΈκ°€ 0이 μ•„λ‹ˆλ©΄ + // console.log(count); + while (inputMoney >= coin && count > 0) { + inputMoney -= coin; + payCoinList[coin] += 1; + count -= 1; + } + }); + + return payCoinList; +}; diff --git a/javascript-vendingmachine-precourse/test/app.spec.js b/javascript-vendingmachine-precourse/test/app.spec.js new file mode 100644 index 0000000..0a09605 --- /dev/null +++ b/javascript-vendingmachine-precourse/test/app.spec.js @@ -0,0 +1,116 @@ +describe("κ΅¬ν˜„ κ²°κ³Όκ°€ μš”κ΅¬μ‚¬ν•­κ³Ό μΌμΉ˜ν•΄μ•Ό ν•œλ‹€.", () => { + const baseUrl = "../index.html"; + const SELECTOR = { + COIN_MENU: "#vending-machine-manage-menu", + COIN_CHARGE_INPUT: "#vending-machine-charge-input", + COIN_CHARGE_BUTTON: "#vending-machine-charge-button", + COIN_500: "#vending-machine-coin-500-quantity", + COIN_100: "#vending-machine-coin-100-quantity", + COIN_50: "#vending-machine-coin-50-quantity", + COIN_10: "#vending-machine-coin-10-quantity", + PRODUCT_MENU: "#product-add-menu", + PRODUCT_NAME_INPUT: "#product-name-input", + PRODUCT_PRICE_INPUT: "#product-price-input", + PRODUCT_QUANTITY_INPUT: "#product-quantity-input", + PRODUCT_ADD_BUTTON: "#product-add-button", + PURCHASE_MENU: "#product-purchase-menu", + PURCHASE_CHARGE_INPUT: "#charge-input", + PURCHASE_CHARGE_AMOUNT: "#charge-amount", + PURCHASE_CHARGE_BUTTON: "#charge-button", + PURCHASE_ITEM_BUTTON: ".purchase-button", + PURCHASE_ITEM_QUANTITY: ".product-purchase-quantity", + }; + + before(() => { + Cypress.Commands.add("stubRandomReturns", (returnValues = []) => { + const randomStub = cy.stub(); + + returnValues.forEach((value, index) => { + randomStub.onCall(index).returns(value); + }); + + cy.visit(baseUrl, { + onBeforeLoad: (window) => { + window.MissionUtils = { + Random: { + pickNumberInList: randomStub, + }, + }; + }, + }); + }); + + Cypress.Commands.add("addProduct", (name, price, quantity) => { + cy.get(SELECTOR.PRODUCT_NAME_INPUT).type(name); + cy.get(SELECTOR.PRODUCT_PRICE_INPUT).type(price); + cy.get(SELECTOR.PRODUCT_QUANTITY_INPUT).type(quantity); + cy.get(SELECTOR.PRODUCT_ADD_BUTTON).click(); + }); + }); + + beforeEach(() => { + cy.stubRandomReturns([100, 100, 100, 100, 50]); + }); + + it("μƒν’ˆ 1개λ₯Ό ꡬ맀할 수 μžˆμ–΄μ•Ό ν•œλ‹€.", () => { + // given + const name = "콜라"; + const price = 1500; + const quantity = 20; + const coinAmount = 450; + const chargeAmount = 3000; + + // μƒν’ˆ μΆ”κ°€ + cy.get(SELECTOR.PRODUCT_MENU).click(); + cy.addProduct(name, price, quantity); + cy.addProduct("사이닀", 1000, 10); + + // μž”λˆ μΆ©μ „ + cy.get(SELECTOR.COIN_MENU).click(); + cy.get(SELECTOR.COIN_CHARGE_INPUT).type(coinAmount); + cy.get(SELECTOR.COIN_CHARGE_BUTTON).click(); + + // κΈˆμ•‘ νˆ¬μž… + cy.get(SELECTOR.PURCHASE_MENU).click(); + cy.get(SELECTOR.PURCHASE_CHARGE_INPUT).type(chargeAmount); + cy.get(SELECTOR.PURCHASE_CHARGE_BUTTON).click(); + + // when + cy.get("[data-product-name='콜라']") + .parent() + .find(SELECTOR.PURCHASE_ITEM_BUTTON) + .click(); + + // then + cy.get(SELECTOR.PURCHASE_CHARGE_AMOUNT).should( + "have.text", + chargeAmount - price + ); + cy.get("[data-product-name='콜라']") + .parent() + .find(SELECTOR.PURCHASE_ITEM_QUANTITY) + .should("have.text", quantity - 1); + cy.get(SELECTOR.COIN_MENU).click(); + cy.get(SELECTOR.COIN_100).should("have.text", "4개"); + cy.get(SELECTOR.COIN_50).should("have.text", "1개"); + }); + + it("잘λͺ»λœ μž…λ ₯κ°’μœΌλ‘œ μž”λˆ 좩전을 μ‹œλ„ν•˜λŠ” 경우 alert이 ν˜ΈμΆœλ˜μ–΄μ•Ό ν•œλ‹€.", () => { + // given + const alertStub = cy.stub(); + const invalidInput = -1; + + cy.on("window:alert", alertStub); + + // when + cy.get(SELECTOR.COIN_MENU).click(); + cy.get(SELECTOR.COIN_CHARGE_INPUT).type(invalidInput); + + // then + cy.get(SELECTOR.COIN_CHARGE_BUTTON) + .click() + .then(() => { + expect(alertStub).to.be.called; + }); + }); +});