Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[2단계 - 행운의 로또 미션] 하루(김하루) 미션 제출합니다. #33

Merged
merged 32 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
53798be
refactor: 로또 번호보기 토글 시 상위 엘리먼트에 CSS를 적용하여 불필요한 반복문 제거
SunYoungKwon Feb 20, 2021
802777c
refactor: 번호보기 토글 시 변경되는 CSS를 단일 클래스로 조정
SunYoungKwon Feb 21, 2021
821c307
docs: 2단계 기능 구현목록 작성
365kim Feb 18, 2021
8159f3a
test: 6개의 당첨번호와 1개의 보너스번호를 모두 알맞게 입력하기 전까지 결과 확인버튼 비활성화
SunYoungKwon Feb 18, 2021
455cd91
Resolve conflicts
365kim Feb 18, 2021
4a95345
test: 입력된 당첨번호 & 보너스번호에 중복된 값이 있을 경우 재입력 요청 메세지 표시
SunYoungKwon Feb 18, 2021
57bc629
test: 로또 구매를 완료했을 때 당첨번호 입력창 표시
365kim Feb 18, 2021
6ad3664
test: 로또 구매 및 당첨번호 입력을 마치고 당첨결과 확인 버튼을 클릭하면 당첨결과 모달 표시
365kim Feb 18, 2021
19ac60c
test: 구매한 로또의 당첨번호 일치 개수 판별
365kim Feb 18, 2021
ebf7ed5
test: 구매금액과 당첨금액에 따라 수익률(%) 반환
365kim Feb 19, 2021
6f345bd
test: 다시 시작하기 버튼을 클릭하면, 모달이 사라지고 화면 초기화
SunYoungKwon Feb 19, 2021
904f2ff
Resolve conflicts
365kim Feb 21, 2021
a2c8822
feat: 입력된 당첨번호가 1 ~ 45가 아닌 경우 재입력 요청 메세지 표시
SunYoungKwon Feb 21, 2021
22186f6
feat: 입력된 당첨번호가 서로 중복되는 경우 재입력 요청 메세지 표시
365kim Feb 21, 2021
67ff83e
feat: 6개의 '당첨번호'와 1개의 '보너스번호' 모두 알맞게 입력 시 결과확인 버튼 활성화
365kim Feb 21, 2021
b60e107
chore: HTML, CSS 정리
365kim Feb 22, 2021
943a107
test: 모든 번호가 올바르게 입력된, 결과 확인 가능 메세지 표시
365kim Feb 22, 2021
d78d3eb
feat: 당첨번호 입력에 빈 칸이 있거나 모두 올바르게 입력된 경우 메세지 표시
365kim Feb 22, 2021
b8a050e
refactor: 상태 변화에 따른 render를 setState에서 관리하도록 수정
SunYoungKwon Feb 22, 2021
f1962f3
feat: 결과 확인하기 버튼을 클릭한 경우 모달을 화면에 표시
SunYoungKwon Feb 22, 2021
af5e3b3
feat: App 컴포넌트에서 당첨번호 상태를 관리
365kim Feb 22, 2021
44ea655
feat: 구매한 로또의 당첨번호 일치 개수 반환 함수 추가
365kim Feb 22, 2021
5972672
test: 수익률이 정수가 아닌 경우 소수점 둘째 자리까지 표기
SunYoungKwon Feb 22, 2021
29d9f7e
feat: 수익률을 반환하는 함수 추가
SunYoungKwon Feb 22, 2021
b26280d
test: UI 테스트와 비즈니스로직 테스트 파일 분리
365kim Feb 22, 2021
d936a19
feat: 결과확인 버튼을 누르면 당첨 통계, 수익률을 모달로 확인
365kim Feb 22, 2021
5a2b590
feat: 다시 시작하기 버튼을 누르면 초기화
365kim Feb 23, 2021
b3156cd
refactor: Boolean 타입으로 중복 변환하는 부분 제거
365kim Feb 25, 2021
4b45e65
refactor: 불필요한 상수 RESULT_TABLE_DISPLAY_KEY 제거
365kim Feb 25, 2021
9ab3fca
refactor: 재시작 버튼 reset 타입 적용, 웹접근성 위반사항 수정
365kim Feb 25, 2021
a047941
refactor: Input 컴포넌트 내의 유틸함수, 사용자입력 검증 함수 분리
365kim Feb 25, 2021
dd7e93f
refactor: this를 쓰지않는 사용자입력 검증함수 컴포넌트 내로 통합 및 eslint 예외함수 지정
365kim Feb 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"no-return-assign": "off",
"import/extensions": "off",
"import/prefer-default-export": "off",
"max-depth": ["error", 2]
//"max-lines-per-function": ["error", 15]
"max-depth": ["error", 2],
"class-methods-use-this": ["error", { "exceptMethods": ["validatePurchaseAmount", "validateWinningNumbers"] }]
// "max-lines-per-function": ["error", 15]
}
}
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
- 최소 화폐단위 미만의 자릿수가 포함된 경우 ➡️ `alert` 후 재입력 요청
- 1,000원 미만일 경우 ➡️ `alert` 후 재입력 요청
- 1,000원으로 나누어 떨어지지 않을 경우 ➡️ `alert`으로 거스름돈 금액을 알려주고 구매 계속 진행
- 금액으로 살 수 있는 갯수만큼 로또를 발급한다.
- 금액으로 살 수 있는 개수만큼 로또를 발급한다.
- 모든 로또를 자동구매 옵션으로 발급한다.
- 로또 한 장 당 번호의 개수는 6개로 한다.
- 번호는 1 ~ 45 사이 랜덤값으로 구성한다.
Expand All @@ -37,8 +37,19 @@

