Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d9c2e74
docs: README에 기능 정리 작성
Oct 29, 2025
cabebe6
feat: 로또 구입금액 입력 및 검증 기능 추가
bbbbmo Nov 2, 2025
f974dd0
refactor: 검증 함수 validate.js로 분리
bbbbmo Nov 2, 2025
08c2128
feat: 로또를 발행하는 기능 추가
bbbbmo Nov 2, 2025
89e11eb
refactor(const): 구매 금액 단위 상수로 분리
bbbbmo Nov 2, 2025
9f8c396
feat: 당첨 번호 입력 기능 추가
bbbbmo Nov 2, 2025
f70b7fc
feat: 보너스 번호 입력 기능 추가
bbbbmo Nov 2, 2025
440313a
feat: 당첨 내역을 출력하는 기능 추가
bbbbmo Nov 2, 2025
e708ec6
feat: 총 수익률을 출력하는 기능 추가/당첨 결과 자료구조 Map에서 객체 배열로 리팩토링
Nov 3, 2025
5f31f23
refactor: 목적에 따라 기능 분리/프로젝트 구조 개선
Nov 3, 2025
7ad64f6
remove: 사용하지 않는 parse.js 삭제
bbbbmo Nov 3, 2025
0824d04
fix: 로또 번호 출력기능이 제대로 되지않던 버그 수정
bbbbmo Nov 3, 2025
6c904c6
refactor: 프로젝트 구조 변경
bbbbmo Nov 3, 2025
f505aef
fix: 에러 처리 제대로 되지않던 버그 수정
bbbbmo Nov 3, 2025
ec5cc9f
test: 검증 유틸 함수 테스트 코드 추가 및 테스트 통과 확인
bbbbmo Nov 3, 2025
e267420
test(lottoMachineTest): 테스트 코드 작성 및 테스트 통과 확인
bbbbmo Nov 3, 2025
b6251f1
fix: 테스트 에러 수정
bbbbmo Nov 3, 2025
581cb5b
test(LottoTest): 로또 클래스 테스트 추가 및 테스트 통과 확인
bbbbmo Nov 3, 2025
20dea89
docs(README): 리드미 기능 구현 확인 및 업데이트
bbbbmo Nov 3, 2025
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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# javascript-lotto-precourse

## 📜기능 정리

### 로또 구입 입력 기능

- [x] **구입 금액** 입력을 받는 기능 - `Console.readLineAsync()`
- [x] 입력이 존재하는지 검사하는 기능
- [x] 입력이 양의 정수인지 검사하는 기능
- [x] 입력이 1000단위로 나누어 떨어지는지 검사하는 기능

### 로또를 발행하는 기능

- [x] 구입 금액을 1000으로 나누어 **로또 발행 수**를 계산하는 기능
- [x] 로또 발행 수를 출력하는 기능 - `Console.print()`
- [x] 1 ~ 45 이내의 중복되지 않는 6개의 숫자로 이루어진 **로또 번호 배열**을 생성하는 기능 - `MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6)`
- [x] 생성한 로또 번호 배열 오름차 순으로 정렬하는 기능
- [x] 발행 수만큼 로또 번호 배열 생성을 반복하는 기능
- [x] 생성된 모든 로또 번호를 출력하는 기능 - `Console.print()`

### 당첨 번호 입력 기능

- [x] **당첨 번호** 입력을 받는 기능 - `Console.readLineAsync()`
- [x] 입력이 있는지 검사하는 기능
- [x] 쉼표(,)를 기준으로 입력을 분리(spliit)하는 기능
- [x] 쉼표로 분리된 입력이 양의 정수인지 검사하는 기능
- [x] 쉼표로 분리된 입력이 1 ~ 45 이내의 숫자인지 검사하는 기능
- [x] 쉼표로 분리된 입력에 중복된 숫자가 있는지 검사하는 기능

### 보너스 번호 입력 기능

- [x] **보너스 번호** 입력을 받는 기능 - `Console.readLineAsync()`
- [x] 입력이 있는지 검사하는 기능
- [x] 입력이 양의 정수인지 검사하는 기능
- [x] 입력이 1 ~ 45 이내의 숫자인지 검사하는 기능
- [x] 입력이 당첨 번호와 중복되는지 검사하는 기능

### 당첨 내역을 출력하는 기능

- [x] “당첨 통계” 메세지를 출력하는 기능 - `Console.print()`
- [x] 로또 번호 배열을 순회하며 일치하는 숫자를 찾아 당첨 정보를 생성하는 기능
- [x] 발행 수만큼 당첨 정보 생성을 반복하는 기능
- [x] 당첨 정보를 받아 당첨 내역을 출력하는 기능 - `Console.print()`