### 🎯🎯 step2 당첨 결과 기능

- 로또를 구매하면 당첨번호를 입력할 수 있는 창이 표시된다.
- 당첨번호를 정상적으로 입력하면 결과 확인버튼을 클릭할 수 있다.
- 결과 확인 버튼은 기본적으로 비활성화 되어있다.
- 각 번호가 1 ~ 45 사이의 값이 아닌 경우 ➡️ 입력칸 하단에 재입력 요청 메세지 표시
- 각 번호가 서로 중복되는 경우 ➡️ 입력칸 하단에 재입력 요청 메세지 표시
- 6개의 '당첨번호'와 1개의 '보너스번호' 모두 알맞게 입력하면 결과 확인 버튼이 활성화된다.
- 결과 확인하기 버튼을 누르면 당첨 통계, 수익률을 모달로 확인할 수 있다.
- 로또 당첨 금액은 고정되어 있는 것으로 가정한다.
- 일치하는 번호 개수가 3, 4, 5, 5.5(보너스번호가 일치하는 경우), 6인 경우 당첨된 로또 장수를 표시한다.
- 구매한 로또의 '당첨번호' 일치 개수를 판별한다.
- 일치 개수가 5개일 경우에만 '보너스번호' 일치여부를 확인한다.
- 수익률을 표시한다.
- 로또 당첨 금액은 고정되어 있는 것으로 가정한다.
- 수익률이 정수가 아닌 경우 소수점 둘째 자리까지 표기한다.
- 다시 시작하기 버튼을 누르면 초기화 되서 다시 구매를 시작할 수 있다.

### 🎯🎯🎯 step3 수동 구매
Expand Down
77 changes: 77 additions & 0 deletions cypress/integration/lottoResultGetter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import LottoTicket from '../../src/js/model/LottoTicket.js';
import ResultModal from '../../src/js/components/ResultModal.js';
import { BONUS_COUNT } from '../../src/js/constants.js';

describe('당첨통계 계산 메서드 검사', () => {
before(() => {
cy.visit('http://localhost:5500/');
});

const winningNumber = {
winningNumbers: [1, 2, 3, 4, 5, 6],
bonusNumber: 7,
};

it('구매한 로또의 당첨번호 일치 개수를 반환한다.', () => {
const lottoTicket1 = new LottoTicket([1, 2, 3, 4, 5, 6]);
lottoTicket1.setTotalMatchCount(winningNumber);
const lottoTicket2 = new LottoTicket([1, 2, 3, 4, 5, 7]);
lottoTicket2.setTotalMatchCount(winningNumber);
const lottoTicket3 = new LottoTicket([1, 2, 3, 4, 5, 8]);
lottoTicket3.setTotalMatchCount(winningNumber);
const lottoTicket4 = new LottoTicket([1, 2, 3, 4, 8, 7]);
lottoTicket4.setTotalMatchCount(winningNumber);
const lottoTicket5 = new LottoTicket([1, 2, 3, 9, 8, 7]);
lottoTicket5.setTotalMatchCount(winningNumber);
const lottoTicket6 = new LottoTicket([1, 2, 10, 9, 8, 7]);
lottoTicket6.setTotalMatchCount(winningNumber);
const lottoTicket7 = new LottoTicket([1, 11, 10, 9, 8, 7]);
lottoTicket7.setTotalMatchCount(winningNumber);
const lottoTicket8 = new LottoTicket([12, 11, 10, 9, 8, 7]);
lottoTicket8.setTotalMatchCount(winningNumber);

expect(lottoTicket1.totalMatchCount).to.equal(6);
expect(lottoTicket2.totalMatchCount).to.equal(5 + BONUS_COUNT);
expect(lottoTicket3.totalMatchCount).to.equal(5);
expect(lottoTicket4.totalMatchCount).to.equal(4);
expect(lottoTicket5.totalMatchCount).to.equal(3);
expect(lottoTicket6.totalMatchCount).to.equal(2);
expect(lottoTicket7.totalMatchCount).to.equal(1);
expect(lottoTicket8.totalMatchCount).to.equal(0);
});

it('구매금액이 5,000원이고 당첨금액이 0원이면, -100의 수익률(%)을 반환한다.', () => {
const lottoTickets = [...Array(5)].map(() => new LottoTicket([7, 8, 9, 10, 11, 12]));
('');
const rateOfReturn = new ResultModal({ lottoTickets, winningNumber }).getLottoRateOfReturn();

expect(rateOfReturn).to.equal(-100);
});

it('구매금액이 5,000원이고 당첨금액이 5,000원이면, 0의 수익률(%)을 반환한다.', () => {
const lottoTickets = [...Array(4)]
.map(() => new LottoTicket([7, 8, 9, 10, 11, 12]))
.concat(new LottoTicket([1, 2, 3, 7, 8, 9]));
const rateOfReturn = new ResultModal({ lottoTickets, winningNumber }).getLottoRateOfReturn();

expect(rateOfReturn).to.equal(0);
});

it('구매금액이 5,000원이고 당첨금액이 2,000,000,000원이면, 39999900의 수익률(%)을 반환한다.', () => {
const lottoTickets = [...Array(4)]
.map(() => new LottoTicket([7, 8, 9, 10, 11, 12]))
.concat(new LottoTicket([1, 2, 3, 4, 5, 6]));
const rateOfReturn = new ResultModal({ lottoTickets, winningNumber }).getLottoRateOfReturn();

expect(rateOfReturn).to.equal(39999900);
});

it('구매금액이 13,000원이고 당첨금액이 5,000원이면, -61.54의 수익률(%)을 반환한다.', () => {
const lottoTickets = [...Array(12)]
.map(() => new LottoTicket([7, 8, 9, 10, 11, 12]))
.concat(new LottoTicket([1, 2, 3, 11, 12, 13]));
const rateOfReturn = new ResultModal({ lottoTickets, winningNumber }).getLottoRateOfReturn();

expect(rateOfReturn).to.equal(-61.54);
});
});
243 changes: 243 additions & 0 deletions cypress/integration/lottoUI.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import {
LOTTO_PRICE,
PURCHASED_QUANTITY_MESSAGE,
LOTTO_NUMBER_SEPARATOR,
LOTTO_MIN_NUMBER,
LOTTO_MAX_NUMBER,
LOTTO_NUMBERS_LENGTH,
MONETARY_UNIT,
PURCHASE_AMOUNT_ALERT_MESSAGE,
WINNING_NUMBER_CHECK_MESSAGE,
} from '../../src/js/constants.js';