### 총 수익률을 출력하는 기능

- [x] 당첨 정보를 기반으로 수익률을 계산하는 기능
- [x] 수익률을 소수점 둘째 자리에서 반올림 하는 기능
- [x] 수익률 정보를 받아 총 수익률을 출력하는 기능 - `Console.print()`
310 changes: 310 additions & 0 deletions __tests__/LottoMachineTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
import {
validatePurchasePrice,
calcPurchaseCount,
createLottoNumbers,
validateWinningNumbers,
validateBonusNumber,
incrementCount,
calcWinningResult,
calcTotalYield,
} from "../src/lottoMachine.js";
import { MissionUtils } from "@woowacourse/mission-utils";
import Lotto from "../src/Lotto.js";
import { BONUS_PRIZE_INDEX, winningInfo } from "../src/const.js";

describe("validatePurchasePrice 테스트", () => {
test("정상적인 구매 금액이면 숫자를 반환한다.", () => {
const result = validatePurchasePrice("1000");
expect(result).toBe(1000);
});

test("공백이 포함된 구매 금액이면 trim 후 검증한다.", () => {
const result = validatePurchasePrice(" 2000 ");
expect(result).toBe(2000);
});

test("빈 문자열이면 예외가 발생한다.", () => {
expect(() => {
validatePurchasePrice("");
}).toThrow("[ERROR]");
});

test("공백만 있으면 예외가 발생한다.", () => {
expect(() => {
validatePurchasePrice(" ");
}).toThrow("[ERROR]");
});

test("1000단위가 아니면 예외가 발생한다.", () => {
expect(() => {
validatePurchasePrice("1500");
}).toThrow("[ERROR]");
});

test("숫자가 아니면 예외가 발생한다.", () => {
expect(() => {
validatePurchasePrice("abc");
}).toThrow("[ERROR]");
});

test("양의 정수가 아니면 예외가 발생한다.", () => {
expect(() => {
validatePurchasePrice("-1000");
}).toThrow("[ERROR]");
});
});

describe("calcPurchaseCount 테스트", () => {
test("1000원이면 1개를 반환한다.", () => {
const result = calcPurchaseCount(1000);
expect(result).toBe(1);
});

test("5000원이면 5개를 반환한다.", () => {
const result = calcPurchaseCount(5000);
expect(result).toBe(5);
});
});

describe("createLottoNumbers 테스트", () => {
beforeEach(() => {
jest.restoreAllMocks();
});

test("구매 개수만큼 로또를 생성한다.", () => {
const mockNumbers = [1, 2, 3, 4, 5, 6];
MissionUtils.Random.pickUniqueNumbersInRange = jest
.fn()
.mockReturnValue(mockNumbers);

const result = createLottoNumbers(3);

expect(result).toHaveLength(3);
expect(MissionUtils.Random.pickUniqueNumbersInRange).toHaveBeenCalledTimes(
3
);
});

test("생성된 로또 번호는 정렬되어 있다.", () => {
const mockNumbers = [6, 1, 5, 2, 4, 3];
MissionUtils.Random.pickUniqueNumbersInRange = jest
.fn()
.mockReturnValue(mockNumbers);

const result = createLottoNumbers(1);

expect(result[0].numbers).toEqual([1, 2, 3, 4, 5, 6]);
});

test("생성된 로또는 Lotto 인스턴스이다.", () => {
const mockNumbers = [1, 2, 3, 4, 5, 6];
MissionUtils.Random.pickUniqueNumbersInRange = jest
.fn()
.mockReturnValue(mockNumbers);

const result = createLottoNumbers(1);

expect(result[0]).toBeInstanceOf(Lotto);
});
});

describe("validateWinningNumbers 테스트", () => {
test("정상적인 당첨 번호이면 배열을 반환한다.", () => {
const result = validateWinningNumbers("1,2,3,4,5,6");
expect(result).toEqual([1, 2, 3, 4, 5, 6]);
});

test("공백이 포함된 당첨 번호이면 trim 후 검증한다.", () => {
const result = validateWinningNumbers(" 1, 2, 3, 4, 5, 6 ");
expect(result).toEqual([1, 2, 3, 4, 5, 6]);
});

test("빈 문자열이면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("");
}).toThrow("[ERROR]");
});

test("6개가 아니면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("1,2,3,4,5");
}).toThrow("[ERROR]");
});

test("중복된 번호가 있으면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("1,2,3,4,5,5");
}).toThrow("[ERROR]");
});

test("범위를 벗어난 번호가 있으면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("1,2,3,4,5,46");
}).toThrow("[ERROR]");
});