describe('구매금액 입력 검사', () => {
beforeEach(() => {
cy.visit('http://localhost:5500/');
});

it('입력된 로또 구입 금액에 화폐단위 미만의 자릿수가 포함된 경우 alert을 띄우고 input창을 초기화한다.', () => {
const invalidMoney = MONETARY_UNIT / 10;
const { PURCHASE_AMOUNT_IS_INVALID_MONEY } = PURCHASE_AMOUNT_ALERT_MESSAGE;
const alertStub = cy.stub();

cy.on('window:alert', alertStub);
cy.get('.purchase-amount-input').type(invalidMoney);
cy.get('.purchase-amount-button')
.click()
.then(() => {
const actualMessage = alertStub.getCall(0).lastArg;
expect(actualMessage).to.equal(PURCHASE_AMOUNT_IS_INVALID_MONEY);
});
cy.get('.purchase-amount-input').should('have.text', '');
cy.get('.purchase-amount-input').should('have.focus');
});

it('입력된 로또 구입 금액이 로또 한 장의 금액보다 적을 경우 alert을 띄우고 input창을 초기화한다.', () => {
const tooLowAmount = Math.floor(LOTTO_PRICE * 0.9);
const { PURCHASE_AMOUNT_IS_TOO_LOW } = PURCHASE_AMOUNT_ALERT_MESSAGE;
const alertStub = cy.stub();

cy.on('window:alert', alertStub);
cy.get('.purchase-amount-input').type(tooLowAmount);
cy.get('.purchase-amount-button')
.click()
.then(() => {
const actualMessage = alertStub.getCall(0).lastArg;
expect(actualMessage).to.equal(PURCHASE_AMOUNT_IS_TOO_LOW);
});
cy.get('.purchase-amount-input').should('have.text', '');
cy.get('.purchase-amount-input').should('have.focus');
});

it('입력된 로또 구입 금액이 로또 한 장의 금액으로 나누어 떨어지지 않을 경우 alert으로 거스름돈 금액을 알려주고 구매한 로또를 표시한다.', () => {
const amountWithChange = Math.ceil(LOTTO_PRICE * 1.1);
const { PURCHASE_AMOUNT_HAS_CHANGE } = PURCHASE_AMOUNT_ALERT_MESSAGE;
const alertStub = cy.stub();

cy.on('window:alert', alertStub);
cy.get('.purchase-amount-input').type(amountWithChange);
cy.get('.purchase-amount-button')
.click()
.then(() => {
const change = amountWithChange % LOTTO_PRICE;
const actualMessage = alertStub.getCall(0).lastArg;
expect(actualMessage).to.equal(PURCHASE_AMOUNT_HAS_CHANGE(change));
});
cy.get('.purchased-lotto-section').should('be.visible');
});
});

describe('구매한 로또 UI 검사', () => {
before(() => {
cy.visit('http://localhost:5500/');
});

const numOfLotto = 3;

it('Enter키 이벤트로 로또를 구입한 후 입력된 로또 구입 금액으로 발급한 로또를 화면에 표시한다.', () => {
cy.get('.purchase-amount-input')
.type(LOTTO_PRICE * numOfLotto)
.type('{enter}');
cy.get('.purchased-lotto-section').should('be.visible');
cy.get('.purchased-lotto-label').should('have.text', PURCHASED_QUANTITY_MESSAGE(numOfLotto));
cy.get('.lotto-ticket-container > li').should('have.length', numOfLotto);
cy.get('.lotto-numbers-toggle-button').should('not.be.checked');
});

it('번호보기 토글이 비활성화 되어 있는 상태에서 토글을 누르면, 로또 아이콘이 세로로 배치되고 로또 번호가 표시된다.', () => {
cy.get('.switch').click();
cy.get('.lotto-numbers-toggle-button').should('be.checked');
cy.get('.lotto-ticket-container').should('have.css', 'flex-direction', 'column');
cy.get('.lotto-numbers').should('be.visible');
});

it('표시된 로또 번호의 개수, 중복여부, 범위를 검사한다.', () => {
cy.get('.lotto-numbers').each(($el) => {
const lottoNumbers = $el.text().split(LOTTO_NUMBER_SEPARATOR);

expect(lottoNumbers.length).to.be.equal(LOTTO_NUMBERS_LENGTH);
expect(lottoNumbers.length).to.be.equal(new Set(lottoNumbers).size);
lottoNumbers.forEach((lottoNumber) => {
expect(Number(lottoNumber)).to.be.within(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER);
});
});
});

it('로또를 재구입하면 기존에 구매한 로또를 삭제하고 새로 구매한 로또를 보여준다.', () => {
const nextNumOfLotto = numOfLotto * 2;

cy.get('.purchase-amount-input')
.clear()
.type(LOTTO_PRICE * nextNumOfLotto);
cy.get('.purchase-amount-button').click();
cy.get('.lotto-ticket-container > li').should('have.length', nextNumOfLotto);
});

it('번호보기 토글이 활성화된 상태에서 재구입을 하면, 토글 상태가 변하지 않고 새로 구매한 로또번호를 보여준다.', () => {
cy.get('.lotto-numbers-toggle-button').should('be.checked');
cy.get('.lotto-numbers').should('be.visible');
});

it('번호보기 토글이 활성화된 상태에서 토글을 누르면, 로또 아이콘이 가로로 배치되고 로또 번호가 사라진다.', () => {
cy.get('.switch').click();
cy.get('.lotto-numbers-toggle-button').should('not.be.checked');
cy.get('.purchased-lotto-section').should('not.have.css', 'flex-direction', 'column');
cy.get('.lotto-numbers').should('not.be.visible');
});
});

describe('당첨번호 입력 검사', () => {
beforeEach(() => {
cy.visit('http://localhost:5500/');
cy.get('.purchase-amount-input').type(LOTTO_PRICE).type('{enter}');
});

it('로또를 구매하면 당첨번호를 입력할 수 있는 창이 표시된다.', () => {
cy.get('.winning-number-form').should('be.visible');
});

it('6개의 당첨번호와 1개의 보너스번호가 모두 정상입력되기 전까지 결과확인하기 버튼이 비활성화 되어 있다.', () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 7;
const { HAS_BLANK } = WINNING_NUMBER_CHECK_MESSAGE;

cy.get('.winning-number').first().should('have.focus');
cy.get('.open-result-modal-button').should('be.disabled');
cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
cy.get('.open-result-modal-button').should('be.disabled');
cy.get('.winning-number-check-message').should('have.text', HAS_BLANK);
cy.get('.winning-number-check-message').should('have.class', 'text-red');
});
cy.get('.bonus-number').type(bonusNumber);
cy.get('.open-result-modal-button').should('not.be.disabled');
});

it('입력된 번호가 1 ~ 45 범위가 아니면, 입력칸 하단에 재입력 요청 메세지를 표시한다.', () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];
const invalidBonusNumber = 77;
const { OUT_OF_RANGE } = WINNING_NUMBER_CHECK_MESSAGE;

cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
});
cy.get('.bonus-number').type(invalidBonusNumber);
cy.get('.winning-number-check-message').should('have.text', OUT_OF_RANGE);
cy.get('.winning-number-check-message').should('have.class', 'text-red');
});

it('입력된 번호에 중복된 값이 있으면, 입력칸 하단에 재입력 요청 메세지를 표시한다.', () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 6;
const { DUPLICATED } = WINNING_NUMBER_CHECK_MESSAGE;

cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
});
cy.get('.bonus-number').type(bonusNumber);
cy.get('.winning-number-check-message').should('have.text', DUPLICATED);
cy.get('.winning-number-check-message').should('have.class', 'text-red');
});

it('모든 번호가 올바르게 입력되면, 입력칸 하단에 결과 확인 가능 메세지를 표시한다.', () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 7;
const { FULFILLED: COMPLETED } = WINNING_NUMBER_CHECK_MESSAGE;

cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
});
cy.get('.bonus-number').type(bonusNumber);
cy.get('.winning-number-check-message').should('have.text', COMPLETED);
cy.get('.winning-number-check-message').should('have.class', 'text-green');
});

it('모든 번호가 올바르게 입력된 후에 입력한 숫자를 지우면, 입력칸 하단에 재입력 요청 메세지를 표시한다.', () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 7;
const { HAS_BLANK } = WINNING_NUMBER_CHECK_MESSAGE;

cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
});
cy.get('.bonus-number').type(bonusNumber);
cy.get('.bonus-number').type('{backspace}');
cy.get('.winning-number-check-message').should('have.text', HAS_BLANK);
cy.get('.winning-number-check-message').should('have.class', 'text-red');
});
});

describe('당첨 결과 모달 UI 검사', () => {
before(() => {
cy.visit('http://localhost:5500/');
});

const winningNumber = {
winningNumbers: [1, 2, 3, 4, 5, 6],
bonusNumber: 7,
};

it('로또 구매 및 당첨번호 입력을 마치고 결과확인 버튼을 클릭하면 당첨 결과 모달이 표시된다.', () => {
const { winningNumbers, bonusNumber } = winningNumber;

cy.get('.purchase-amount-input').type(LOTTO_PRICE).type('{enter}');
cy.get('.winning-number').each(($el, index) => {
cy.wrap($el).type(winningNumbers[index]);
});
cy.get('.bonus-number').type(bonusNumber);
cy.get('.open-result-modal-button').click();
cy.get('.modal').should('be.visible');
});

it('다시 시작하기 버튼을 클릭하면, 모달이 사라지고 화면이 초기화된다.', () => {
cy.get('.reset-button').click();
cy.get('.modal').should('not.be.visible');
cy.get('.purchased-lotto-section').should('not.be.visible');
cy.get('.winning-number-form').should('not.be.visible');
cy.get('.purchase-amount-input').should('have.text', '');
cy.get('.lotto-numbers-toggle-button').should('not.be.checked');
cy.get('.winning-number').each(($el) => {
cy.wrap($el).should('have.text', '');
});
});
});
Loading