test("숫자가 아닌 값이 있으면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("1,2,3,4,5,abc");
}).toThrow("[ERROR]");
});

test("양의 정수가 아닌 값이 있으면 예외가 발생한다.", () => {
expect(() => {
validateWinningNumbers("1,2,3,4,5,abc");
}).toThrow("[ERROR]");
});
});

describe("validateBonusNumber 테스트", () => {
const winningNumbers = [1, 2, 3, 4, 5, 6];

test("정상적인 보너스 번호이면 숫자를 반환한다.", () => {
const result = validateBonusNumber("7", winningNumbers);
expect(result).toBe(7);
});

test("공백이 포함된 보너스 번호이면 trim 후 검증한다.", () => {
const result = validateBonusNumber(" 7 ", winningNumbers);
expect(result).toBe(7);
});

test("빈 문자열이면 예외가 발생한다.", () => {
expect(() => {
validateBonusNumber("", winningNumbers);
}).toThrow("[ERROR]");
});

test("당첨 번호와 중복되면 예외가 발생한다.", () => {
expect(() => {
validateBonusNumber("6", winningNumbers);
}).toThrow("[ERROR]");
});

test("범위를 벗어난 번호이면 예외가 발생한다.", () => {
expect(() => {
validateBonusNumber("46", winningNumbers);
}).toThrow("[ERROR]");
});

test("숫자가 아니면 예외가 발생한다.", () => {
expect(() => {
validateBonusNumber("abc", winningNumbers);
}).toThrow("[ERROR]");
});
});

describe("incrementCount 테스트", () => {
test("3개 일치이면 result[0]의 count가 증가한다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 3, false);

expect(result[0].count).toBe(1);
});

test("4개 일치이면 result[1]의 count가 증가한다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 4, false);

expect(result[1].count).toBe(1);
});

test("5개 일치이고 보너스 숫자가 일치하지 않으면 result[2]의 count가 증가한다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 5, false);

expect(result[2].count).toBe(1);
expect(result[BONUS_PRIZE_INDEX].count).toBe(0);
});

test("5개 일치이고 보너스 숫자가 일치하면 result[3]의 count가 증가한다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 5, true);

expect(result[2].count).toBe(0);
expect(result[BONUS_PRIZE_INDEX].count).toBe(1);
});

test("6개 일치이면 result[4]의 count가 증가한다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 6, false);

expect(result[4].count).toBe(1);
});

test("2개 이하 일치이면 count가 증가하지 않는다.", () => {
const result = structuredClone(winningInfo);
incrementCount(result, 2, false);

result.forEach((item) => {
expect(item.count).toBe(0);
});
});
});

describe("calcWinningResult 테스트", () => {
test("당첨된 로또가 있으면 해당 등수 count가 증가한다.", () => {
const lottoArray = [
new Lotto([1, 2, 3, 4, 5, 6]),
new Lotto([1, 2, 3, 4, 5, 7]),
new Lotto([1, 2, 3, 4, 10, 11]),
new Lotto([1, 2, 3, 10, 11, 12]),
];
const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 7;

const result = calcWinningResult(lottoArray, winningNumbers, bonusNumber);

expect(result[0].count).toBe(1);
expect(result[1].count).toBe(1);
expect(result[2].count).toBe(0);
expect(result[BONUS_PRIZE_INDEX].count).toBe(1);
expect(result[4].count).toBe(1);
});
});

describe("calcTotalYield 테스트", () => {
test("당첨금이 없으면 수익률은 0.0%이다.", () => {
const result = structuredClone(winningInfo);
const purchasePrice = 1000;

const totalYield = calcTotalYield(result, purchasePrice);

expect(totalYield).toBe("0.0");
});

test("당첨금이 있으면 수익률을 계산한다.", () => {
const result = structuredClone(winningInfo);
result[0].count = 1;
result[1].count = 1;
const purchasePrice = 10000;

const totalYield = calcTotalYield(result, purchasePrice);

expect(totalYield).toBe("550.0");
});

test("수익률은 소수점 첫째 자리까지 반환한다.", () => {
const result = structuredClone(winningInfo);
result[0].count = 1;
const purchasePrice = 3000;

const totalYield = calcTotalYield(result, purchasePrice);

expect(totalYield).toBe("166.7");
});

test("모든 등수 당첨 시 수익률을 계산한다.", () => {
const result = structuredClone(winningInfo);
result[0].count = 1;
result[1].count = 1;
result[2].count = 1;
result[BONUS_PRIZE_INDEX].count = 1;
result[4].count = 1;
const purchasePrice = 10000;

const totalYield = calcTotalYield(result, purchasePrice);

expect(totalYield).toBe("20315550.0");
});
});
Loading