From b1bb107b0dbd2f2d5f945409c2a0d84a87e8b379 Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 15:15:04 +0900 Subject: [PATCH 001/109] =?UTF-8?q?chore:=20=EA=B0=9C=EB=B0=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=8F=84=EA=B5=AC=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prettier, ESlint: 컨벤션을 위해 스타일라이브러리 추가 - @woowacourse/mission-utils: 랜덤 함수를 사용하기 위한 유틸라이브러리 추가 --- .eslintrc.cjs | 41 + .prettierignore | 3 + .prettierrc | 7 + __tests__/ApplicationTest.js | 50 +- __tests__/LottoTest.js | 12 +- package-lock.json | 5763 +++++++++++++++++++++++++++++++--- package.json | 12 +- src/Lotto.js | 2 +- src/index.js | 2 +- 9 files changed, 5439 insertions(+), 453 deletions(-) create mode 100644 .eslintrc.cjs create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..bd3e9bd85 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,41 @@ +module.exports = { + env: { + browser: true, + node: true, + es2021: true, + jest: true, + }, + ignorePatterns: ['package*', '.npmrc', '*.md', '.*', '__tests__'], + extends: ['eslint:recommended', 'airbnb-base', 'prettier'], + + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, + + rules: { + 'no-var': 'error', + 'prefer-const': 'error', + 'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['acc', 'e'] }], + 'class-methods-use-this': 'off', + 'no-console': 'warn', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + 'import/no-unresolved': 'off', + 'import/extensions': 'off', + 'lines-between-class-members': [ + 'error', + 'always', + { + exceptAfterSingleLine: true, + }, + ], + }, +}; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..904e091e0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +package* +.npmrc +*.md \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..649d1df7a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 872380c9c..b8a446383 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -1,5 +1,5 @@ -import App from "../src/App.js"; -import { MissionUtils } from "@woowacourse/mission-utils"; +import App from '../src/App.js'; +import { MissionUtils } from '@woowacourse/mission-utils'; const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -19,7 +19,7 @@ const mockRandoms = (numbers) => { }; const getLogSpy = () => { - const logSpy = jest.spyOn(MissionUtils.Console, "print"); + const logSpy = jest.spyOn(MissionUtils.Console, 'print'); logSpy.mockClear(); return logSpy; }; @@ -29,7 +29,7 @@ const runException = async (input) => { const logSpy = getLogSpy(); const RANDOM_NUMBERS_TO_END = [1, 2, 3, 4, 5, 6]; - const INPUT_NUMBERS_TO_END = ["1000", "1,2,3,4,5,6", "7"]; + const INPUT_NUMBERS_TO_END = ['1000', '1,2,3,4,5,6', '7']; mockRandoms([RANDOM_NUMBERS_TO_END]); mockQuestions([input, ...INPUT_NUMBERS_TO_END]); @@ -39,15 +39,15 @@ const runException = async (input) => { await app.run(); // then - expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[ERROR]')); }; -describe("로또 테스트", () => { +describe('로또 테스트', () => { beforeEach(() => { jest.restoreAllMocks(); }); - test("기능 테스트", async () => { + test('기능 테스트', async () => { // given const logSpy = getLogSpy(); @@ -61,7 +61,7 @@ describe("로또 테스트", () => { [2, 13, 22, 32, 38, 45], [1, 3, 5, 14, 22, 45], ]); - mockQuestions(["8000", "1,2,3,4,5,6", "7"]); + mockQuestions(['8000', '1,2,3,4,5,6', '7']); // when const app = new App(); @@ -69,21 +69,21 @@ describe("로또 테스트", () => { // then const logs = [ - "8개를 구매했습니다.", - "[8, 21, 23, 41, 42, 43]", - "[3, 5, 11, 16, 32, 38]", - "[7, 11, 16, 35, 36, 44]", - "[1, 8, 11, 31, 41, 42]", - "[13, 14, 16, 38, 42, 45]", - "[7, 11, 30, 40, 42, 43]", - "[2, 13, 22, 32, 38, 45]", - "[1, 3, 5, 14, 22, 45]", - "3개 일치 (5,000원) - 1개", - "4개 일치 (50,000원) - 0개", - "5개 일치 (1,500,000원) - 0개", - "5개 일치, 보너스 볼 일치 (30,000,000원) - 0개", - "6개 일치 (2,000,000,000원) - 0개", - "총 수익률은 62.5%입니다.", + '8개를 구매했습니다.', + '[8, 21, 23, 41, 42, 43]', + '[3, 5, 11, 16, 32, 38]', + '[7, 11, 16, 35, 36, 44]', + '[1, 8, 11, 31, 41, 42]', + '[13, 14, 16, 38, 42, 45]', + '[7, 11, 30, 40, 42, 43]', + '[2, 13, 22, 32, 38, 45]', + '[1, 3, 5, 14, 22, 45]', + '3개 일치 (5,000원) - 1개', + '4개 일치 (50,000원) - 0개', + '5개 일치 (1,500,000원) - 0개', + '5개 일치, 보너스 볼 일치 (30,000,000원) - 0개', + '6개 일치 (2,000,000,000원) - 0개', + '총 수익률은 62.5%입니다.', ]; logs.forEach((log) => { @@ -91,7 +91,7 @@ describe("로또 테스트", () => { }); }); - test("예외 테스트", async () => { - await runException("1000j"); + test('예외 테스트', async () => { + await runException('1000j'); }); }); diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 409aaf69b..19d576106 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,17 +1,17 @@ -import Lotto from "../src/Lotto"; +import Lotto from '../src/Lotto'; -describe("로또 클래스 테스트", () => { - test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => { +describe('로또 클래스 테스트', () => { + test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow("[ERROR]"); + }).toThrow('[ERROR]'); }); // TODO: 테스트가 통과하도록 프로덕션 코드 구현 - test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => { + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow("[ERROR]"); + }).toThrow('[ERROR]'); }); // TODO: 추가 기능 구현에 따른 테스트 코드 작성 diff --git a/package-lock.json b/package-lock.json index 148f5f072..a1146cb73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,15 @@ "devDependencies": { "@babel/core": "^7.25.8", "@babel/preset-env": "^7.25.8", + "@eslint/js": "^9.38.0", "babel-jest": "^30.1.2", - "jest": "^30.1.2" + "eslint": "^8.2.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.25.2", + "globals": "^16.4.0", + "jest": "^30.1.2", + "prettier": "^3.6.2" }, "engines": { "node": ">=22.19.0", @@ -919,6 +926,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", @@ -1819,6 +1836,207 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2268,6 +2486,44 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2292,6 +2548,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", @@ -2402,6 +2665,13 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", @@ -2721,6 +2991,46 @@ "npm": ">=10.8.2" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2789,89 +3099,237 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/babel-jest": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz", - "integrity": "sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.1.2", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.11.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "@types/babel__core": "^7.20.5" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, - "peerDependencies": { + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz", + "integrity": "sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.1.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.0", + "babel-preset-jest": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, @@ -3012,6 +3470,56 @@ "dev": true, "license": "MIT" }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3223,6 +3731,13 @@ "dev": true, "license": "MIT" }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3258,6 +3773,60 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3290,6 +3859,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3300,6 +3876,42 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3310,6 +3922,34 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3354,416 +3994,1791 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" }, "engines": { "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", + "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.1.2", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.1.2", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" } }, - "node_modules/expect": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", - "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.1.2", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "bser": "2.1.1" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "hasown": "^2.0.2" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=0.10.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/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, "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=10.17.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/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, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4511,6 +6526,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4518,6 +6540,20 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4530,6 +6566,16 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4540,6 +6586,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4567,6 +6627,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4616,6 +6683,16 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4663,6 +6740,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4679,64 +6766,177 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/napi-postinstall" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/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==", + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { @@ -4765,6 +6965,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4827,6 +7063,19 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4949,6 +7198,42 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -4977,6 +7262,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -4994,6 +7289,27 @@ ], "license": "MIT" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5001,6 +7317,29 @@ "dev": true, "license": "MIT" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -5038,6 +7377,27 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", @@ -5072,58 +7432,211 @@ "dependencies": { "jsesc": "~3.0.2" }, - "bin": { - "regjsparser": "bin/parser" + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">=0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", "dependencies": { - "resolve-from": "^5.0.0" + "es-errors": "^1.3.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { @@ -5135,6 +7648,55 @@ "semver": "bin/semver.js" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5158,6 +7720,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5221,6 +7859,20 @@ "node": ">=10" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5322,6 +7974,65 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -5496,6 +8207,13 @@ "node": "*" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5510,41 +8228,187 @@ "dev": true, "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, - "license": "0BSD", - "optional": true + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undici-types": { @@ -5664,6 +8528,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -5705,6 +8579,105 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -6502,6 +9475,14 @@ "@babel/helper-replace-supers": "^7.25.9", "@babel/traverse": "^7.25.9", "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } } }, "@babel/plugin-transform-computed-properties": { @@ -7080,6 +10061,139 @@ "tslib": "^2.4.0" } }, + "@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + } + }, + "@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -7421,6 +10535,32 @@ "@tybys/wasm-util": "^0.10.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -7434,6 +10574,12 @@ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true }, + "@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "@sinclair/typebox": { "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", @@ -7533,6 +10679,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/node": { "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", @@ -7710,47 +10862,167 @@ "resolved": "https://registry.npmjs.org/@woowacourse/mission-utils/-/mission-utils-2.2.0.tgz", "integrity": "sha512-yuG68cd42EsvI43c/7T6eQOKgobRkMGqvpN9gp4P134L1LQ/BksA+4hfDt8Qc71TzQEf3cb17y2L7BWwXudu8Q==" }, + "acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "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" + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "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" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + } + }, + "array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + } + }, + "array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" } }, - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true + "array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" } }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "possible-typed-array-names": "^1.0.0" } }, "babel-jest": { @@ -7906,6 +11178,38 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -8041,6 +11345,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -8067,6 +11377,39 @@ "which": "^2.0.1" } }, + "data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8083,18 +11426,66 @@ "dev": true, "requires": {} }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8125,20 +11516,441 @@ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "requires": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, + "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 + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + }, + "eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "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" + } + } + } + }, + "eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "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" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "requires": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } }, "esprima": { "version": "4.0.1", @@ -8146,6 +11958,30 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8197,12 +12033,33 @@ "jest-util": "30.0.5" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -8212,6 +12069,15 @@ "bser": "2.1.1" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8231,6 +12097,32 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "requires": { + "is-callable": "^1.2.7" + } + }, "foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -8254,6 +12146,38 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8266,18 +12190,57 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + } + }, "glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -8292,10 +12255,35 @@ "path-scurry": "^1.11.1" } }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true + }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true }, "graceful-fs": { @@ -8304,10 +12292,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true }, "has-flag": { @@ -8316,6 +12310,48 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8328,6 +12364,30 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -8360,19 +12420,115 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + } + }, + "is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "requires": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "requires": { + "has-bigints": "^1.0.2" + } + }, + "is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.2" + } + }, + "is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + } + }, + "is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" } }, "is-fullwidth-code-point": { @@ -8387,18 +12543,156 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "requires": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.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-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, "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-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + } + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, + "is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8935,24 +13229,61 @@ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8974,6 +13305,12 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9009,6 +13346,12 @@ "tmpl": "1.0.5" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9040,6 +13383,12 @@ "brace-expansion": "^2.0.1" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, "minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -9091,6 +13440,79 @@ "path-key": "^3.0.0" } }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + } + }, + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + } + }, + "object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + } + }, + "object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9109,6 +13531,31 @@ "mimic-fn": "^2.1.0" } }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, + "own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9150,6 +13597,15 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9231,6 +13687,24 @@ "find-up": "^4.0.0" } }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true + }, "pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -9250,18 +13724,46 @@ } } }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, "pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9292,6 +13794,20 @@ "@babel/runtime": "^7.8.4" } }, + "regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + } + }, "regexpu-core": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", @@ -9353,12 +13869,142 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "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" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + } + }, + "safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + } + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + } + }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9374,6 +14020,54 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -9417,6 +14111,16 @@ "escape-string-regexp": "^2.0.0" } }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -9489,6 +14193,44 @@ } } }, + "string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, "strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -9603,6 +14345,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9618,6 +14366,35 @@ "is-number": "^7.0.0" } }, + "tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -9625,6 +14402,15 @@ "dev": true, "optional": true }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -9637,6 +14423,71 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + } + }, + "typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + } + }, + "unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + } + }, "undici-types": { "version": "7.12.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", @@ -9709,6 +14560,15 @@ "picocolors": "^1.1.0" } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -9738,6 +14598,73 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index 88274c374..f92178d05 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,20 @@ "type": "module", "scripts": { "start": "node src/index.js", - "test": "jest --detectOpenHandles" + "test": "jest --detectOpenHandles", + "lint": "prettier --write . && eslint --fix ." }, "devDependencies": { "@babel/core": "^7.25.8", "@babel/preset-env": "^7.25.8", "babel-jest": "^30.1.2", + "@eslint/js": "^9.38.0", + "eslint": "^8.2.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.25.2", + "globals": "^16.4.0", + "prettier": "^3.6.2", "jest": "^30.1.2" }, "dependencies": { @@ -19,7 +27,7 @@ "jest": { "transform": { "\\.js$": "babel-jest" - } + } }, "babel": { "presets": [ diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e..1164a3f81 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -8,7 +8,7 @@ class Lotto { #validate(numbers) { if (numbers.length !== 6) { - throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); + throw new Error('[ERROR] 로또 번호는 6개여야 합니다.'); } } diff --git a/src/index.js b/src/index.js index 02a1d389e..9daefc93f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import App from "./App.js"; +import App from './App.js'; const app = new App(); await app.run(); From 950fb98f4b9ce5c28ef4e27973535491c5a3cf63 Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 15:16:42 +0900 Subject: [PATCH 002/109] docs: Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구현할 기능 목록 추가 - 실행 흐름 추가 - 요구사항 추가 --- README.md | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 255 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15bb106b5..a89d9b662 100644 --- a/README.md +++ b/README.md @@ -1 +1,255 @@ -# javascript-lotto-precourse +# 로또 + +## 소개 + +Image + +저는 이번 과제를 크게 **세 가지 기능**으로 나눠 구현하려고 합니다 + +1. 구매 +2. 수령 +3. 수익률 + +레드-그린-리팩토링 사이클을 도입할 것이고 +**구매** 먼저 구현하려고 합니다 + +## 구현할 기능 목록 + +아래 [실행과정](#실행과정)의 흐름에 따라 기능 목록을 작성 +🔴테스트 만들기 +🟢테스트 통과 +🔵리펙토링 + +### feat(구매): + +#### 유효성 검사 + +🔴 입력문자열이 숫자로 변환될수있나? +🔴 숫자가 1000원으로 나눠떨어지나? +🔴 숫자가 양수인가? +🔴 숫자가 1-45인가? +🔴 배열에서 중복은 없는가? + + +### 수령 + +구매부터진행 + +### 수익률 + +구매부터진행 + +--- + +## 실행흐름 + +### 구매 + +1. 컨트롤러는 구매 함수 실행 +2. 컨트롤러는 인풋한테 얼마 살건지 입력받음 + - 숫자로 변할 수 있는 지 검사 + - 숫자로 변환하여 리턴 + +3. 컨트롤러는 입력 받은 값을 매개변수로 로또 서비스의 구매 함수를 호출 +4. 로또 서비스는 받아온 값을 사용해서 가계부에 기입하고 로또 스토어의 구매를 호출 +5. 가계부는 사용자가 받아온 값을 저장 +6. 로또 스토어는 받아온 값이 맞는지 검사 + - 숫자인지? + - 1,000원으로 딱 나누어 떨어지는지? +7. 로또 스토어는 받아온 값으로 몇 장 살 수 있나 계산 +8. 계산한 값으로 로또 팩토리에게 요청 +9. 로또 팩토리는 받아온 값을 검사 + - 양수인지? +10. 로또 팩토리는 랜덤 함수를 사용해 숫자 6개를 받아옴 + - 숫자로 변할 수 있는지? + - 범위에 맞는지?(1-45) + - 값이 중복되진 않았는지? +11. 숫자들을 로또에게 넘겨 로또를 생성 요청 +12. 로또는 생성 시 받아온 값의 검사 + - 6개인지? +13. 로또 팩토리는 생성된 로또들을 반환 +14. 이 반환 값을 서비스가 자신의 상태에 저장하고 반환 +15. 컨트롤러는 받아온 값으로 출력함수 실행 + +### 수령 + +1. 컨트롤러는 입력함수를 실행, 당첨 번호를 받아옴 +2. 컨트롤러는 당첨 번호를 +3. 컨트롤러는 입력함수를 실행, 보너스 번호를 받아옴 +4. 컨트롤러는 이거 두 개를 매개변수로 서비스에게 getMoney를 실행 +5. 서비스는 두 개로 로또당첨 객체 생성 +6. 로또당첨 객체는 생성과 동시에 번호들 유효성 검사를 진행 + - 당첨 번호의 유효성 검사 - 타입, 범위 + - 보너스 번호의 유효성 검사 - 타입, 중복, 범위 +7. 서비스는 로또당첨에 로또번호 리스트를 넘겨서 몇 개 당첨됐나 물어봄 +8. 로또당첨은 로또에게 당첨 번호와 몇 개 겹치나 물어봄 +9. 로또당첨은 로또에게 보너스 번호는 겹치나 물어봄 +10. 이제 이 당첨 번호 숫자로 등수를 판별함 +11. 판별한 등수를 서비스에게 리턴 +12. 서비스는 받아온 숫자로 로또뱅크에게 물어봄 +13. 로또뱅크는 등수에 따른 금액을 판별 +14. 서비스는 가계부에 수익을 적고 컨트롤러에 리턴 +15. 컨트롤러는 해당 값으로 출력실행 + +### 수익률 + +1. 로또 컨트롤러는 서비스에게 수익률을 요청 +2. 서비스는 가계부의 수익률 계산을 누름 +3. 가계부는 저장 되어있는 값으로 수익률을 계산하여 반환 +4. 컨트롤러는 해당 값으로 출력실행 + +--- + +## 요구 사항 + +
+클릭하면 요구사항이 열립니다 + +### 기능 요구 사항 + +간단한 로또 발매기를 구현한다. + +- 로또 번호의 숫자 범위는 1~45까지이다. +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. +- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다. +- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다. +- 로또 1장의 가격은 1,000원이다. +- 당첨 번호와 보너스 번호를 입력받는다. +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다. +- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 해당 지점부터 다시 입력을 받는다. + +#### 입출력 요구 사항 + +##### 입력 + +- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. + +```json +14000 +``` + +- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. + +```json +1,2,3,4,5,6 +``` + +- 보너스 번호를 입력 받는다. + +```json +7 +``` + +##### 출력 + +- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다. + +```js +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] +``` + +- 당첨 내역을 출력한다. + +```json +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +``` + +수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) + +```json +총 수익률은 62.5%입니다. +``` + +- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다. + +```js +[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. +``` + +- 실행 결과 예시 + +```json +구입금액을 입력해 주세요. +8000 + +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] + +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 + +보너스 번호를 입력해 주세요. +7 + + +당첨 통계 +--- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수익률은 62.5%입니다. +``` + +### 프로그래밍 요구 사항 1 + +- Node.js 22.19.0 버전에서 실행 가능해야 한다. +- 프로그램 실행의 시작점은 App.js의 run()이다. +- package.json 파일은 변경할 수 없으며, 제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다. +- 프로그램 종료 시 process.exit()를 호출하지 않는다. +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. +- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다. +- 기본적으로 JavaScript Style Guide를 원칙으로 한다. + +### 프로그래밍 요구 사항 2 + +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- 3항 연산자를 쓰지 않는다. +- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다. +- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. + - Using Matchers + - Testing Asynchronous Code + - Jest로 파라미터화 테스트하기: test.each(), describe.each() + +### 프로그래밍 요구 사항 3 + +- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- else를 지양한다. + - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. +- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. +- 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. + +
+ +--- From bb2886cff160ce30884cd208c5c1d7bce0c05a18 Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 16:58:20 +0900 Subject: [PATCH 003/109] =?UTF-8?q?test(validator):=20=F0=9F=94=B4=20UtilV?= =?UTF-8?q?alidator=20isConvertorNum=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력한 값에 대해서 숫자로 변환 가능한지 테스트 - 성공/실패 케이스 추가 --- __tests__/validator/UtilValidator.test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 __tests__/validator/UtilValidator.test.js diff --git a/__tests__/validator/UtilValidator.test.js b/__tests__/validator/UtilValidator.test.js new file mode 100644 index 000000000..a0a9be1c3 --- /dev/null +++ b/__tests__/validator/UtilValidator.test.js @@ -0,0 +1,23 @@ +import UtilValidator from '../../src/validator/UtilValidator.js'; + +describe('UtilValidator', () => { + describe('숫자 변환 가능 여부 검사,isConvertNum', () => { + test.each([ + ['1', true], + ['0', true], + ['-1', true], + ])('성공 테스트 (%s) returns %s', (test, expected) => { + expect(UtilValidator.isConvertNum(test)).toBe(expected); + }); + test.each([ + ['1,000', false], + ['1+2', false], + ['', false], + [' ', false], + [undefined, false], + [null, false], + ])('실패 테스트(%s) returns %s', (test, expected) => { + expect(UtilValidator.isConvertNum(test)).toBe(expected); + }); + }); +}); From 9c2ed87f09bcffd3ed18eb67af36bdcfc7716baa Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 17:04:34 +0900 Subject: [PATCH 004/109] =?UTF-8?q?feat(validator):=20=F0=9F=9F=A2=20UtilV?= =?UTF-8?q?alidator.isConvertNum=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력한 값에 대해서 숫자로 변환 가능한지 - null 과 undefined 인지 체크 - 문자열로 변환하여 공백인지 체크 - 숫자로 변환해보고 체크 --- src/validator/UtilValidator.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/validator/UtilValidator.js diff --git a/src/validator/UtilValidator.js b/src/validator/UtilValidator.js new file mode 100644 index 000000000..8da042764 --- /dev/null +++ b/src/validator/UtilValidator.js @@ -0,0 +1,16 @@ +const UtilValidator = { + isConvertNum(param) { + if (param === null || typeof param === 'undefined') { + return false; + } + if (String(param).trim() === '') { + return false; + } + if (Number.isNaN(Number(param))) { + return false; + } + return true; + }, +}; + +export default UtilValidator; From 6f7f3bd5984180a58751e1dc9cb6afb508a73b4f Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 17:28:16 +0900 Subject: [PATCH 005/109] =?UTF-8?q?test(validator):=20=F0=9F=94=B4=20UtilV?= =?UTF-8?q?alidator=20isNum=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력한 값이 숫자인지 테스트 성공/실패 케이스 추가 - 문자열로 들어온 "1"은 false --- __tests__/validator/UtilValidator.test.js | 27 ++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/__tests__/validator/UtilValidator.test.js b/__tests__/validator/UtilValidator.test.js index a0a9be1c3..8e6f6965d 100644 --- a/__tests__/validator/UtilValidator.test.js +++ b/__tests__/validator/UtilValidator.test.js @@ -1,12 +1,12 @@ import UtilValidator from '../../src/validator/UtilValidator.js'; describe('UtilValidator', () => { - describe('숫자 변환 가능 여부 검사,isConvertNum', () => { + describe('숫자 변환 가능 여부 검사 isConvertNum', () => { test.each([ ['1', true], ['0', true], ['-1', true], - ])('성공 테스트 (%s) returns %s', (test, expected) => { + ])('성공 테스트 (%s) return %s', (test, expected) => { expect(UtilValidator.isConvertNum(test)).toBe(expected); }); test.each([ @@ -16,8 +16,29 @@ describe('UtilValidator', () => { [' ', false], [undefined, false], [null, false], - ])('실패 테스트(%s) returns %s', (test, expected) => { + ])('실패 테스트(%s) return %s', (test, expected) => { expect(UtilValidator.isConvertNum(test)).toBe(expected); }); }); + + describe('숫자인지 타입 체크 isNum', () => { + test.each([ + [1, true], + [0, true], + [-1, true], + ])('성공 테스트 (%s) return %s', (test, expected) => { + expect(UtilValidator.isNum(test)).toBe(expected); + }); + test.each([ + ['1,000', false], + ['1+2', false], + ['1', false], + ['', false], + [' ', false], + [undefined, false], + [null, false], + ])('실패 테스트(%s) return %s', (test, expected) => { + expect(UtilValidator.isNum(test)).toBe(expected); + }); + }); }); From c474945a9ef9fef02fbef9dd51ad5e04dbbb912d Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 17:35:51 +0900 Subject: [PATCH 006/109] =?UTF-8?q?feat(validator):=20=F0=9F=9F=A2=20UtilV?= =?UTF-8?q?alidator.isNum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력 값이 Number 타입인지 - "1"에 대해선 false --- src/validator/UtilValidator.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/validator/UtilValidator.js b/src/validator/UtilValidator.js index 8da042764..d27c2a15a 100644 --- a/src/validator/UtilValidator.js +++ b/src/validator/UtilValidator.js @@ -11,6 +11,10 @@ const UtilValidator = { } return true; }, + + isNum(param) { + return typeof param === 'number'; + }, }; export default UtilValidator; From 77dd7a7dfa4983d1487f1840b4350823ba2c059f Mon Sep 17 00:00:00 2001 From: iftype Date: Wed, 29 Oct 2025 17:37:13 +0900 Subject: [PATCH 007/109] =?UTF-8?q?chore:=20.gitignore=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - coverage: jest coverage 파일 제외 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a547bf36d..3e12931ad 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* +coverage node_modules dist From 528bf980eb37581055ef08379e734e7364ddc6b7 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 03:32:59 +0900 Subject: [PATCH 008/109] =?UTF-8?q?test(validator):=20=F0=9F=94=B4=20UtilV?= =?UTF-8?q?alidator=20isPostive=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 값이 양수인지 확인함 - 문자열로 들어온 "1"까지 true - 0.1도 true --- __tests__/validator/UtilValidator.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/__tests__/validator/UtilValidator.test.js b/__tests__/validator/UtilValidator.test.js index 8e6f6965d..520cfb46b 100644 --- a/__tests__/validator/UtilValidator.test.js +++ b/__tests__/validator/UtilValidator.test.js @@ -1,6 +1,27 @@ import UtilValidator from '../../src/validator/UtilValidator.js'; describe('UtilValidator', () => { + describe('양수인지 검사 isPositive', () => { + test.each([ + [1, true], + [0.1, true], + ['1', true], + ])('⭕성공 테스트 (%s) return %s', (test, expected) => { + expect(UtilValidator.isPositive(test)).toBe(expected); + }); + test.each([ + ['', false], + [undefined, false], + [null, false], + ['0', false], + [0, false], + [-1, false], + ['str', false], + ])('❌실패 테스트(%s) return %s', (test, expected) => { + expect(UtilValidator.isPositive(test)).toBe(expected); + }); + }); + describe('숫자 변환 가능 여부 검사 isConvertNum', () => { test.each([ ['1', true], From a0fa5ba53f98e5b704f27032ad7b5f3294f60ab0 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:22:50 +0900 Subject: [PATCH 009/109] =?UTF-8?q?feat(validator):=20=F0=9F=9F=A2=20UtilV?= =?UTF-8?q?alidator.isPostive=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력 값이 숫자로 변환가능한지 검사한후 숫자로 변환한값이 0보다 큰지 검사 --- src/validator/UtilValidator.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/validator/UtilValidator.js b/src/validator/UtilValidator.js index d27c2a15a..6012746f0 100644 --- a/src/validator/UtilValidator.js +++ b/src/validator/UtilValidator.js @@ -15,6 +15,12 @@ const UtilValidator = { isNum(param) { return typeof param === 'number'; }, + + isPositive(param) { + if (!this.isConvertNum(param)) return false; + if (Number(param) <= 0) return false; + return true; + }, }; export default UtilValidator; From b50ebbf4f401b3753a5b46f42a36ed3a3837d7d1 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:25:41 +0900 Subject: [PATCH 010/109] =?UTF-8?q?refactor(validator):=20=F0=9F=9F=A1=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EB=A6=AC=ED=84=B0=EB=9F=B4=EC=9D=84=20Sta?= =?UTF-8?q?tic=20class=20=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 정적메서드를 가진 클래스로 바꿔 가독성 향상 --- src/validator/UtilValidator.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/validator/UtilValidator.js b/src/validator/UtilValidator.js index 6012746f0..0a834185d 100644 --- a/src/validator/UtilValidator.js +++ b/src/validator/UtilValidator.js @@ -1,5 +1,5 @@ -const UtilValidator = { - isConvertNum(param) { +class UtilValidator { + static isConvertNum(param) { if (param === null || typeof param === 'undefined') { return false; } @@ -10,17 +10,17 @@ const UtilValidator = { return false; } return true; - }, + } - isNum(param) { + static isNum(param) { return typeof param === 'number'; - }, + } - isPositive(param) { - if (!this.isConvertNum(param)) return false; + static isPositive(param) { + if (!UtilValidator.isConvertNum(param)) return false; if (Number(param) <= 0) return false; return true; - }, -}; + } +} export default UtilValidator; From 641b30ddc73093d9d87f2f4ed8703870b655d06f Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:32:07 +0900 Subject: [PATCH 011/109] =?UTF-8?q?test(validator):=20=F0=9F=94=B4=20UtilV?= =?UTF-8?q?alidator=20isInteger=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력한 값이 정수인지 테스트 성공/실패 케이스 추가 --- __tests__/validator/UtilValidator.test.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/__tests__/validator/UtilValidator.test.js b/__tests__/validator/UtilValidator.test.js index 520cfb46b..418992e83 100644 --- a/__tests__/validator/UtilValidator.test.js +++ b/__tests__/validator/UtilValidator.test.js @@ -1,6 +1,28 @@ import UtilValidator from '../../src/validator/UtilValidator.js'; describe('UtilValidator', () => { + describe('정수인지 검사 isInteger', () => { + test.each([ + [1, true], + [0, true], + [-1, true], + ['0', true], + ['1', true], + ])('⭕성공 테스트 (%s) return %s', (test, expected) => { + expect(UtilValidator.isInteger(test)).toBe(expected); + }); + test.each([ + ['', false], + [' ', false], + [undefined, false], + [null, false], + [0.1, false], + ['str', false], + ])('❌실패 테스트(%s) return %s', (test, expected) => { + expect(UtilValidator.isInteger(test)).toBe(expected); + }); + }); + describe('양수인지 검사 isPositive', () => { test.each([ [1, true], From b35fa5ac66b0ee96f39dff451336197b32c201a5 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:35:02 +0900 Subject: [PATCH 012/109] =?UTF-8?q?feat(validator):=20=F0=9F=9F=A2UtilVali?= =?UTF-8?q?dator.isInteger=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력값이 숫자로 변할 수 있는지 검사하고 정수인지 확인 --- src/validator/UtilValidator.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/validator/UtilValidator.js b/src/validator/UtilValidator.js index 0a834185d..2cd1f0c8c 100644 --- a/src/validator/UtilValidator.js +++ b/src/validator/UtilValidator.js @@ -21,6 +21,11 @@ class UtilValidator { if (Number(param) <= 0) return false; return true; } + + static isInteger(param) { + if (!UtilValidator.isConvertNum(param)) return false; + return Number.isInteger(Number(param)); + } } export default UtilValidator; From be465dcb662e723720b597b34420a0cf0ef7ecf8 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:38:18 +0900 Subject: [PATCH 013/109] =?UTF-8?q?chore(constants):=20ERROR=5FMESSAGES=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 에러 메세지 상수파일 추가 --- src/constants/errorMessages.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/constants/errorMessages.js diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js new file mode 100644 index 000000000..49d756bdd --- /dev/null +++ b/src/constants/errorMessages.js @@ -0,0 +1,10 @@ +const PREFIX = '[ERROR]'; + +const ERROR_MESSAGES = Object.freeze({ + FORMAT_NOT_NUM: `${PREFIX}구매 금액 형식이 잘못되었습니다`, + UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, + POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, + INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, +}); + +export default ERROR_MESSAGES; From f2c31098c5b22dfb023b3ed3c5fd89681d0af6a7 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 04:51:00 +0900 Subject: [PATCH 014/109] =?UTF-8?q?test(validator):=20=F0=9F=94=B4=20Lotto?= =?UTF-8?q?StoreValidator=20validate=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자-정수-양수-단위 테스트를 함 --- .../validator/LottoStoreValidator.test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 __tests__/validator/LottoStoreValidator.test.js diff --git a/__tests__/validator/LottoStoreValidator.test.js b/__tests__/validator/LottoStoreValidator.test.js new file mode 100644 index 000000000..c30fe2fff --- /dev/null +++ b/__tests__/validator/LottoStoreValidator.test.js @@ -0,0 +1,34 @@ +import LottoStoreValidator from '../../src/validator/LottoStoreValidator.js'; +import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; + +describe('LottoStoreValidator', () => { + describe('구매 금액이 유효한지 validate', () => { + test.each([[1000], [14000]])('⭕성공 테스트 (%s) 통과', (test) => { + expect(LottoStoreValidator.validate(test)).not.toThrow(); + }); + + // 1.숫자인지 + // 2.정수인지 + // 3.양수인지 + // 4.1000으로 나누어 떨어지는지 + test.each([ + [undefined, ERROR_MESSAGES.FORMAT_NOT_NUM], + ['', ERROR_MESSAGES.FORMAT_NOT_NUM], + [' ', ERROR_MESSAGES.FORMAT_NOT_NUM], + ['10,', ERROR_MESSAGES.FORMAT_NOT_NUM], + + [0.1, ERROR_MESSAGES.INTEGER], + [-0.1, ERROR_MESSAGES.INTEGER], + ['0.1', ERROR_MESSAGES.INTEGER], + + ['0', ERROR_MESSAGES.POSITVE], + [0, ERROR_MESSAGES.POSITVE], + [-1, ERROR_MESSAGES.POSITVE], + + [900, ERROR_MESSAGES.UNIT], + [1100, ERROR_MESSAGES.UNIT], + ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { + expect(LottoStoreValidator.validate(test)).toThrow(expected); + }); + }); +}); From 8d0a504ccfdcc78189b5ca44e2d9f468c3cbfea7 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 05:31:25 +0900 Subject: [PATCH 015/109] =?UTF-8?q?fix(validator):=20LottoStore=20test=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - expect에 화살표 함수 추가 --- __tests__/validator/LottoStoreValidator.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/validator/LottoStoreValidator.test.js b/__tests__/validator/LottoStoreValidator.test.js index c30fe2fff..a3be81dd5 100644 --- a/__tests__/validator/LottoStoreValidator.test.js +++ b/__tests__/validator/LottoStoreValidator.test.js @@ -4,7 +4,7 @@ import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; describe('LottoStoreValidator', () => { describe('구매 금액이 유효한지 validate', () => { test.each([[1000], [14000]])('⭕성공 테스트 (%s) 통과', (test) => { - expect(LottoStoreValidator.validate(test)).not.toThrow(); + expect(() => LottoStoreValidator.validate(test)).not.toThrow(); }); // 1.숫자인지 @@ -28,7 +28,7 @@ describe('LottoStoreValidator', () => { [900, ERROR_MESSAGES.UNIT], [1100, ERROR_MESSAGES.UNIT], ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { - expect(LottoStoreValidator.validate(test)).toThrow(expected); + expect(() => LottoStoreValidator.validate(test)).toThrow(expected); }); }); }); From fb1a312d3906a294965242f0b6a6fa05274ad3ab Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 05:32:44 +0900 Subject: [PATCH 016/109] =?UTF-8?q?feat(validator):=20=F0=9F=9F=A2LottoSto?= =?UTF-8?q?reValidator=20validate=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타입,정수,양수,단위 순서로 진행 --- src/validator/LottoStoreValidator.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/validator/LottoStoreValidator.js diff --git a/src/validator/LottoStoreValidator.js b/src/validator/LottoStoreValidator.js new file mode 100644 index 000000000..b46b2642f --- /dev/null +++ b/src/validator/LottoStoreValidator.js @@ -0,0 +1,21 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; +import UtilValidator from './UtilValidator.js'; + +class LottoStoreValidator { + static validate(purchase) { + if (!UtilValidator.isConvertNum(purchase)) { + throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); + } + if (!UtilValidator.isInteger(purchase)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + if (!UtilValidator.isPositive(purchase)) { + throw new Error(ERROR_MESSAGES.POSITVE); + } + if (Number(purchase) % 1000 !== 0) { + throw new Error(ERROR_MESSAGES.UNIT); + } + } +} + +export default LottoStoreValidator; From 037eafbd12f86d9c421e1ff22380a0c56bb04959 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 06:09:23 +0900 Subject: [PATCH 017/109] =?UTF-8?q?chore(errorMessages):=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LOTTO_DUPLICATE: 중복검사 - LOTTO_QUANTITY: 수량검사 - LOTTO_RANGE: 범위검사 --- src/constants/errorMessages.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 49d756bdd..0fc2fe1f5 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -2,9 +2,13 @@ const PREFIX = '[ERROR]'; const ERROR_MESSAGES = Object.freeze({ FORMAT_NOT_NUM: `${PREFIX}구매 금액 형식이 잘못되었습니다`, - UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, + + LOTTO_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, + LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, + LOTTO_QUANTITY: `${PREFIX}로또 번호는 6개여야합니다`, + LOTTO_RANGE: `${PREFIX}로또 번호는 1-45여야 합니다`, }); export default ERROR_MESSAGES; From 87711ac04576fe4605d730c682fdc5c0fa99686e Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 06:19:53 +0900 Subject: [PATCH 018/109] =?UTF-8?q?fix(LottoStoreValidator):=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UNIT: LOTTO_UNIT으로 변경 --- __tests__/validator/LottoStoreValidator.test.js | 4 ++-- src/validator/LottoStoreValidator.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/validator/LottoStoreValidator.test.js b/__tests__/validator/LottoStoreValidator.test.js index a3be81dd5..c468738a2 100644 --- a/__tests__/validator/LottoStoreValidator.test.js +++ b/__tests__/validator/LottoStoreValidator.test.js @@ -25,8 +25,8 @@ describe('LottoStoreValidator', () => { [0, ERROR_MESSAGES.POSITVE], [-1, ERROR_MESSAGES.POSITVE], - [900, ERROR_MESSAGES.UNIT], - [1100, ERROR_MESSAGES.UNIT], + [900, ERROR_MESSAGES.LOTTO_UNIT], + [1100, ERROR_MESSAGES.LOTTO_UNIT], ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { expect(() => LottoStoreValidator.validate(test)).toThrow(expected); }); diff --git a/src/validator/LottoStoreValidator.js b/src/validator/LottoStoreValidator.js index b46b2642f..097a51b76 100644 --- a/src/validator/LottoStoreValidator.js +++ b/src/validator/LottoStoreValidator.js @@ -13,7 +13,7 @@ class LottoStoreValidator { throw new Error(ERROR_MESSAGES.POSITVE); } if (Number(purchase) % 1000 !== 0) { - throw new Error(ERROR_MESSAGES.UNIT); + throw new Error(ERROR_MESSAGES.LOTTO_UNIT); } } } From 028bf0b00076285283aaa35501b92655b5565306 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 06:59:17 +0900 Subject: [PATCH 019/109] =?UTF-8?q?test(LottoUtilValidator):=20?= =?UTF-8?q?=F0=9F=94=B4LottoUtilValidator=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로또 숫자 전담하는 유효성검사 테스트 추가 - isQuantity: 수량체크 - isOutRange: 범위체크 - isDuplicate: 중복체크 - hasLottoList: 포함 여부 체크 --- .../validator/LottoUtilValidator.test.js | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 __tests__/validator/LottoUtilValidator.test.js diff --git a/__tests__/validator/LottoUtilValidator.test.js b/__tests__/validator/LottoUtilValidator.test.js new file mode 100644 index 000000000..106a3665a --- /dev/null +++ b/__tests__/validator/LottoUtilValidator.test.js @@ -0,0 +1,66 @@ +import LottoUtilValidator from '../../src/validator/LottoUtilValidator.js'; +// 🔴 번호는 6개인가? +// 🔴 숫자가 1-45인가? +// 🔴 로또 숫자가 중복은 없는가? +// 🔴 해당 번호가 로또 리스트에 포함되어있나? +describe('LottoUtilValidator', () => { + describe('6개가 맞는지 isQuantity', () => { + test.each([[[1, 2, 3, 4, 5, 6], true]])( + '⭕성공 테스트(%s) throw Error %s', + (test, expected) => { + expect(LottoUtilValidator.isQuantity(test)).toBe(expected); + }, + ); + test.each([ + [[1, 2, 3, 4, 5], false], + [[1, 2, 3, 4, 5, 6, 7], false], + ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { + expect(LottoUtilValidator.isQuantity(test)).toBe(expected); + }); + }); + + describe('범위가 1-45인지 isOutRange', () => { + test.each([ + [[0, 2, 3, 4, 5, 6], true], + [[46, 2, 3, 4, 5, 6], true], + ])('⭕성공 테스트(%s) throw Error %s', (test, expected) => { + expect(LottoUtilValidator.isOutRange(test)).toBe(expected); + }); + test.each([[[1, 2, 3, 4, 5, 6], false]])( + '❌실패 테스트(%s) throw Error %s', + (test, expected) => { + expect(LottoUtilValidator.isOutRange(test)).toBe(expected); + }, + ); + }); + + describe('로또 번호가 중복됐는지 isDuplicate', () => { + test.each([[[1, 1, 3, 4, 5, 6], true]])( + '⭕성공 테스트(%s) throw Error %s', + (test, expected) => { + expect(LottoUtilValidator.isDuplicate(test)).toBe(expected); + }, + ); + test.each([[[1, 2, 3, 4, 5, 6], false]])( + '❌실패 테스트(%s) throw Error %s', + (test, expected) => { + expect(LottoUtilValidator.isDuplicate(test)).toBe(expected); + }, + ); + }); + + describe('해당 번호가 중복되어있는지 hasLottoList', () => { + test.each([ + [1, [1, 2, 3, 4, 5, 6], true], + [6, [1, 2, 3, 4, 5, 6], true], + ])('⭕성공 테스트(%s는 %s에 포함되었나?) throw Error %s', (target, list, expected) => { + expect(LottoUtilValidator.hasLottoList(target, list)).toBe(expected); + }); + }); + test.each([ + [11, [1, 2, 3, 4, 5, 6], false], + [31, [1, 2, 3, 4, 5, 6], false], + ])('⭕실패 테스트(%s는 %s에 포함되었나?) throw Error %s', (target, list, expected) => { + expect(LottoUtilValidator.hasLottoList(target, list)).toBe(expected); + }); +}); From 13cbcaa0f45265df02dcc8c1d82303848d0fde23 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 07:00:14 +0900 Subject: [PATCH 020/109] =?UTF-8?q?feat(LottoUtilValidator):=20?= =?UTF-8?q?=F0=9F=9F=A2LottoUtilValidator=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isQuantity: 수량체크 - isOutRange: 범위체크 - isDuplicate: 중복체크 - hasLottoList: 포함 여부 체크 --- src/validator/LottoUtilValidator.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/validator/LottoUtilValidator.js diff --git a/src/validator/LottoUtilValidator.js b/src/validator/LottoUtilValidator.js new file mode 100644 index 000000000..ad60b0841 --- /dev/null +++ b/src/validator/LottoUtilValidator.js @@ -0,0 +1,24 @@ +const QUANTITY = 6; +const MIN_LANGE = 1; +const MAX_LANGE = 45; + +class LottoValidator { + static isQuantity(lotto) { + return lotto.length === QUANTITY; + } + + static isOutRange(lotto) { + return lotto.some((num) => num < MIN_LANGE || MAX_LANGE < num); + } + + static isDuplicate(lotto) { + const setLotto = new Set(lotto); + return setLotto.size !== lotto.length; + } + + static hasLottoList(num, lotto) { + return lotto.includes(num); + } +} + +export default LottoValidator; From 3a9a8157a969a8f6d8801ba2e449dc14137d7b30 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 07:21:50 +0900 Subject: [PATCH 021/109] =?UTF-8?q?test(LottoUtilValidator):=20?= =?UTF-8?q?=F0=9F=94=B4LottoUtilValidator=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isNumber: 각 요소들이 숫자가 맞는지 테스트 --- __tests__/validator/LottoUtilValidator.test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/__tests__/validator/LottoUtilValidator.test.js b/__tests__/validator/LottoUtilValidator.test.js index 106a3665a..e240a890f 100644 --- a/__tests__/validator/LottoUtilValidator.test.js +++ b/__tests__/validator/LottoUtilValidator.test.js @@ -1,9 +1,26 @@ import LottoUtilValidator from '../../src/validator/LottoUtilValidator.js'; +// 🔴 전부 숫자인가? // 🔴 번호는 6개인가? // 🔴 숫자가 1-45인가? // 🔴 로또 숫자가 중복은 없는가? // 🔴 해당 번호가 로또 리스트에 포함되어있나? describe('LottoUtilValidator', () => { + describe('요소들이 숫자가 맞는지', () => { + test.each([[[1, 2, 3, 4, 5, 6], true]])( + '⭕성공 테스트(%s) throw Error %s', + (test, expected) => { + expect(LottoUtilValidator.isNumber(test)).toBe(expected); + }, + ); + test.each([ + [['1, 2, 3, 4, 5,6'], false], + [[null, 2, 3, 4, 5, 6, 7], false], + [[undefined, 2, 3, 4, 5, 6, 7], false], + ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { + expect(LottoUtilValidator.isNumber(test)).toBe(expected); + }); + }); + describe('6개가 맞는지 isQuantity', () => { test.each([[[1, 2, 3, 4, 5, 6], true]])( '⭕성공 테스트(%s) throw Error %s', From 190ea5613efb3ea9cd0f69b4593e1ec34956fa02 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 07:33:23 +0900 Subject: [PATCH 022/109] =?UTF-8?q?feat(LottoUtilValidator):=20?= =?UTF-8?q?=F0=9F=9F=A2isNumber=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 원소값이 숫자인지 판별 --- src/validator/LottoUtilValidator.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/validator/LottoUtilValidator.js b/src/validator/LottoUtilValidator.js index ad60b0841..112871e41 100644 --- a/src/validator/LottoUtilValidator.js +++ b/src/validator/LottoUtilValidator.js @@ -2,7 +2,11 @@ const QUANTITY = 6; const MIN_LANGE = 1; const MAX_LANGE = 45; -class LottoValidator { +class LottoUtilValidator { + static isNumber(lotto) { + return lotto.every((num) => typeof num === 'number'); + } + static isQuantity(lotto) { return lotto.length === QUANTITY; } @@ -21,4 +25,4 @@ class LottoValidator { } } -export default LottoValidator; +export default LottoUtilValidator; From b33e9095b9874bdac0395e5cce9aaf213443785d Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 08:04:17 +0900 Subject: [PATCH 023/109] =?UTF-8?q?refactor(LottoUtilValidator):=20?= =?UTF-8?q?=F0=9F=9F=A1Refactor=20isNumber=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isNumber의 검사부를 UtilValidator.isNum으로 사용 --- src/validator/LottoUtilValidator.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/validator/LottoUtilValidator.js b/src/validator/LottoUtilValidator.js index 112871e41..2d937fa5d 100644 --- a/src/validator/LottoUtilValidator.js +++ b/src/validator/LottoUtilValidator.js @@ -1,10 +1,12 @@ +import UtilValidator from './UtilValidator.js'; + const QUANTITY = 6; const MIN_LANGE = 1; const MAX_LANGE = 45; class LottoUtilValidator { static isNumber(lotto) { - return lotto.every((num) => typeof num === 'number'); + return lotto.every((num) => UtilValidator.isNum(num)); } static isQuantity(lotto) { From ba52ed8b13c334a09a9b19de8c894e97d3c927fb Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 08:23:59 +0900 Subject: [PATCH 024/109] =?UTF-8?q?docs(README):=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=A0=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구현한 기능 목록 추가 - 구현할 기능 목록 추가 --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a89d9b662..48d125d1e 100644 --- a/README.md +++ b/README.md @@ -18,17 +18,38 @@ 아래 [실행과정](#실행과정)의 흐름에 따라 기능 목록을 작성 🔴테스트 만들기 🟢테스트 통과 -🔵리펙토링 +🟡리펙토링완료 ### feat(구매): #### 유효성 검사 -🔴 입력문자열이 숫자로 변환될수있나? -🔴 숫자가 1000원으로 나눠떨어지나? -🔴 숫자가 양수인가? -🔴 숫자가 1-45인가? -🔴 배열에서 중복은 없는가? +##### UtilVaildator: true / false +🟡 숫자로 변환할 수 있는지? +🟡 숫자 타입인지? +🟡 양수인지? +🟡 정수인지? + +##### LottoUtilValidator: true / false +🟡 번호들이 전부 숫자인지? +🟡 번호들의 길이가 6인지? +🟡 번호들의 범위가 1-45인지? +🟡 번호들이 중복되었는지? +🟡 특정 번호가 번호리스트에 포함되어있는지? + +##### LottoValidator: throw Error +🔴 번호들의 길이가 6인지? +🔴 번호들은 숫자인지? +🔴 번호들은 1-45범위에 포함되어 있는지? +🔴 번호들은 중복되지 않았는지? + + +##### LottoStoreValidator: throw Error +🟡 구매금액을 숫자로 변환 할 수 있는지? +🟡 구매금액은 정수인지? +🟡 구매금액은 양수인지? +🟡 구매금액은 1,000원으로 나누어 떨어지는지? + ### 수령 From 187b0f542d91c800633f748f1eff1322b46e8d5f Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 09:04:53 +0900 Subject: [PATCH 025/109] =?UTF-8?q?refactor(errorMessage):=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LOTTO_UNIT: PURCHASE_UNIT으로 변경 --- __tests__/validator/LottoStoreValidator.test.js | 4 ++-- src/constants/errorMessages.js | 4 +++- src/validator/LottoStoreValidator.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/__tests__/validator/LottoStoreValidator.test.js b/__tests__/validator/LottoStoreValidator.test.js index c468738a2..67d511b90 100644 --- a/__tests__/validator/LottoStoreValidator.test.js +++ b/__tests__/validator/LottoStoreValidator.test.js @@ -25,8 +25,8 @@ describe('LottoStoreValidator', () => { [0, ERROR_MESSAGES.POSITVE], [-1, ERROR_MESSAGES.POSITVE], - [900, ERROR_MESSAGES.LOTTO_UNIT], - [1100, ERROR_MESSAGES.LOTTO_UNIT], + [900, ERROR_MESSAGES.PURCHASE_UNIT], + [1100, ERROR_MESSAGES.PURCHASE_UNIT], ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { expect(() => LottoStoreValidator.validate(test)).toThrow(expected); }); diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 0fc2fe1f5..6222ba738 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -5,7 +5,9 @@ const ERROR_MESSAGES = Object.freeze({ POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, - LOTTO_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, + PURCHASE_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, + + LOTTO_NOT_NUMBER: `${PREFIX}로또 번호는 숫자만 추가 할 수 있습니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, LOTTO_QUANTITY: `${PREFIX}로또 번호는 6개여야합니다`, LOTTO_RANGE: `${PREFIX}로또 번호는 1-45여야 합니다`, diff --git a/src/validator/LottoStoreValidator.js b/src/validator/LottoStoreValidator.js index 097a51b76..0ca68f8ee 100644 --- a/src/validator/LottoStoreValidator.js +++ b/src/validator/LottoStoreValidator.js @@ -13,7 +13,7 @@ class LottoStoreValidator { throw new Error(ERROR_MESSAGES.POSITVE); } if (Number(purchase) % 1000 !== 0) { - throw new Error(ERROR_MESSAGES.LOTTO_UNIT); + throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); } } } From 07340db65ab00ffd3fceb0861317e516b94b600d Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 08:59:44 +0900 Subject: [PATCH 026/109] =?UTF-8?q?test(LottoValidator):=20=F0=9F=94=B4Lot?= =?UTF-8?q?toValidator=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타입, 수량, 범위, 중복 검사를 함 --- __tests__/validator/LottoValidator.test.js | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 __tests__/validator/LottoValidator.test.js diff --git a/__tests__/validator/LottoValidator.test.js b/__tests__/validator/LottoValidator.test.js new file mode 100644 index 000000000..91edaaf70 --- /dev/null +++ b/__tests__/validator/LottoValidator.test.js @@ -0,0 +1,29 @@ +import LottoValidator from '../../src/validator/LottoValidator.js'; +import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; + +describe('LottoValidator', () => { + describe('로또를 생성할 수 있는지 validate', () => { + test.each([[[1, 2, 3, 4, 5, 6]]])('⭕성공 테스트 (%s) 통과', (test) => { + expect(() => LottoValidator.validate(test)).not.toThrow(); + }); + // 1. 번호들은 숫자인지? + // 2. 번호들의 길이가 6인지? + // 3. 번호들은 1-45범위에 포함되어 있는지? + // 4. 번호들은 중복되지 않았는지? + test.each([ + [['', 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], + [[undefined, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], + [['1', 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], + + [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], + [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], + + [[0, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], + [[46, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], + + [[1, 1, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_DUPLICATE], + ])('❌실패 테스트 (%s) throw Error %s', (test, expected) => { + expect(() => LottoValidator.validate(test)).toThrow(expected); + }); + }); +}); From 842621601e7bc599be233d4e5149d70f25807aea Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 09:38:03 +0900 Subject: [PATCH 027/109] =?UTF-8?q?feat(LottoValidator):=20=F0=9F=9F=A2Lot?= =?UTF-8?q?toValidator=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타입, 수량, 범위, 중복 검사를 함 --- src/validator/LottoValidator.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/validator/LottoValidator.js diff --git a/src/validator/LottoValidator.js b/src/validator/LottoValidator.js new file mode 100644 index 000000000..6f9ab34f0 --- /dev/null +++ b/src/validator/LottoValidator.js @@ -0,0 +1,21 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LottoUtilValidator from './LottoUtilValidator.js'; + +class LottoValidator { + static validate(lotto) { + if (!LottoUtilValidator.isNumber(lotto)) { + throw new Error(ERROR_MESSAGES.LOTTO_NOT_NUMBER); + } + if (!LottoUtilValidator.isQuantity(lotto)) { + throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); + } + if (LottoUtilValidator.isOutRange(lotto)) { + throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + } + if (LottoUtilValidator.isDuplicate(lotto)) { + throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); + } + } +} + +export default LottoValidator; From 01c154ed39923204a7b89434a213772859d8b9a4 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 09:49:45 +0900 Subject: [PATCH 028/109] =?UTF-8?q?test(Lotto):=20=F0=9F=94=B4Lotto=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=9D=98=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타입, 수량, 범위 검사를 추가 --- __tests__/LottoTest.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 19d576106..67aaac8a0 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,4 +1,4 @@ -import Lotto from '../src/Lotto'; +import Lotto from '../src/Lotto.js'; describe('로또 클래스 테스트', () => { test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { @@ -7,12 +7,30 @@ describe('로또 클래스 테스트', () => { }).toThrow('[ERROR]'); }); - // TODO: 테스트가 통과하도록 프로덕션 코드 구현 + test('로또 번호에 숫자가 없으면 예외가 발생한다.', () => { + expect(() => { + new Lotto(['1', 2, 3, 4, 5, 6]); + }).toThrow('[ERROR]'); + }); + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5]); + }).toThrow('[ERROR]'); + expect(() => { + new Lotto([1, 2, 3, 4, 5, 6, 7]); + }).toThrow('[ERROR]'); + }); + + test('로또 번호가 1-45를 벗어나면 예외가 발생한다.', () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); }).toThrow('[ERROR]'); }); - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 5]); + }).toThrow('[ERROR]'); + }); }); From 9e4f98806713023b84bfbd119c6b35c10c5f8987 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 11:30:45 +0900 Subject: [PATCH 029/109] =?UTF-8?q?test(Lotto):=20=F0=9F=9F=A2Lotto=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=9D=98=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타입, 수량, 범위, 중복여부를 검사 --- src/Lotto.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index 1164a3f81..6b10bf3ea 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,3 +1,5 @@ +import LottoValidator from './validator/LottoValidator'; + class Lotto { #numbers; @@ -7,11 +9,8 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 6) { - throw new Error('[ERROR] 로또 번호는 6개여야 합니다.'); - } + LottoValidator.validate(numbers); } - // TODO: 추가 기능 구현 } From 497de6f8174e16b2fa64f331fa431788ff11846d Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 12:07:39 +0900 Subject: [PATCH 030/109] =?UTF-8?q?refactor(Lotto):=20=F0=9F=9F=A1Lotto=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=9C=84=EC=B9=98=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /src-> /src/domain --- __tests__/LottoTest.js | 2 +- src/{ => domain}/Lotto.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{ => domain}/Lotto.js (80%) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 67aaac8a0..b304d6f60 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,4 +1,4 @@ -import Lotto from '../src/Lotto.js'; +import Lotto from '../src/domain/Lotto.js'; describe('로또 클래스 테스트', () => { test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { diff --git a/src/Lotto.js b/src/domain/Lotto.js similarity index 80% rename from src/Lotto.js rename to src/domain/Lotto.js index 6b10bf3ea..473dfa086 100644 --- a/src/Lotto.js +++ b/src/domain/Lotto.js @@ -1,4 +1,4 @@ -import LottoValidator from './validator/LottoValidator'; +import LottoValidator from '../validator/LottoValidator'; class Lotto { #numbers; From 093b423dc219558d354b651187f643c41a521df7 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 12:22:22 +0900 Subject: [PATCH 031/109] =?UTF-8?q?refactor(lottoSetting):=20=F0=9F=9F=A1?= =?UTF-8?q?=EB=A1=9C=EB=98=90=20=EA=B4=80=EB=A0=A8=20=EC=93=B0=EB=8D=98=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EC=9C=84=EC=B9=98=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UtilValidator.js-> constants --- src/constants/lottoSetting.js | 6 ++++++ src/validator/LottoUtilValidator.js | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 src/constants/lottoSetting.js diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js new file mode 100644 index 000000000..5140dd0d3 --- /dev/null +++ b/src/constants/lottoSetting.js @@ -0,0 +1,6 @@ +const LOTTO_SETTING = Object.freeze({ + QUANTITY: 6, + MIN_LANGE: 1, + MAX_LANGE: 45, +}); +export default LOTTO_SETTING; diff --git a/src/validator/LottoUtilValidator.js b/src/validator/LottoUtilValidator.js index 2d937fa5d..30924cadf 100644 --- a/src/validator/LottoUtilValidator.js +++ b/src/validator/LottoUtilValidator.js @@ -1,8 +1,5 @@ import UtilValidator from './UtilValidator.js'; - -const QUANTITY = 6; -const MIN_LANGE = 1; -const MAX_LANGE = 45; +import LOTTO_SETTING from '../constants/lottoSetting.js'; class LottoUtilValidator { static isNumber(lotto) { @@ -10,11 +7,13 @@ class LottoUtilValidator { } static isQuantity(lotto) { - return lotto.length === QUANTITY; + return lotto.length === LOTTO_SETTING.QUANTITY; } static isOutRange(lotto) { - return lotto.some((num) => num < MIN_LANGE || MAX_LANGE < num); + const underCondition = (num) => num < LOTTO_SETTING.MIN_LANGE; + const overCondition = (num) => LOTTO_SETTING.MAX_LANGE < num; + return lotto.some((num) => underCondition(num) || overCondition(num)); } static isDuplicate(lotto) { From e6310f2c90dea50700b579ac1c4980057298beb4 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 12:28:56 +0900 Subject: [PATCH 032/109] =?UTF-8?q?test(Lotto):=20=F0=9F=94=B4LottoFactory?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=9D=98=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lotto의 type으로 이루어진 lottos 배열이 반환됨 --- __tests__/domain/LottoFacotry.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 __tests__/domain/LottoFacotry.test.js diff --git a/__tests__/domain/LottoFacotry.test.js b/__tests__/domain/LottoFacotry.test.js new file mode 100644 index 000000000..b8aef9b91 --- /dev/null +++ b/__tests__/domain/LottoFacotry.test.js @@ -0,0 +1,12 @@ +import Lotto from '../../src/domain/Lotto.js'; + +describe('LottoFactory', () => { + test('create 구매하면 Lotto List반환', () => { + const purchaseAmount = 10000; + const lottos = LottoFactory.create(purchaseAmount); + expect(lottos).toHaveLength(10); + lottos.forEach((lotto) => { + expect(lotto).toBeInstanceOf(Lotto); + }); + }); +}); From c8a09bcea31d3f08c1f6d6c5bd258993bf367438 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 12:47:09 +0900 Subject: [PATCH 033/109] =?UTF-8?q?feat(LottoFactory):=20=F0=9F=9F=A2Lotto?= =?UTF-8?q?Factory=20crate=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/domain/LottoFacotry.test.js | 2 ++ src/domain/LottoFactory.js | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/domain/LottoFactory.js diff --git a/__tests__/domain/LottoFacotry.test.js b/__tests__/domain/LottoFacotry.test.js index b8aef9b91..8607f84af 100644 --- a/__tests__/domain/LottoFacotry.test.js +++ b/__tests__/domain/LottoFacotry.test.js @@ -1,8 +1,10 @@ import Lotto from '../../src/domain/Lotto.js'; +import LottoFactory from '../../src/domain/LottoFactory.js'; describe('LottoFactory', () => { test('create 구매하면 Lotto List반환', () => { const purchaseAmount = 10000; + const lottos = LottoFactory.create(purchaseAmount); expect(lottos).toHaveLength(10); lottos.forEach((lotto) => { diff --git a/src/domain/LottoFactory.js b/src/domain/LottoFactory.js new file mode 100644 index 000000000..8167ef806 --- /dev/null +++ b/src/domain/LottoFactory.js @@ -0,0 +1,25 @@ +import { Random } from '@woowacourse/mission-utils'; +import Lotto from './Lotto.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; + +function getRandomNumber() { + return Random.pickUniqueNumbersInRange( + LOTTO_SETTING.MIN_LANGE, + LOTTO_SETTING.MAX_LANGE, + LOTTO_SETTING.QUANTITY, + ); +} + +class LottoFactory { + static create(purchaseAmount) { + const lotts = []; + const quan = purchaseAmount / 1000; + for (let i = 0; i < quan; i += 1) { + const random = getRandomNumber(); + lotts.push(new Lotto(random)); + } + return lotts; + } +} + +export default LottoFactory; From 8ef3a7c13ed01e71b67fcef68fee099fa8a3636f Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 13:00:40 +0900 Subject: [PATCH 034/109] =?UTF-8?q?refactor(LottoFactory):=20=F0=9F=9F=A1R?= =?UTF-8?q?andom=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=B0=98=EB=B3=B5=EB=AC=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 랜덤 생성 함수 유틸로 분리 - for 반복을 함수형으로 변경 --- src/constants/lottoSetting.js | 1 + src/domain/LottoFactory.js | 21 +++++---------------- src/utils/getRandomNumber.js | 11 +++++++++++ src/validator/LottoStoreValidator.js | 3 ++- 4 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 src/utils/getRandomNumber.js diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 5140dd0d3..9d7ebd9e0 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -2,5 +2,6 @@ const LOTTO_SETTING = Object.freeze({ QUANTITY: 6, MIN_LANGE: 1, MAX_LANGE: 45, + PRICE: 1000, }); export default LOTTO_SETTING; diff --git a/src/domain/LottoFactory.js b/src/domain/LottoFactory.js index 8167ef806..89f2019c0 100644 --- a/src/domain/LottoFactory.js +++ b/src/domain/LottoFactory.js @@ -1,24 +1,13 @@ -import { Random } from '@woowacourse/mission-utils'; import Lotto from './Lotto.js'; +import getRandomNumber from '../utils/getRandomNumber'; import LOTTO_SETTING from '../constants/lottoSetting.js'; -function getRandomNumber() { - return Random.pickUniqueNumbersInRange( - LOTTO_SETTING.MIN_LANGE, - LOTTO_SETTING.MAX_LANGE, - LOTTO_SETTING.QUANTITY, - ); -} - class LottoFactory { static create(purchaseAmount) { - const lotts = []; - const quan = purchaseAmount / 1000; - for (let i = 0; i < quan; i += 1) { - const random = getRandomNumber(); - lotts.push(new Lotto(random)); - } - return lotts; + const quan = purchaseAmount / LOTTO_SETTING.PRICE; + return Array.from({ length: quan }, () => getRandomNumber()).map( + (numbers) => new Lotto(numbers), + ); } } diff --git a/src/utils/getRandomNumber.js b/src/utils/getRandomNumber.js new file mode 100644 index 000000000..58ef55593 --- /dev/null +++ b/src/utils/getRandomNumber.js @@ -0,0 +1,11 @@ +import { Random } from '@woowacourse/mission-utils'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; + +function getRandomNumber() { + return Random.pickUniqueNumbersInRange( + LOTTO_SETTING.MIN_LANGE, + LOTTO_SETTING.MAX_LANGE, + LOTTO_SETTING.QUANTITY, + ); +} +export default getRandomNumber; diff --git a/src/validator/LottoStoreValidator.js b/src/validator/LottoStoreValidator.js index 0ca68f8ee..b14c762d6 100644 --- a/src/validator/LottoStoreValidator.js +++ b/src/validator/LottoStoreValidator.js @@ -1,4 +1,5 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; import UtilValidator from './UtilValidator.js'; class LottoStoreValidator { @@ -12,7 +13,7 @@ class LottoStoreValidator { if (!UtilValidator.isPositive(purchase)) { throw new Error(ERROR_MESSAGES.POSITVE); } - if (Number(purchase) % 1000 !== 0) { + if (Number(purchase) % LOTTO_SETTING.PRICE !== 0) { throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); } } From 4c604f2adea7d9af25a77e148760ef965095a556 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 13:53:26 +0900 Subject: [PATCH 035/109] =?UTF-8?q?test(Lotto):=20=F0=9F=94=B4getNumbers?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 항상 정렬된 값으로 나와야함 - sort는 내부 로직 --- __tests__/LottoTest.js | 64 +++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index b304d6f60..75f1a3117 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,36 +1,48 @@ import Lotto from '../src/domain/Lotto.js'; describe('로또 클래스 테스트', () => { - test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow('[ERROR]'); - }); + describe('유효성 검사', () => { + test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 6, 7]); + }).toThrow('[ERROR]'); + }); - test('로또 번호에 숫자가 없으면 예외가 발생한다.', () => { - expect(() => { - new Lotto(['1', 2, 3, 4, 5, 6]); - }).toThrow('[ERROR]'); - }); + test('로또 번호에 숫자가 없으면 예외가 발생한다.', () => { + expect(() => { + new Lotto(['1', 2, 3, 4, 5, 6]); + }).toThrow('[ERROR]'); + }); - test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5]); - }).toThrow('[ERROR]'); - expect(() => { - new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow('[ERROR]'); - }); + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5]); + }).toThrow('[ERROR]'); + expect(() => { + new Lotto([1, 2, 3, 4, 5, 6, 7]); + }).toThrow('[ERROR]'); + }); - test('로또 번호가 1-45를 벗어나면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow('[ERROR]'); + test('로또 번호가 1-45를 벗어나면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 5]); + }).toThrow('[ERROR]'); + }); + + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 5]); + }).toThrow('[ERROR]'); + }); }); - test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow('[ERROR]'); + describe('메서드 테스트', () => { + test('getNumbers 하면 정렬된 배열이 나와야함', () => { + const originalNumbers = [2, 1, 3, 4, 5, 6]; + const newLotto = new Lotto(originalNumbers); + + const resultNumbers = [1, 2, 3, 4, 5, 6]; + expect(newLotto.getNumbers()).toEqual(resultNumbers); + }); }); }); From 3748125960c38357ca2005f7639bf79e09d8a22e Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 13:54:21 +0900 Subject: [PATCH 036/109] =?UTF-8?q?feat(Lotto):=20=F0=9F=9F=A2getNumbers?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 오름차순 정렬된 numbers 배열을 복사해서 반환 --- src/domain/Lotto.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/domain/Lotto.js b/src/domain/Lotto.js index 473dfa086..ecf92b572 100644 --- a/src/domain/Lotto.js +++ b/src/domain/Lotto.js @@ -5,13 +5,20 @@ class Lotto { constructor(numbers) { this.#validate(numbers); - this.#numbers = numbers; + this.#numbers = this.#sort(numbers); } #validate(numbers) { LottoValidator.validate(numbers); } - // TODO: 추가 기능 구현 + + #sort(numbers) { + return numbers.sort((a, b) => a - b); + } + + getNumbers() { + return [...this.#numbers]; + } } export default Lotto; From 61589bddfda32a6ab0b5d737e7574dbff6a93e28 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 14:50:54 +0900 Subject: [PATCH 037/109] =?UTF-8?q?test(AccountBook):=20=F0=9F=94=B4?= =?UTF-8?q?=EA=B0=80=EA=B3=84=EB=B6=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지출과 수입을 저장하고 확인 - 소수점 둘째자리에서 반올림한 수익률 계산 --- __tests__/domain/AccountBook.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 __tests__/domain/AccountBook.test.js diff --git a/__tests__/domain/AccountBook.test.js b/__tests__/domain/AccountBook.test.js new file mode 100644 index 000000000..65a1d9b95 --- /dev/null +++ b/__tests__/domain/AccountBook.test.js @@ -0,0 +1,28 @@ +import AccountBook from '../../src/domain/AccountBook.js'; + +describe('AccountBook', () => { + let accountBook; + beforeEach(() => { + accountBook = new AccountBook(); + }); + + test('writePurchase로 지출 저장 getPurchase로 확인', () => { + const purchaseAmount = 5000; + accountBook.writePurchase(purchaseAmount); + expect(accountBook.getPurchase()).toBe(5000); + }); + + test('writeIncome로 수입저장 getIncome 로 확인', () => { + const winningAmount = 5000; + accountBook.writeIncome(winningAmount); + expect(accountBook.getIncome()).toBe(5000); + }); + + test('calculrateYield() 수익률을 반환함', () => { + const purchaseAmount = 8000; + const winningAmount = 5000; + accountBook.writePurchase(purchaseAmount); + accountBook.writeIncome(winningAmount); + expect(accountBook.calculrateYield()).toBe(62.5); + }); +}); From bdcadc85b4f1661f0d3a3fd648b55a0f674d56b7 Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 14:51:08 +0900 Subject: [PATCH 038/109] =?UTF-8?q?feat(AccountBook):=F0=9F=9F=A2=EA=B0=80?= =?UTF-8?q?=EA=B3=84=EB=B6=80=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수입과 지출을 저장하고 반환 - 수익률을 계산후 숫자로 반환 --- src/domain/AccountBook.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/domain/AccountBook.js diff --git a/src/domain/AccountBook.js b/src/domain/AccountBook.js new file mode 100644 index 000000000..5925bc8e9 --- /dev/null +++ b/src/domain/AccountBook.js @@ -0,0 +1,26 @@ +class AccountBook { + #purchaseAmount = 0; + #incomeAmount = 0; + + writePurchase(purchase) { + this.#purchaseAmount += purchase; + } + + writeIncome(winningAmount) { + this.#incomeAmount += winningAmount; + } + + getPurchase() { + return this.#purchaseAmount; + } + + getIncome() { + return this.#incomeAmount; + } + + calculrateYield() { + const myYield = (this.#incomeAmount / this.#purchaseAmount) * 100; + return Number(myYield.toFixed(1)); + } +} +export default AccountBook; From e801030baeabbb2c4a40e185c293de4d723fc82a Mon Sep 17 00:00:00 2001 From: iftype Date: Thu, 30 Oct 2025 14:57:02 +0900 Subject: [PATCH 039/109] =?UTF-8?q?docs(README):=20README=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=ED=95=9C=20=EA=B8=B0=EB=8A=A5=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구현한 기능 업데이트 --- README.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 48d125d1e..8bfc1a90b 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ 🟡 특정 번호가 번호리스트에 포함되어있는지? ##### LottoValidator: throw Error -🔴 번호들의 길이가 6인지? -🔴 번호들은 숫자인지? -🔴 번호들은 1-45범위에 포함되어 있는지? -🔴 번호들은 중복되지 않았는지? +🟡 번호들의 길이가 6인지? +🟡 번호들은 숫자인지? +🟡 번호들은 1-45범위에 포함되어 있는지? +🟡 번호들은 중복되지 않았는지? ##### LottoStoreValidator: throw Error @@ -51,6 +51,25 @@ 🟡 구매금액은 1,000원으로 나누어 떨어지는지? +#### Domain + +##### Lotto +🟡 생성 가능한지? +🟡 오름차순 정렬 + +##### LottoFactory +🟡 랜덤한 숫자 6개로 로또 리스트 생성 + +##### AccountBook +🟡 지출 금액을 기입 +🟡 지출 금액을 추가 +🟡 당첨 금액을 기입 +🟡 당첨 금액을 추가 +🟡 수익률 확인 + +#### utils + +랜덤한 숫자 6개를 가져옴 ### 수령 @@ -66,6 +85,8 @@ ### 구매 +Image + 1. 컨트롤러는 구매 함수 실행 2. 컨트롤러는 인풋한테 얼마 살건지 입력받음 - 숫자로 변할 수 있는 지 검사 From 574491e1adeb3eef3a8be0708f3abab90be2878c Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 05:33:04 +0900 Subject: [PATCH 040/109] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 90 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8bfc1a90b..f535ea17f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,57 @@ # 로또 +## 3주차 과제의 숨겨진 의도를 발견하다 +개발하다 구조의 잘못됨을 깨닫고 전부 날린 뒤 새로 작업을 시작했다 +새로 작업한 이유는 다음과 같다 +1. [프리코스의 한 글](https://velog.io/@hayooncode/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-8%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9C%BC%EB%A1%9C%EC%9D%98-%EC%97%AC%EC%A0%95)을 보고 객체지향에 대해 다시 한번 고민하게 됨 +2. 그러다 로또 자체가 일을 다 하는 방법을 찾아보기 시작함 +3. nextjs로 블로그를 만드려고 공부하다 캐싱이란걸 보게됨 +4. 이 프로젝트에 캐싱을 적용하려고 보니.. +[사진] +5. 모든 퍼즐이 맞춰짐 + + + + + +## 진짜 구현할 기능 목록 + +### LottoNumber +로또넘버 하나 하나를 객체로 본다 +- [ ] 1-45 범위 내에서 정수 값을 프로퍼티로 가짐 + +### LottoNumberFactory +로또 넘버를 인스턴스화시킴 +- [ ] 프로그램 시작시 로또 번호들을 인스턴스화시킨다 + +### Lotto +- [ ] 6개의 lottoNumber의 참조를 프로퍼티로 가짐 +- [ ] 당첨 번호와 일치하는지 확인 +- [ ] 보너스 번호와 일치하는지 확인 + +### LottoWinning +- [ ] 당첨금을 알려줌 + +### LottoStroe +- [ ] 로또를 살 수 있음 +- [ ] 랜덤한 숫자를 받아옴 + +### Account +- [ ] 구매 금액을 저장 +- [ ] 수익률을 계산 + + + + + + +--- + +## 과제 포맷하기 전 구현할 기능 목록 + +
+🟢여기를 클릭하면 기능 목록이 열립니다 + ## 소개 Image @@ -7,8 +59,8 @@ 저는 이번 과제를 크게 **세 가지 기능**으로 나눠 구현하려고 합니다 1. 구매 -2. 수령 -3. 수익률 +1. 수령 +2. 수익률 레드-그린-리팩토링 사이클을 도입할 것이고 **구매** 먼저 구현하려고 합니다 @@ -100,18 +152,18 @@ - 1,000원으로 딱 나누어 떨어지는지? 7. 로또 스토어는 받아온 값으로 몇 장 살 수 있나 계산 8. 계산한 값으로 로또 팩토리에게 요청 -9. 로또 팩토리는 받아온 값을 검사 +3. 로또 팩토리는 받아온 값을 검사 - 양수인지? -10. 로또 팩토리는 랜덤 함수를 사용해 숫자 6개를 받아옴 +4. 로또 팩토리는 랜덤 함수를 사용해 숫자 6개를 받아옴 - 숫자로 변할 수 있는지? - 범위에 맞는지?(1-45) - 값이 중복되진 않았는지? -11. 숫자들을 로또에게 넘겨 로또를 생성 요청 -12. 로또는 생성 시 받아온 값의 검사 +5. 숫자들을 로또에게 넘겨 로또를 생성 요청 +6. 로또는 생성 시 받아온 값의 검사 - 6개인지? -13. 로또 팩토리는 생성된 로또들을 반환 -14. 이 반환 값을 서비스가 자신의 상태에 저장하고 반환 -15. 컨트롤러는 받아온 값으로 출력함수 실행 +7. 로또 팩토리는 생성된 로또들을 반환 +8. 이 반환 값을 서비스가 자신의 상태에 저장하고 반환 +9. 컨트롤러는 받아온 값으로 출력함수 실행 ### 수령 @@ -123,15 +175,15 @@ 6. 로또당첨 객체는 생성과 동시에 번호들 유효성 검사를 진행 - 당첨 번호의 유효성 검사 - 타입, 범위 - 보너스 번호의 유효성 검사 - 타입, 중복, 범위 -7. 서비스는 로또당첨에 로또번호 리스트를 넘겨서 몇 개 당첨됐나 물어봄 -8. 로또당첨은 로또에게 당첨 번호와 몇 개 겹치나 물어봄 -9. 로또당첨은 로또에게 보너스 번호는 겹치나 물어봄 -10. 이제 이 당첨 번호 숫자로 등수를 판별함 -11. 판별한 등수를 서비스에게 리턴 -12. 서비스는 받아온 숫자로 로또뱅크에게 물어봄 -13. 로또뱅크는 등수에 따른 금액을 판별 -14. 서비스는 가계부에 수익을 적고 컨트롤러에 리턴 -15. 컨트롤러는 해당 값으로 출력실행 +1. 서비스는 로또당첨에 로또번호 리스트를 넘겨서 몇 개 당첨됐나 물어봄 +2. 로또당첨은 로또에게 당첨 번호와 몇 개 겹치나 물어봄 +3. 로또당첨은 로또에게 보너스 번호는 겹치나 물어봄 +4. 이제 이 당첨 번호 숫자로 등수를 판별함 +5. 판별한 등수를 서비스에게 리턴 +6. 서비스는 받아온 숫자로 로또뱅크에게 물어봄 +7. 로또뱅크는 등수에 따른 금액을 판별 +8. 서비스는 가계부에 수익을 적고 컨트롤러에 리턴 +9. 컨트롤러는 해당 값으로 출력실행 ### 수익률 @@ -140,6 +192,8 @@ 3. 가계부는 저장 되어있는 값으로 수익률을 계산하여 반환 4. 컨트롤러는 해당 값으로 출력실행 +
+ --- ## 요구 사항 From a215d0bfb74d97b9325cccb1b4263ca83bd6f521 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 07:15:28 +0900 Subject: [PATCH 041/109] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/domain/AccountBook.test.js | 28 ------ __tests__/domain/LottoFacotry.test.js | 14 --- .../validator/LottoStoreValidator.test.js | 34 -------- .../validator/LottoUtilValidator.test.js | 83 ------------------ __tests__/validator/LottoValidator.test.js | 29 ------- __tests__/validator/UtilValidator.test.js | 87 ------------------- src/constants/lottoSetting.js | 7 -- src/domain/AccountBook.js | 26 ------ src/domain/Lotto.js | 24 ----- src/domain/LottoFactory.js | 14 --- src/utils/getRandomNumber.js | 11 --- src/validator/LottoStoreValidator.js | 22 ----- src/validator/LottoUtilValidator.js | 29 ------- src/validator/LottoValidator.js | 21 ----- 14 files changed, 429 deletions(-) delete mode 100644 __tests__/domain/AccountBook.test.js delete mode 100644 __tests__/domain/LottoFacotry.test.js delete mode 100644 __tests__/validator/LottoStoreValidator.test.js delete mode 100644 __tests__/validator/LottoUtilValidator.test.js delete mode 100644 __tests__/validator/LottoValidator.test.js delete mode 100644 __tests__/validator/UtilValidator.test.js delete mode 100644 src/constants/lottoSetting.js delete mode 100644 src/domain/AccountBook.js delete mode 100644 src/domain/Lotto.js delete mode 100644 src/domain/LottoFactory.js delete mode 100644 src/utils/getRandomNumber.js delete mode 100644 src/validator/LottoStoreValidator.js delete mode 100644 src/validator/LottoUtilValidator.js delete mode 100644 src/validator/LottoValidator.js diff --git a/__tests__/domain/AccountBook.test.js b/__tests__/domain/AccountBook.test.js deleted file mode 100644 index 65a1d9b95..000000000 --- a/__tests__/domain/AccountBook.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import AccountBook from '../../src/domain/AccountBook.js'; - -describe('AccountBook', () => { - let accountBook; - beforeEach(() => { - accountBook = new AccountBook(); - }); - - test('writePurchase로 지출 저장 getPurchase로 확인', () => { - const purchaseAmount = 5000; - accountBook.writePurchase(purchaseAmount); - expect(accountBook.getPurchase()).toBe(5000); - }); - - test('writeIncome로 수입저장 getIncome 로 확인', () => { - const winningAmount = 5000; - accountBook.writeIncome(winningAmount); - expect(accountBook.getIncome()).toBe(5000); - }); - - test('calculrateYield() 수익률을 반환함', () => { - const purchaseAmount = 8000; - const winningAmount = 5000; - accountBook.writePurchase(purchaseAmount); - accountBook.writeIncome(winningAmount); - expect(accountBook.calculrateYield()).toBe(62.5); - }); -}); diff --git a/__tests__/domain/LottoFacotry.test.js b/__tests__/domain/LottoFacotry.test.js deleted file mode 100644 index 8607f84af..000000000 --- a/__tests__/domain/LottoFacotry.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import Lotto from '../../src/domain/Lotto.js'; -import LottoFactory from '../../src/domain/LottoFactory.js'; - -describe('LottoFactory', () => { - test('create 구매하면 Lotto List반환', () => { - const purchaseAmount = 10000; - - const lottos = LottoFactory.create(purchaseAmount); - expect(lottos).toHaveLength(10); - lottos.forEach((lotto) => { - expect(lotto).toBeInstanceOf(Lotto); - }); - }); -}); diff --git a/__tests__/validator/LottoStoreValidator.test.js b/__tests__/validator/LottoStoreValidator.test.js deleted file mode 100644 index 67d511b90..000000000 --- a/__tests__/validator/LottoStoreValidator.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import LottoStoreValidator from '../../src/validator/LottoStoreValidator.js'; -import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; - -describe('LottoStoreValidator', () => { - describe('구매 금액이 유효한지 validate', () => { - test.each([[1000], [14000]])('⭕성공 테스트 (%s) 통과', (test) => { - expect(() => LottoStoreValidator.validate(test)).not.toThrow(); - }); - - // 1.숫자인지 - // 2.정수인지 - // 3.양수인지 - // 4.1000으로 나누어 떨어지는지 - test.each([ - [undefined, ERROR_MESSAGES.FORMAT_NOT_NUM], - ['', ERROR_MESSAGES.FORMAT_NOT_NUM], - [' ', ERROR_MESSAGES.FORMAT_NOT_NUM], - ['10,', ERROR_MESSAGES.FORMAT_NOT_NUM], - - [0.1, ERROR_MESSAGES.INTEGER], - [-0.1, ERROR_MESSAGES.INTEGER], - ['0.1', ERROR_MESSAGES.INTEGER], - - ['0', ERROR_MESSAGES.POSITVE], - [0, ERROR_MESSAGES.POSITVE], - [-1, ERROR_MESSAGES.POSITVE], - - [900, ERROR_MESSAGES.PURCHASE_UNIT], - [1100, ERROR_MESSAGES.PURCHASE_UNIT], - ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { - expect(() => LottoStoreValidator.validate(test)).toThrow(expected); - }); - }); -}); diff --git a/__tests__/validator/LottoUtilValidator.test.js b/__tests__/validator/LottoUtilValidator.test.js deleted file mode 100644 index e240a890f..000000000 --- a/__tests__/validator/LottoUtilValidator.test.js +++ /dev/null @@ -1,83 +0,0 @@ -import LottoUtilValidator from '../../src/validator/LottoUtilValidator.js'; -// 🔴 전부 숫자인가? -// 🔴 번호는 6개인가? -// 🔴 숫자가 1-45인가? -// 🔴 로또 숫자가 중복은 없는가? -// 🔴 해당 번호가 로또 리스트에 포함되어있나? -describe('LottoUtilValidator', () => { - describe('요소들이 숫자가 맞는지', () => { - test.each([[[1, 2, 3, 4, 5, 6], true]])( - '⭕성공 테스트(%s) throw Error %s', - (test, expected) => { - expect(LottoUtilValidator.isNumber(test)).toBe(expected); - }, - ); - test.each([ - [['1, 2, 3, 4, 5,6'], false], - [[null, 2, 3, 4, 5, 6, 7], false], - [[undefined, 2, 3, 4, 5, 6, 7], false], - ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { - expect(LottoUtilValidator.isNumber(test)).toBe(expected); - }); - }); - - describe('6개가 맞는지 isQuantity', () => { - test.each([[[1, 2, 3, 4, 5, 6], true]])( - '⭕성공 테스트(%s) throw Error %s', - (test, expected) => { - expect(LottoUtilValidator.isQuantity(test)).toBe(expected); - }, - ); - test.each([ - [[1, 2, 3, 4, 5], false], - [[1, 2, 3, 4, 5, 6, 7], false], - ])('❌실패 테스트(%s) throw Error %s', (test, expected) => { - expect(LottoUtilValidator.isQuantity(test)).toBe(expected); - }); - }); - - describe('범위가 1-45인지 isOutRange', () => { - test.each([ - [[0, 2, 3, 4, 5, 6], true], - [[46, 2, 3, 4, 5, 6], true], - ])('⭕성공 테스트(%s) throw Error %s', (test, expected) => { - expect(LottoUtilValidator.isOutRange(test)).toBe(expected); - }); - test.each([[[1, 2, 3, 4, 5, 6], false]])( - '❌실패 테스트(%s) throw Error %s', - (test, expected) => { - expect(LottoUtilValidator.isOutRange(test)).toBe(expected); - }, - ); - }); - - describe('로또 번호가 중복됐는지 isDuplicate', () => { - test.each([[[1, 1, 3, 4, 5, 6], true]])( - '⭕성공 테스트(%s) throw Error %s', - (test, expected) => { - expect(LottoUtilValidator.isDuplicate(test)).toBe(expected); - }, - ); - test.each([[[1, 2, 3, 4, 5, 6], false]])( - '❌실패 테스트(%s) throw Error %s', - (test, expected) => { - expect(LottoUtilValidator.isDuplicate(test)).toBe(expected); - }, - ); - }); - - describe('해당 번호가 중복되어있는지 hasLottoList', () => { - test.each([ - [1, [1, 2, 3, 4, 5, 6], true], - [6, [1, 2, 3, 4, 5, 6], true], - ])('⭕성공 테스트(%s는 %s에 포함되었나?) throw Error %s', (target, list, expected) => { - expect(LottoUtilValidator.hasLottoList(target, list)).toBe(expected); - }); - }); - test.each([ - [11, [1, 2, 3, 4, 5, 6], false], - [31, [1, 2, 3, 4, 5, 6], false], - ])('⭕실패 테스트(%s는 %s에 포함되었나?) throw Error %s', (target, list, expected) => { - expect(LottoUtilValidator.hasLottoList(target, list)).toBe(expected); - }); -}); diff --git a/__tests__/validator/LottoValidator.test.js b/__tests__/validator/LottoValidator.test.js deleted file mode 100644 index 91edaaf70..000000000 --- a/__tests__/validator/LottoValidator.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import LottoValidator from '../../src/validator/LottoValidator.js'; -import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; - -describe('LottoValidator', () => { - describe('로또를 생성할 수 있는지 validate', () => { - test.each([[[1, 2, 3, 4, 5, 6]]])('⭕성공 테스트 (%s) 통과', (test) => { - expect(() => LottoValidator.validate(test)).not.toThrow(); - }); - // 1. 번호들은 숫자인지? - // 2. 번호들의 길이가 6인지? - // 3. 번호들은 1-45범위에 포함되어 있는지? - // 4. 번호들은 중복되지 않았는지? - test.each([ - [['', 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], - [[undefined, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], - [['1', 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_NOT_NUMBER], - - [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], - [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], - - [[0, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], - [[46, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], - - [[1, 1, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_DUPLICATE], - ])('❌실패 테스트 (%s) throw Error %s', (test, expected) => { - expect(() => LottoValidator.validate(test)).toThrow(expected); - }); - }); -}); diff --git a/__tests__/validator/UtilValidator.test.js b/__tests__/validator/UtilValidator.test.js deleted file mode 100644 index 418992e83..000000000 --- a/__tests__/validator/UtilValidator.test.js +++ /dev/null @@ -1,87 +0,0 @@ -import UtilValidator from '../../src/validator/UtilValidator.js'; - -describe('UtilValidator', () => { - describe('정수인지 검사 isInteger', () => { - test.each([ - [1, true], - [0, true], - [-1, true], - ['0', true], - ['1', true], - ])('⭕성공 테스트 (%s) return %s', (test, expected) => { - expect(UtilValidator.isInteger(test)).toBe(expected); - }); - test.each([ - ['', false], - [' ', false], - [undefined, false], - [null, false], - [0.1, false], - ['str', false], - ])('❌실패 테스트(%s) return %s', (test, expected) => { - expect(UtilValidator.isInteger(test)).toBe(expected); - }); - }); - - describe('양수인지 검사 isPositive', () => { - test.each([ - [1, true], - [0.1, true], - ['1', true], - ])('⭕성공 테스트 (%s) return %s', (test, expected) => { - expect(UtilValidator.isPositive(test)).toBe(expected); - }); - test.each([ - ['', false], - [undefined, false], - [null, false], - ['0', false], - [0, false], - [-1, false], - ['str', false], - ])('❌실패 테스트(%s) return %s', (test, expected) => { - expect(UtilValidator.isPositive(test)).toBe(expected); - }); - }); - - describe('숫자 변환 가능 여부 검사 isConvertNum', () => { - test.each([ - ['1', true], - ['0', true], - ['-1', true], - ])('성공 테스트 (%s) return %s', (test, expected) => { - expect(UtilValidator.isConvertNum(test)).toBe(expected); - }); - test.each([ - ['1,000', false], - ['1+2', false], - ['', false], - [' ', false], - [undefined, false], - [null, false], - ])('실패 테스트(%s) return %s', (test, expected) => { - expect(UtilValidator.isConvertNum(test)).toBe(expected); - }); - }); - - describe('숫자인지 타입 체크 isNum', () => { - test.each([ - [1, true], - [0, true], - [-1, true], - ])('성공 테스트 (%s) return %s', (test, expected) => { - expect(UtilValidator.isNum(test)).toBe(expected); - }); - test.each([ - ['1,000', false], - ['1+2', false], - ['1', false], - ['', false], - [' ', false], - [undefined, false], - [null, false], - ])('실패 테스트(%s) return %s', (test, expected) => { - expect(UtilValidator.isNum(test)).toBe(expected); - }); - }); -}); diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js deleted file mode 100644 index 9d7ebd9e0..000000000 --- a/src/constants/lottoSetting.js +++ /dev/null @@ -1,7 +0,0 @@ -const LOTTO_SETTING = Object.freeze({ - QUANTITY: 6, - MIN_LANGE: 1, - MAX_LANGE: 45, - PRICE: 1000, -}); -export default LOTTO_SETTING; diff --git a/src/domain/AccountBook.js b/src/domain/AccountBook.js deleted file mode 100644 index 5925bc8e9..000000000 --- a/src/domain/AccountBook.js +++ /dev/null @@ -1,26 +0,0 @@ -class AccountBook { - #purchaseAmount = 0; - #incomeAmount = 0; - - writePurchase(purchase) { - this.#purchaseAmount += purchase; - } - - writeIncome(winningAmount) { - this.#incomeAmount += winningAmount; - } - - getPurchase() { - return this.#purchaseAmount; - } - - getIncome() { - return this.#incomeAmount; - } - - calculrateYield() { - const myYield = (this.#incomeAmount / this.#purchaseAmount) * 100; - return Number(myYield.toFixed(1)); - } -} -export default AccountBook; diff --git a/src/domain/Lotto.js b/src/domain/Lotto.js deleted file mode 100644 index ecf92b572..000000000 --- a/src/domain/Lotto.js +++ /dev/null @@ -1,24 +0,0 @@ -import LottoValidator from '../validator/LottoValidator'; - -class Lotto { - #numbers; - - constructor(numbers) { - this.#validate(numbers); - this.#numbers = this.#sort(numbers); - } - - #validate(numbers) { - LottoValidator.validate(numbers); - } - - #sort(numbers) { - return numbers.sort((a, b) => a - b); - } - - getNumbers() { - return [...this.#numbers]; - } -} - -export default Lotto; diff --git a/src/domain/LottoFactory.js b/src/domain/LottoFactory.js deleted file mode 100644 index 89f2019c0..000000000 --- a/src/domain/LottoFactory.js +++ /dev/null @@ -1,14 +0,0 @@ -import Lotto from './Lotto.js'; -import getRandomNumber from '../utils/getRandomNumber'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; - -class LottoFactory { - static create(purchaseAmount) { - const quan = purchaseAmount / LOTTO_SETTING.PRICE; - return Array.from({ length: quan }, () => getRandomNumber()).map( - (numbers) => new Lotto(numbers), - ); - } -} - -export default LottoFactory; diff --git a/src/utils/getRandomNumber.js b/src/utils/getRandomNumber.js deleted file mode 100644 index 58ef55593..000000000 --- a/src/utils/getRandomNumber.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Random } from '@woowacourse/mission-utils'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; - -function getRandomNumber() { - return Random.pickUniqueNumbersInRange( - LOTTO_SETTING.MIN_LANGE, - LOTTO_SETTING.MAX_LANGE, - LOTTO_SETTING.QUANTITY, - ); -} -export default getRandomNumber; diff --git a/src/validator/LottoStoreValidator.js b/src/validator/LottoStoreValidator.js deleted file mode 100644 index b14c762d6..000000000 --- a/src/validator/LottoStoreValidator.js +++ /dev/null @@ -1,22 +0,0 @@ -import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; -import UtilValidator from './UtilValidator.js'; - -class LottoStoreValidator { - static validate(purchase) { - if (!UtilValidator.isConvertNum(purchase)) { - throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); - } - if (!UtilValidator.isInteger(purchase)) { - throw new Error(ERROR_MESSAGES.INTEGER); - } - if (!UtilValidator.isPositive(purchase)) { - throw new Error(ERROR_MESSAGES.POSITVE); - } - if (Number(purchase) % LOTTO_SETTING.PRICE !== 0) { - throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); - } - } -} - -export default LottoStoreValidator; diff --git a/src/validator/LottoUtilValidator.js b/src/validator/LottoUtilValidator.js deleted file mode 100644 index 30924cadf..000000000 --- a/src/validator/LottoUtilValidator.js +++ /dev/null @@ -1,29 +0,0 @@ -import UtilValidator from './UtilValidator.js'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; - -class LottoUtilValidator { - static isNumber(lotto) { - return lotto.every((num) => UtilValidator.isNum(num)); - } - - static isQuantity(lotto) { - return lotto.length === LOTTO_SETTING.QUANTITY; - } - - static isOutRange(lotto) { - const underCondition = (num) => num < LOTTO_SETTING.MIN_LANGE; - const overCondition = (num) => LOTTO_SETTING.MAX_LANGE < num; - return lotto.some((num) => underCondition(num) || overCondition(num)); - } - - static isDuplicate(lotto) { - const setLotto = new Set(lotto); - return setLotto.size !== lotto.length; - } - - static hasLottoList(num, lotto) { - return lotto.includes(num); - } -} - -export default LottoUtilValidator; diff --git a/src/validator/LottoValidator.js b/src/validator/LottoValidator.js deleted file mode 100644 index 6f9ab34f0..000000000 --- a/src/validator/LottoValidator.js +++ /dev/null @@ -1,21 +0,0 @@ -import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LottoUtilValidator from './LottoUtilValidator.js'; - -class LottoValidator { - static validate(lotto) { - if (!LottoUtilValidator.isNumber(lotto)) { - throw new Error(ERROR_MESSAGES.LOTTO_NOT_NUMBER); - } - if (!LottoUtilValidator.isQuantity(lotto)) { - throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); - } - if (LottoUtilValidator.isOutRange(lotto)) { - throw new Error(ERROR_MESSAGES.LOTTO_RANGE); - } - if (LottoUtilValidator.isDuplicate(lotto)) { - throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); - } - } -} - -export default LottoValidator; From 04cc7bfce84529fd4b9ba3fa9cda6328fda371b9 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 09:17:36 +0900 Subject: [PATCH 042/109] =?UTF-8?q?feat(LottoNumber):=20LottoNumber=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - propertie: 범위와 숫자를 프로퍼티로 가짐 - validator: 내부에서 검사하여 응집도를 증가 - test: 경계 테스트를 진행하여 프로퍼티와 결합도를 낮춤 - 범위는 static으로 선언해 인스턴스를 생성하지 않더라도 사용 가능 --- __tests__/domain/LottoNumber.test.js | 37 +++++++++++++++++++++++++++ src/domain/LottoNumber.js | 38 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 __tests__/domain/LottoNumber.test.js create mode 100644 src/domain/LottoNumber.js diff --git a/__tests__/domain/LottoNumber.test.js b/__tests__/domain/LottoNumber.test.js new file mode 100644 index 000000000..8d7d34d5d --- /dev/null +++ b/__tests__/domain/LottoNumber.test.js @@ -0,0 +1,37 @@ +import LottoNumber from '../../src/domain/LottoNumber.js'; + +describe('LottoNumber 클래스 테스트', () => { + let min; + let max; + + beforeEach(() => { + [min, max] = Object.values(LottoNumber.getRange()); + }); + + describe('생성자 에러 검사', () => { + test(`최소범위 테스트 ${min - 1}`, () => { + expect(() => new LottoNumber(min - 1)).toThrow('[ERROR]'); + }); //test + + test(`최대범위 테스트 ${max - 1}`, () => { + expect(() => new LottoNumber(max + 1)).toThrow('[ERROR]'); + }); //test + }); //describe 생성자 에러 검사 + + describe('생성자 성공 검사', () => { + test(`최소범위 테스트 ${min}`, () => { + expect(() => new LottoNumber(min)).not.toThrow(); + }); //test + + test(`최대범위 테스트 ${max}`, () => { + expect(() => new LottoNumber(max)).not.toThrow(); + }); //test + }); //describe 생성자 성공 검사 + + describe('생성자 성공 검사', () => { + test(`최소범위 테스트 ${min}`, () => { + const lottoNum = new LottoNumber(min); + expect(lottoNum.getNumber()).toBe(min); + }); //test + }); //describe 메서드 검사 +}); //describe diff --git a/src/domain/LottoNumber.js b/src/domain/LottoNumber.js new file mode 100644 index 000000000..e3d2a620b --- /dev/null +++ b/src/domain/LottoNumber.js @@ -0,0 +1,38 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; + +class LottoNumber { + #number; + static #MIN_RANGE = 1; + static #MAX_RANGE = 45; + + constructor(number) { + this.#validate(number); + this.#number = number; + } + + #validate(number) { + if (!this.#isInteger(number)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + if (!this.#isInRange(number)) { + throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + } + } + + #isInteger(number) { + return Number.isInteger(number); + } + + #isInRange(number) { + return LottoNumber.#MIN_RANGE <= number && number <= LottoNumber.#MAX_RANGE; + } + + static getRange() { + return { MIN_RANGE: this.#MIN_RANGE, MAX: this.#MAX_RANGE }; + } + + getNumber() { + return this.#number; + } +} +export default LottoNumber; From 2979f342ad0849ca7906e767259bcf7bebabee0c Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 09:18:12 +0900 Subject: [PATCH 043/109] =?UTF-8?q?feat(LottoNumberFactory):=20LottoNumber?= =?UTF-8?q?Factory=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모듈이 불러와질때 Factory인스턴스 생성 - Lotto에서가져온 범위 만큼 Map에 number 인스턴스 생성 - get은 참조값을 반환 --- __tests__/domain/LottoNumberFactory.test.js | 27 +++++++++++++++++++++ src/domain/LottoNumberFactory.js | 27 +++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 __tests__/domain/LottoNumberFactory.test.js create mode 100644 src/domain/LottoNumberFactory.js diff --git a/__tests__/domain/LottoNumberFactory.test.js b/__tests__/domain/LottoNumberFactory.test.js new file mode 100644 index 000000000..6dacaed8a --- /dev/null +++ b/__tests__/domain/LottoNumberFactory.test.js @@ -0,0 +1,27 @@ +import LottoNumber from '../../src/domain/LottoNumber.js'; +import LottoNumberFactory from '../../src/domain/LottoNumberFactory.js'; +import LottoNumberFactoryCopy from '../../src/domain/LottoNumberFactory.js'; + +const [min, max] = Object.values(LottoNumber.getRange()); +describe('LottoNumberFactory 테스트', () => { + beforeEach(() => {}); + + describe('생성 테스트', () => { + test('같은 인스턴스여야함', () => { + // 싱글턴 참조확인용 + expect(LottoNumberFactory).toBe(LottoNumberFactoryCopy); + }); + }); // 생성 테스트 + + describe('메서드 getLottoNumber 에러테스트', () => { + console.log(min, 'asd'); + test.each([[min - 1], [max + 1]])('❌에러 테스트(%s) throw Error %s', (test) => { + expect(() => LottoNumberFactory.getLottoNumber(test)).toThrow('[ERROR]'); + }); // 실패테스트 + + test.each([[min], [max]])('⭕성공 테스트(%s)', (number) => { + const lottoNumber = LottoNumberFactory.getLottoNumber(number); + expect(lottoNumber.getNumber(number)).toBe(number); + }); // 성공테스트 + }); // 메서드 테스트 +}); // LottoNumberFactory 테스트 diff --git a/src/domain/LottoNumberFactory.js b/src/domain/LottoNumberFactory.js new file mode 100644 index 000000000..35524bdf4 --- /dev/null +++ b/src/domain/LottoNumberFactory.js @@ -0,0 +1,27 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LottoNumber from './LottoNumber.js'; + +class LottoNumberFactory { + #numberMap = new Map(); + + constructor() { + this.#initNumberMap(); + } + + #initNumberMap() { + const [min, max] = Object.values(LottoNumber.getRange()); + for (let number = min; number <= max; number += 1) { + this.#numberMap.set(number, new LottoNumber(number)); + } + } + + getLottoNumber(number) { + if (!this.#numberMap.has(number)) { + throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + } + return this.#numberMap.get(number); + } +} +// 모듈 싱글톤 적용 +const LottoNumberFactoryInstance = new LottoNumberFactory(); +export default LottoNumberFactoryInstance; From adfd1a94c8e84193a3495e5da71f3eb7787e7ada Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 10:16:34 +0900 Subject: [PATCH 044/109] =?UTF-8?q?chore(lottoSetting):=20LOTTO=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=83=81=EC=88=98=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MAX_QUANTITY: 로또의 수량 - PURCHASE_UNIT: 구매 금액 단위 --- src/constants/lottoSetting.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/constants/lottoSetting.js diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js new file mode 100644 index 000000000..4017ae94c --- /dev/null +++ b/src/constants/lottoSetting.js @@ -0,0 +1,5 @@ +const LOTTO_SETTING = Object.freeze({ + MAX_QUANTITY: 6, + PURCHASE_UNIT: 1000, +}); +export default LOTTO_SETTING; From 69e2b2ac2ced38286855caee3d3db52ab15ea3fd Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 13:35:31 +0900 Subject: [PATCH 045/109] =?UTF-8?q?feat(Lotto):=20Lotto=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lottoNumber의 인스턴스 배열을 프로퍼티로 가짐 - Number은 Map 객체이기 때문에 n.getNumber()등으로 접근 - validator: 수량과 중복 검사를함 --- __tests__/LottoTest.js | 48 ---------------------------------- __tests__/domain/Lotto.test.js | 35 +++++++++++++++++++++++++ src/domain/Lotto.js | 39 +++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 48 deletions(-) delete mode 100644 __tests__/LottoTest.js create mode 100644 __tests__/domain/Lotto.test.js create mode 100644 src/domain/Lotto.js diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js deleted file mode 100644 index 75f1a3117..000000000 --- a/__tests__/LottoTest.js +++ /dev/null @@ -1,48 +0,0 @@ -import Lotto from '../src/domain/Lotto.js'; - -describe('로또 클래스 테스트', () => { - describe('유효성 검사', () => { - test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow('[ERROR]'); - }); - - test('로또 번호에 숫자가 없으면 예외가 발생한다.', () => { - expect(() => { - new Lotto(['1', 2, 3, 4, 5, 6]); - }).toThrow('[ERROR]'); - }); - - test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5]); - }).toThrow('[ERROR]'); - expect(() => { - new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow('[ERROR]'); - }); - - test('로또 번호가 1-45를 벗어나면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow('[ERROR]'); - }); - - test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow('[ERROR]'); - }); - }); - - describe('메서드 테스트', () => { - test('getNumbers 하면 정렬된 배열이 나와야함', () => { - const originalNumbers = [2, 1, 3, 4, 5, 6]; - const newLotto = new Lotto(originalNumbers); - - const resultNumbers = [1, 2, 3, 4, 5, 6]; - expect(newLotto.getNumbers()).toEqual(resultNumbers); - }); - }); -}); diff --git a/__tests__/domain/Lotto.test.js b/__tests__/domain/Lotto.test.js new file mode 100644 index 000000000..bbcd46383 --- /dev/null +++ b/__tests__/domain/Lotto.test.js @@ -0,0 +1,35 @@ +import Lotto from '../../src/domain/Lotto.js'; +import LottoNumber from '../../src/domain/LottoNumber.js'; +import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; + +const createlottoNumbers = (numbers) => { + return numbers.map((numbers) => new LottoNumber(numbers)); +}; + +describe('로또 클래스 테스트', () => { + describe('생성 유효성 테스트', () => { + test.each([ + [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], + [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], + ])('❌수량 에러 테스트(%s) throw Error %s', (numbers, errorMessage) => { + expect(() => new Lotto(createlottoNumbers(numbers))).toThrow(errorMessage); + }); // 실패테스트 수량 + + test.each([[[1, 1, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_DUPLICATE]])( + '❌중복 에러 테스트(%s) throw Error %s', + (numbers, errorMessage) => { + expect(() => new Lotto(createlottoNumbers(numbers))).toThrow(errorMessage); + }, + ); // 실패테스트 중복됐는가 + }); + + describe('메서드 테스트', () => { + test('⭕메서드 테스트() throw Error ', () => { + const numbers = [6, 2, 3, 4, 5, 1]; + const lotto = new Lotto(createlottoNumbers(numbers)); + + const resultArray = [1, 2, 3, 4, 5, 6]; + expect(lotto.getNumbers()).toEqual(resultArray); + }); // 성공 테스트 + }); +}); diff --git a/src/domain/Lotto.js b/src/domain/Lotto.js new file mode 100644 index 000000000..f56b7f870 --- /dev/null +++ b/src/domain/Lotto.js @@ -0,0 +1,39 @@ +import ERROR_MESSAGES from '../constants/errorMessages'; +import LOTTO_SETTING from '../constants/lottoSetting'; + +class Lotto { + #numbers; + + constructor(numbers) { + this.#validate(numbers); + this.#numbers = this.#sort(numbers); + } + + #validate(numbers) { + if (!this.#isQuantity(numbers)) { + throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); + } + if (!this.#isDuplicate(numbers)) { + throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); + } + } + + #sort(numbers) { + return [...numbers].sort((a, b) => a.getNumber() - b.getNumber()); + } + + #isQuantity(numbers) { + return numbers.length === LOTTO_SETTING.MAX_QUANTITY; + } + + #isDuplicate(numbers) { + const newLotto = numbers.map((number) => number.getNumber()); + const setLotto = new Set(newLotto); + return setLotto.size === newLotto.length; + } + + getNumbers() { + return [...this.#numbers].map((number) => number.getNumber()); + } +} +export default Lotto; From b037bd111664384a1b2e0e07174bf90a264de296 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 13:53:49 +0900 Subject: [PATCH 046/109] =?UTF-8?q?feat(LottoFactory):=20LottoFactory=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 6개의 LottoNumber을 가지는 배열을 반환 - 테스트하기위해 랜덤성있는 함수 의존성 주입으로 분리 --- __tests__/domain/LottoFactory.test.js | 15 +++++++++++++++ src/domain/LottoFacotry.js | 26 ++++++++++++++++++++++++++ src/wrapper/pickRandomNumber.js | 5 +++++ 3 files changed, 46 insertions(+) create mode 100644 __tests__/domain/LottoFactory.test.js create mode 100644 src/domain/LottoFacotry.js create mode 100644 src/wrapper/pickRandomNumber.js diff --git a/__tests__/domain/LottoFactory.test.js b/__tests__/domain/LottoFactory.test.js new file mode 100644 index 000000000..200a03b3d --- /dev/null +++ b/__tests__/domain/LottoFactory.test.js @@ -0,0 +1,15 @@ +import LottoFactory from '../../src/domain/LottoFacotry.js'; + +function pickTestNumber(a, b, c) { + return [2, 1, 3, 4, 5, 6]; +} +describe('LottoFactory 테스트', () => { + describe('생성 테스트', () => { + test('같은 인스턴스여야함', () => { + const factory = new LottoFactory(pickTestNumber); + const lotto = factory.createLotto(); + const result = [1, 2, 3, 4, 5, 6]; + expect(lotto.getNumbers()).toEqual(result); + }); + }); // 생성 테스트 +}); // LottoNumberFactory 테스트 diff --git a/src/domain/LottoFacotry.js b/src/domain/LottoFacotry.js new file mode 100644 index 000000000..1f1fde349 --- /dev/null +++ b/src/domain/LottoFacotry.js @@ -0,0 +1,26 @@ +import LOTTO_SETTING from '../constants/lottoSetting.js'; +import Lotto from './Lotto.js'; +import LottoNumber from './LottoNumber.js'; +import LottoNumberInstance from './LottoNumberFactory.js'; + +class LottoFactory { + #picker; + + constructor(picker) { + this.#picker = picker; + } + + createLotto() { + const newArray = this.#getNumbers(); + const numbers = newArray.map((number) => LottoNumberInstance.getLottoNumber(number)); + return new Lotto(numbers); + } + + #getNumbers() { + const [min, max] = Object.values(LottoNumber.getRange()); + const quan = LOTTO_SETTING.MAX_QUANTITY; + return this.#picker(min, max, quan); + } +} + +export default LottoFactory; diff --git a/src/wrapper/pickRandomNumber.js b/src/wrapper/pickRandomNumber.js new file mode 100644 index 000000000..cb85f4d7d --- /dev/null +++ b/src/wrapper/pickRandomNumber.js @@ -0,0 +1,5 @@ +import { Random } from '@woowacourse/mission-utils'; + +export default function pickRandomNumber(min, max, quan) { + return Random.pickUniqueNumbersInRange(min, max, quan); +} From 6c4f9d666a4a3a7cee2991230426a02a0117262c Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 17:40:06 +0900 Subject: [PATCH 047/109] =?UTF-8?q?feat(LottoStore):=20LottoStore=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구매 금액을 저장 - 구매금액과 카운트를 사면 로또를 카운트만큼 반환 - 구매 단위(1,000)를 다른 곳에서 관리하기 때문 - test: 의존성 주입으로 랜덤값을 고정시켜 통합테스트를 진행 --- __tests__/domain/LottoStore.test.js | 24 ++++++++++++++++++++++++ src/domain/LottoStore.js | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 __tests__/domain/LottoStore.test.js create mode 100644 src/domain/LottoStore.js diff --git a/__tests__/domain/LottoStore.test.js b/__tests__/domain/LottoStore.test.js new file mode 100644 index 000000000..762fb8926 --- /dev/null +++ b/__tests__/domain/LottoStore.test.js @@ -0,0 +1,24 @@ +import LottoStore from '../../src/domain/LottoStore.js'; +import LottoFactory from '../../src/domain/LottoFacotry.js'; +import Lotto from '../../src/domain/Lotto.js'; +function pickTestNumber(a, b, c) { + return [1, 2, 3, 4, 5, 6]; +} +const lottofactory = new LottoFactory(pickTestNumber); +const store = new LottoStore(lottofactory); +describe('LottoStore 테스트', () => { + describe('buyLotto(purchaseAmount, count) ⭕성공 테스트', () => { + test('제대로 반환하는지 테스트', () => { + const { purchaseAmount, count } = { purchaseAmount: 8000, count: 8 }; + const lottos = store.buyLotto(purchaseAmount, count); + // 길이 체크 + expect(lottos).toHaveLength(count); + // 인스턴스체크 + expect(lottos[0]).toBeInstanceOf(Lotto); + // 반환값 체크 + lottos.forEach((lotto) => { + expect(lotto.getNumbers()).toEqual(pickTestNumber()); + }); + }); + }); // 생성 테스트 +}); // LottoNumberFactory 테스트 diff --git a/src/domain/LottoStore.js b/src/domain/LottoStore.js new file mode 100644 index 000000000..e7a6b1293 --- /dev/null +++ b/src/domain/LottoStore.js @@ -0,0 +1,19 @@ +class LottoStore { + #purchaseAmount = 0; + #lottofactory; + + constructor(lottofactory) { + this.#lottofactory = lottofactory; + } + + buyLotto(purchaseAmount, count) { + this.#purchaseAmount = purchaseAmount; + + const lottos = []; + for (let i = 0; i < count; i += 1) { + lottos.push(this.#lottofactory.createLotto()); + } + return lottos; + } +} +export default LottoStore; From d64ec1860050253a01774a2e3f848e164c1ebfaa Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 17:51:41 +0900 Subject: [PATCH 048/109] =?UTF-8?q?feat(LottoPrice):=20LottoPrice=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 원시값 단위를 포장한 객체 - exchange: 로또 개수를 반환한다 - modUnit: 구매금액의 나머지연산값을 반환한다 --- __tests__/domain/LottoPrice.test.js | 17 +++++++++++++++++ src/constants/lottoSetting.js | 1 - src/domain/LottoPrice.js | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 __tests__/domain/LottoPrice.test.js create mode 100644 src/domain/LottoPrice.js diff --git a/__tests__/domain/LottoPrice.test.js b/__tests__/domain/LottoPrice.test.js new file mode 100644 index 000000000..27358106c --- /dev/null +++ b/__tests__/domain/LottoPrice.test.js @@ -0,0 +1,17 @@ +import LottoPrice from '../../src/domain/LottoPrice'; + +describe('LottoPrice 테스트', () => { + describe('메서드 테스트', () => { + const PURCHASE_UNIT = 1000; + const purchaseAmount = 8000; + test('exchange(purchaseAmount) 단위로 구매금액을 나눈 count를 반환', () => { + const result = purchaseAmount / PURCHASE_UNIT; + expect(LottoPrice.exchange(purchaseAmount)).toBe(result); + }); //test + + test('modUnit(purchaseAmount) 단위로 구매금액의 나머지연산값을 반환', () => { + const result = purchaseAmount % PURCHASE_UNIT; + expect(LottoPrice.modUnit(purchaseAmount)).toBe(result); + }); //test + }); // 생성 테스트 +}); // LottoNumberFactory 테스트 diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 4017ae94c..4988362bd 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -1,5 +1,4 @@ const LOTTO_SETTING = Object.freeze({ MAX_QUANTITY: 6, - PURCHASE_UNIT: 1000, }); export default LOTTO_SETTING; diff --git a/src/domain/LottoPrice.js b/src/domain/LottoPrice.js new file mode 100644 index 000000000..c5da12564 --- /dev/null +++ b/src/domain/LottoPrice.js @@ -0,0 +1,12 @@ +class LottoPrice { + static #PURCHASE_UNIT = 1000; + + static exchange(purchaseAmount) { + return purchaseAmount / LottoPrice.#PURCHASE_UNIT; + } + + static modUnit(purchaseAmount) { + return purchaseAmount % LottoPrice.#PURCHASE_UNIT; + } +} +export default LottoPrice; From 018498a6021ec2e74424fc5926972e03e4b1e2bc Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 18:10:39 +0900 Subject: [PATCH 049/109] =?UTF-8?q?feat(PurchaseValidator):=20PurchaseVali?= =?UTF-8?q?dator=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 구매 금액에 대한 유효성 검사 - isConvertNumber: 숫자로 변환 가능한지 검증한다 - isInteger: 정수인지 검증한다 - isModUnit: 구매 금액 단위로 모듈러 연산을 했을때 0인지 확인한다 --- __tests__/validator/PurchaseValidator.test.js | 26 +++++++++++++ src/validator/PurchaseValidator.js | 39 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 __tests__/validator/PurchaseValidator.test.js create mode 100644 src/validator/PurchaseValidator.js diff --git a/__tests__/validator/PurchaseValidator.test.js b/__tests__/validator/PurchaseValidator.test.js new file mode 100644 index 000000000..898efa22c --- /dev/null +++ b/__tests__/validator/PurchaseValidator.test.js @@ -0,0 +1,26 @@ +import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; +import PurchaseValidator from '../../src/validator/PurchaseValidator.js'; + +describe('PurchaseValidator 클래스 테스트', () => { + describe('구매금액 유효성 검사 ⭕실패 테스트', () => { + const purchase = new PurchaseValidator(); + test.each([ + // 숫자변환검사 + ['1,000', ERROR_MESSAGES.FORMAT_NOT_NUM], + // 정수검사 + ['10.5', ERROR_MESSAGES.INTEGER], + // 나머지 테스트 + [-1500, ERROR_MESSAGES.PURCHASE_UNIT], + [900, ERROR_MESSAGES.PURCHASE_UNIT], + [1500, ERROR_MESSAGES.PURCHASE_UNIT], + ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { + expect(() => purchase.validate(amount)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('구매금액 유효성 검사 ⭕성공테스트', () => { + const purchase = new PurchaseValidator(); + test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { + expect(() => purchase.validate(amount)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 diff --git a/src/validator/PurchaseValidator.js b/src/validator/PurchaseValidator.js new file mode 100644 index 000000000..b6cf745b0 --- /dev/null +++ b/src/validator/PurchaseValidator.js @@ -0,0 +1,39 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LottoPrice from '../domain/LottoPrice.js'; + +class PurchaseValidator { + validate(purchaseAmount) { + if (!this.#isConvertNumber(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); + } + if (!this.#isInteger(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + if (!this.#isModUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); + } + } + + #isConvertNumber(purchaseAmount) { + if (purchaseAmount === null || typeof purchaseAmount === 'undefined') { + return false; + } + if (String(purchaseAmount).trim() === '') { + return false; + } + if (Number.isNaN(Number(purchaseAmount))) { + return false; + } + return true; + } + + #isInteger(purchaseAmount) { + return Number.isInteger(Number(purchaseAmount)); + } + + #isModUnit(purchaseAmount) { + return LottoPrice.modUnit(purchaseAmount) === 0; + } +} + +export default PurchaseValidator; From 973689531a895d982907f495e96785f959e12bb5 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 18:18:23 +0900 Subject: [PATCH 050/109] =?UTF-8?q?refactor(PurchaseValidator):=20static?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 의존성 주입을 할 필요 없다고 판단 --- __tests__/validator/PurchaseValidator.test.js | 6 ++---- src/validator/PurchaseValidator.js | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/__tests__/validator/PurchaseValidator.test.js b/__tests__/validator/PurchaseValidator.test.js index 898efa22c..fb53f3b01 100644 --- a/__tests__/validator/PurchaseValidator.test.js +++ b/__tests__/validator/PurchaseValidator.test.js @@ -3,7 +3,6 @@ import PurchaseValidator from '../../src/validator/PurchaseValidator.js'; describe('PurchaseValidator 클래스 테스트', () => { describe('구매금액 유효성 검사 ⭕실패 테스트', () => { - const purchase = new PurchaseValidator(); test.each([ // 숫자변환검사 ['1,000', ERROR_MESSAGES.FORMAT_NOT_NUM], @@ -14,13 +13,12 @@ describe('PurchaseValidator 클래스 테스트', () => { [900, ERROR_MESSAGES.PURCHASE_UNIT], [1500, ERROR_MESSAGES.PURCHASE_UNIT], ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { - expect(() => purchase.validate(amount)).toThrow(errorMessage); + expect(() => PurchaseValidator.validate(amount)).toThrow(errorMessage); }); // 실패테스트 }); //describe describe('구매금액 유효성 검사 ⭕성공테스트', () => { - const purchase = new PurchaseValidator(); test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { - expect(() => purchase.validate(amount)).not.toThrow(); + expect(() => PurchaseValidator.validate(amount)).not.toThrow(); }); // 성공테스트 }); //describe }); //describe 클래스 테스트 diff --git a/src/validator/PurchaseValidator.js b/src/validator/PurchaseValidator.js index b6cf745b0..6f2dbc88a 100644 --- a/src/validator/PurchaseValidator.js +++ b/src/validator/PurchaseValidator.js @@ -2,19 +2,19 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; import LottoPrice from '../domain/LottoPrice.js'; class PurchaseValidator { - validate(purchaseAmount) { - if (!this.#isConvertNumber(purchaseAmount)) { + static validate(purchaseAmount) { + if (!PurchaseValidator.#isConvertNumber(purchaseAmount)) { throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); } - if (!this.#isInteger(purchaseAmount)) { + if (!PurchaseValidator.#isInteger(purchaseAmount)) { throw new Error(ERROR_MESSAGES.INTEGER); } - if (!this.#isModUnit(purchaseAmount)) { + if (!PurchaseValidator.#isModUnit(purchaseAmount)) { throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); } } - #isConvertNumber(purchaseAmount) { + static #isConvertNumber(purchaseAmount) { if (purchaseAmount === null || typeof purchaseAmount === 'undefined') { return false; } @@ -27,11 +27,11 @@ class PurchaseValidator { return true; } - #isInteger(purchaseAmount) { + static #isInteger(purchaseAmount) { return Number.isInteger(Number(purchaseAmount)); } - #isModUnit(purchaseAmount) { + static #isModUnit(purchaseAmount) { return LottoPrice.modUnit(purchaseAmount) === 0; } } From 09cfdf0308ad5bfa268e55dc8009461bee8d0ab8 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 18:21:29 +0900 Subject: [PATCH 051/109] =?UTF-8?q?refactor(errorMessages):=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LOTTO_QUANTITY: 특정 수량이 아닌 포괄적인 정보로 변경 - FORMAT_NOT_NUM: 구매금액에 대한 정보에서 포괄적인 정보로 변경 --- src/constants/errorMessages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 6222ba738..9559d3bb3 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,15 +1,15 @@ const PREFIX = '[ERROR]'; const ERROR_MESSAGES = Object.freeze({ - FORMAT_NOT_NUM: `${PREFIX}구매 금액 형식이 잘못되었습니다`, POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, + FORMAT_NOT_NUM: `${PREFIX} 숫자를 입력해야 합니다`, PURCHASE_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, LOTTO_NOT_NUMBER: `${PREFIX}로또 번호는 숫자만 추가 할 수 있습니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, - LOTTO_QUANTITY: `${PREFIX}로또 번호는 6개여야합니다`, + LOTTO_QUANTITY: `${PREFIX}로또 번호는 정의된 만큼 생성되어야 합니다 `, LOTTO_RANGE: `${PREFIX}로또 번호는 1-45여야 합니다`, }); From 4921c28db8b6a498f841e3c3f8655687ac256097 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 18:23:11 +0900 Subject: [PATCH 052/109] =?UTF-8?q?fix(Lotto):=20import=20Path=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 확장자 .js 추가 --- src/domain/Lotto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/Lotto.js b/src/domain/Lotto.js index f56b7f870..a59ec42d8 100644 --- a/src/domain/Lotto.js +++ b/src/domain/Lotto.js @@ -1,5 +1,5 @@ -import ERROR_MESSAGES from '../constants/errorMessages'; -import LOTTO_SETTING from '../constants/lottoSetting'; +import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; class Lotto { #numbers; From 3447ad5ef7e603b25539e19ba1b44b1ccb1459d6 Mon Sep 17 00:00:00 2001 From: iftype Date: Fri, 31 Oct 2025 18:44:16 +0900 Subject: [PATCH 053/109] =?UTF-8?q?feat(Lottos):=20Lottos=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lotto 객체를 저장하는 배열 - getLottosData: 배열에 담아 값을 출력 --- __tests__/domain/Lottos.test.js | 21 +++++++++++++++++++++ src/domain/Lottos.js | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 __tests__/domain/Lottos.test.js create mode 100644 src/domain/Lottos.js diff --git a/__tests__/domain/Lottos.test.js b/__tests__/domain/Lottos.test.js new file mode 100644 index 000000000..6353c336f --- /dev/null +++ b/__tests__/domain/Lottos.test.js @@ -0,0 +1,21 @@ +import Lottos from '../../src/domain/Lottos.js'; +import LottoFactory from '../../src/domain/LottoFacotry.js'; + +function pickTestNumber(a, b, c) { + return [2, 1, 3, 4, 5, 6]; +} +const factory = new LottoFactory(pickTestNumber); +const lottoArray = []; +lottoArray.push(factory.createLotto()); +lottoArray.push(factory.createLotto()); +const lottos = new Lottos(lottoArray); +describe('Lottos 테스트', () => { + const result = [ + [1, 2, 3, 4, 5, 6], + [1, 2, 3, 4, 5, 6], + ]; + test('getLottosData는 valude 배열을 출력', () => { + console.log(result); + expect(lottos.getLottosData()).toEqual(result); + }); //test +}); // Lottos 테스트 diff --git a/src/domain/Lottos.js b/src/domain/Lottos.js new file mode 100644 index 000000000..da2e2d859 --- /dev/null +++ b/src/domain/Lottos.js @@ -0,0 +1,17 @@ +class Lottos { + #lottos = []; + + constructor(lottos) { + this.#lottos = lottos; + } + + getLottosData() { + const data = []; + this.#lottos.forEach((lotto) => { + data.push(lotto.getNumbers()); + }); + return data; + } +} + +export default Lottos; From 0785d71bd15d1c3b6b1fcacb91c3c5b065897eb1 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 09:52:59 +0900 Subject: [PATCH 054/109] =?UTF-8?q?feat(LottoState):=20LottoState=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 다회 입력을 위해 생성 - 서비스와 컨트롤러 사이에서 상태저장을 해줌 --- src/domain/LottoState.js | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/domain/LottoState.js diff --git a/src/domain/LottoState.js b/src/domain/LottoState.js new file mode 100644 index 000000000..f0bfeb6a7 --- /dev/null +++ b/src/domain/LottoState.js @@ -0,0 +1,46 @@ +class LottoState { + #purchaseAmount; + #lottos; + #winningNumbers; + #bonusNumber; + #winningRate; + + constructor() { + this.#purchaseAmount = 0; + this.#lottos = []; + this.#winningNumbers = []; + this.#bonusNumber = 0; + this.#winningRate = []; + } + + setPurchaseAmount(input) { + this.#purchaseAmount = input; + } + + setLottos(input) { + this.#lottos = input; + } + + setWinningNumbers(input) { + this.#winningNumbers = input; + } + + setBonusNumber(input) { + this.#bonusNumber = input; + } + + setWinningRate(input) { + this.#winningRate = input; + } + + getState() { + return { + purchaseAmount: this.#purchaseAmount, + lottos: this.#lottos, + winningNumbers: this.#winningNumbers, + bonusNumber: this.#bonusNumber, + winningRate: this.#winningRate, + }; + } +} +export default LottoState; From 1fdf8b9fe18459877421eecc4ada56a477ba4484 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 10:21:51 +0900 Subject: [PATCH 055/109] =?UTF-8?q?refactor(LottoStore):=20LottoState?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구매 금액 프로퍼티 삭제 --- src/domain/LottoStore.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/domain/LottoStore.js b/src/domain/LottoStore.js index e7a6b1293..27a31b424 100644 --- a/src/domain/LottoStore.js +++ b/src/domain/LottoStore.js @@ -1,14 +1,11 @@ class LottoStore { - #purchaseAmount = 0; #lottofactory; constructor(lottofactory) { this.#lottofactory = lottofactory; } - buyLotto(purchaseAmount, count) { - this.#purchaseAmount = purchaseAmount; - + buyLotto(count) { const lottos = []; for (let i = 0; i < count; i += 1) { lottos.push(this.#lottofactory.createLotto()); From 92b1db4fdc2aa28c3fba3510fe39bec7640f6077 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 12:36:54 +0900 Subject: [PATCH 056/109] =?UTF-8?q?refactor(LottoFacotry):=20LottoFactory?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LottoNumberFactory를 의존성 주입함에 따라 관련 테스트들을 통합 테스트에서 유닛테스트로 변경 --- __tests__/domain/Lotto.test.js | 17 ++++++++++++++++- __tests__/domain/LottoFactory.test.js | 6 +++++- __tests__/domain/LottoStore.test.js | 25 +++++++++++++------------ __tests__/domain/Lottos.test.js | 2 +- src/domain/LottoFacotry.js | 11 ++++++----- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/__tests__/domain/Lotto.test.js b/__tests__/domain/Lotto.test.js index bbcd46383..f7061f67a 100644 --- a/__tests__/domain/Lotto.test.js +++ b/__tests__/domain/Lotto.test.js @@ -24,12 +24,27 @@ describe('로또 클래스 테스트', () => { }); describe('메서드 테스트', () => { - test('⭕메서드 테스트() throw Error ', () => { + test('⭕메서드 테스트 getNumbers() ', () => { const numbers = [6, 2, 3, 4, 5, 1]; const lotto = new Lotto(createlottoNumbers(numbers)); const resultArray = [1, 2, 3, 4, 5, 6]; expect(lotto.getNumbers()).toEqual(resultArray); }); // 성공 테스트 + + test('⭕메서드 테스트 hasNumber(number)', () => { + const numbers = [6, 2, 3, 4, 5, 1]; + const lotto = new Lotto(createlottoNumbers(numbers)); + + expect(lotto.hasNumber(6)).toBe(true); + }); // 성공 테스트 + + test('⭕메서드 테스트 countNumbers(numbers)', () => { + const numbers = [6, 2, 3, 4, 5, 1]; + const lotto = new Lotto(createlottoNumbers(numbers)); + + const resultArray = [1, 2, 3, 4, 5, 6]; + expect(lotto.countNumbers(resultArray)).toBe(6); + }); // 성공 테스트 }); }); diff --git a/__tests__/domain/LottoFactory.test.js b/__tests__/domain/LottoFactory.test.js index 200a03b3d..661a97f87 100644 --- a/__tests__/domain/LottoFactory.test.js +++ b/__tests__/domain/LottoFactory.test.js @@ -3,10 +3,14 @@ import LottoFactory from '../../src/domain/LottoFacotry.js'; function pickTestNumber(a, b, c) { return [2, 1, 3, 4, 5, 6]; } +const mockLottoNumberFactory = { + getLottoNumber: jest.fn((number) => ({ getNumber: () => number })), +}; + describe('LottoFactory 테스트', () => { describe('생성 테스트', () => { test('같은 인스턴스여야함', () => { - const factory = new LottoFactory(pickTestNumber); + const factory = new LottoFactory(pickTestNumber, mockLottoNumberFactory); const lotto = factory.createLotto(); const result = [1, 2, 3, 4, 5, 6]; expect(lotto.getNumbers()).toEqual(result); diff --git a/__tests__/domain/LottoStore.test.js b/__tests__/domain/LottoStore.test.js index 762fb8926..46654df33 100644 --- a/__tests__/domain/LottoStore.test.js +++ b/__tests__/domain/LottoStore.test.js @@ -1,23 +1,24 @@ import LottoStore from '../../src/domain/LottoStore.js'; -import LottoFactory from '../../src/domain/LottoFacotry.js'; import Lotto from '../../src/domain/Lotto.js'; -function pickTestNumber(a, b, c) { - return [1, 2, 3, 4, 5, 6]; -} -const lottofactory = new LottoFactory(pickTestNumber); -const store = new LottoStore(lottofactory); + +const MOCK_NUMBERS = [1, 2, 3, 4, 5, 6]; +const mockLottoArray = MOCK_NUMBERS.map((num) => ({ getNumber: () => num })); +const mockLotto = new Lotto(mockLottoArray); +const mockLottoFactory = { createLotto: () => mockLotto }; + +const store = new LottoStore(mockLottoFactory); describe('LottoStore 테스트', () => { describe('buyLotto(purchaseAmount, count) ⭕성공 테스트', () => { + // 길이 체크 + // 인스턴스체크 + // 반환값 체크 test('제대로 반환하는지 테스트', () => { - const { purchaseAmount, count } = { purchaseAmount: 8000, count: 8 }; - const lottos = store.buyLotto(purchaseAmount, count); - // 길이 체크 + const count = 8; + const lottos = store.buyLotto(count); expect(lottos).toHaveLength(count); - // 인스턴스체크 expect(lottos[0]).toBeInstanceOf(Lotto); - // 반환값 체크 lottos.forEach((lotto) => { - expect(lotto.getNumbers()).toEqual(pickTestNumber()); + expect(lotto.getNumbers()).toEqual(MOCK_NUMBERS); }); }); }); // 생성 테스트 diff --git a/__tests__/domain/Lottos.test.js b/__tests__/domain/Lottos.test.js index 6353c336f..840e4a1b8 100644 --- a/__tests__/domain/Lottos.test.js +++ b/__tests__/domain/Lottos.test.js @@ -1,7 +1,7 @@ import Lottos from '../../src/domain/Lottos.js'; import LottoFactory from '../../src/domain/LottoFacotry.js'; -function pickTestNumber(a, b, c) { +function pickTestNumber() { return [2, 1, 3, 4, 5, 6]; } const factory = new LottoFactory(pickTestNumber); diff --git a/src/domain/LottoFacotry.js b/src/domain/LottoFacotry.js index 1f1fde349..c73330dfe 100644 --- a/src/domain/LottoFacotry.js +++ b/src/domain/LottoFacotry.js @@ -1,22 +1,23 @@ import LOTTO_SETTING from '../constants/lottoSetting.js'; import Lotto from './Lotto.js'; import LottoNumber from './LottoNumber.js'; -import LottoNumberInstance from './LottoNumberFactory.js'; class LottoFactory { #picker; + #lottoNumberFactory; - constructor(picker) { + constructor(picker, lottoNumberFactory) { this.#picker = picker; + this.#lottoNumberFactory = lottoNumberFactory; } createLotto() { - const newArray = this.#getNumbers(); - const numbers = newArray.map((number) => LottoNumberInstance.getLottoNumber(number)); + const newArray = this.#generateNumbers(); + const numbers = newArray.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); return new Lotto(numbers); } - #getNumbers() { + #generateNumbers() { const [min, max] = Object.values(LottoNumber.getRange()); const quan = LOTTO_SETTING.MAX_QUANTITY; return this.#picker(min, max, quan); From 7e79bc4af69dbca749c2f32c2ea9db1c5d73a5a4 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 13:46:06 +0900 Subject: [PATCH 057/109] =?UTF-8?q?refactor(pickRandomNumber):=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pickRandomNumber: randomPicker --- src/{wrapper/pickRandomNumber.js => utils/picker.js} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename src/{wrapper/pickRandomNumber.js => utils/picker.js} (50%) diff --git a/src/wrapper/pickRandomNumber.js b/src/utils/picker.js similarity index 50% rename from src/wrapper/pickRandomNumber.js rename to src/utils/picker.js index cb85f4d7d..6a2c94d9c 100644 --- a/src/wrapper/pickRandomNumber.js +++ b/src/utils/picker.js @@ -1,5 +1,9 @@ import { Random } from '@woowacourse/mission-utils'; -export default function pickRandomNumber(min, max, quan) { +export function randomPicker(min, max, quan) { return Random.pickUniqueNumbersInRange(min, max, quan); } + +export function testingPicker() { + return [1, 2, 3, 4, 5, 6]; +} From eb7fa7fa9e855c497b20f2fbe4e494d686142fe0 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 15:00:25 +0900 Subject: [PATCH 058/109] refactor(Lotto): Change Array to Set type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lotto배열을 Set 타입으로 변경 - 참조 시 성능 향상 --- __tests__/domain/Lotto.test.js | 33 ++++++++++++--------------------- src/domain/Lotto.js | 23 +++++++++++++---------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/__tests__/domain/Lotto.test.js b/__tests__/domain/Lotto.test.js index f7061f67a..258d4179e 100644 --- a/__tests__/domain/Lotto.test.js +++ b/__tests__/domain/Lotto.test.js @@ -1,10 +1,10 @@ import Lotto from '../../src/domain/Lotto.js'; -import LottoNumber from '../../src/domain/LottoNumber.js'; import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; -const createlottoNumbers = (numbers) => { - return numbers.map((numbers) => new LottoNumber(numbers)); -}; +const MOCK_NUMBERS = [2, 1, 3, 4, 5, 6]; +const mockLottoNumber = (num) => ({ getNumber: () => num }); +const mockLottoArray = MOCK_NUMBERS.map((num) => mockLottoNumber(num)); +const mockLotto = new Lotto(mockLottoArray); describe('로또 클래스 테스트', () => { describe('생성 유효성 테스트', () => { @@ -12,39 +12,30 @@ describe('로또 클래스 테스트', () => { [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], ])('❌수량 에러 테스트(%s) throw Error %s', (numbers, errorMessage) => { - expect(() => new Lotto(createlottoNumbers(numbers))).toThrow(errorMessage); + expect(() => new Lotto(numbers)).toThrow(errorMessage); }); // 실패테스트 수량 test.each([[[1, 1, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_DUPLICATE]])( '❌중복 에러 테스트(%s) throw Error %s', (numbers, errorMessage) => { - expect(() => new Lotto(createlottoNumbers(numbers))).toThrow(errorMessage); + expect(() => new Lotto(numbers)).toThrow(errorMessage); }, ); // 실패테스트 중복됐는가 }); describe('메서드 테스트', () => { test('⭕메서드 테스트 getNumbers() ', () => { - const numbers = [6, 2, 3, 4, 5, 1]; - const lotto = new Lotto(createlottoNumbers(numbers)); - - const resultArray = [1, 2, 3, 4, 5, 6]; - expect(lotto.getNumbers()).toEqual(resultArray); + const result = [1, 2, 3, 4, 5, 6]; + expect(mockLotto.getNumbers()).toEqual(result); }); // 성공 테스트 - test('⭕메서드 테스트 hasNumber(number)', () => { - const numbers = [6, 2, 3, 4, 5, 1]; - const lotto = new Lotto(createlottoNumbers(numbers)); - - expect(lotto.hasNumber(6)).toBe(true); + test('⭕메서드 테스트 hasLottoNumber(lottoNumber)', () => { + const test = mockLottoArray.find((obj) => obj.getNumber() === 3); + expect(mockLotto.hasLottoNumber(test)).toBe(true); }); // 성공 테스트 test('⭕메서드 테스트 countNumbers(numbers)', () => { - const numbers = [6, 2, 3, 4, 5, 1]; - const lotto = new Lotto(createlottoNumbers(numbers)); - - const resultArray = [1, 2, 3, 4, 5, 6]; - expect(lotto.countNumbers(resultArray)).toBe(6); + expect(mockLotto.countNumbers(mockLottoArray)).toBe(6); }); // 성공 테스트 }); }); diff --git a/src/domain/Lotto.js b/src/domain/Lotto.js index a59ec42d8..858708956 100644 --- a/src/domain/Lotto.js +++ b/src/domain/Lotto.js @@ -2,11 +2,11 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; import LOTTO_SETTING from '../constants/lottoSetting.js'; class Lotto { - #numbers; + #lottoNumbers; constructor(numbers) { + this.#lottoNumbers = new Set(numbers); this.#validate(numbers); - this.#numbers = this.#sort(numbers); } #validate(numbers) { @@ -18,22 +18,25 @@ class Lotto { } } - #sort(numbers) { - return [...numbers].sort((a, b) => a.getNumber() - b.getNumber()); - } - #isQuantity(numbers) { return numbers.length === LOTTO_SETTING.MAX_QUANTITY; } #isDuplicate(numbers) { - const newLotto = numbers.map((number) => number.getNumber()); - const setLotto = new Set(newLotto); - return setLotto.size === newLotto.length; + return this.#lottoNumbers.size === numbers.length; + } + + hasLottoNumber(lottoNumber) { + return this.#lottoNumbers.has(lottoNumber); + } + + countNumbers(numbers) { + return numbers.filter((number) => this.#lottoNumbers.has(number)).length; } getNumbers() { - return [...this.#numbers].map((number) => number.getNumber()); + const numbers = Array.from(this.#lottoNumbers).map((lottoNumber) => lottoNumber.getNumber()); + return numbers.sort((a, b) => a - b); } } export default Lotto; From cc19c2a84579fef2f326e86996b261102599092c Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 17:47:44 +0900 Subject: [PATCH 059/109] =?UTF-8?q?refactor(LottoFactorry):=20Strategy=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로그램 실행단계에서 의존성을 주입 - 구매단계에선 random, 당첨단계에선 fixed를 사용하기에 각각 따로 주입 - FixedPicker: 고정값을 반환하는 Strategy - RandomPicker: 랜덤값을 반환하는 Strategy --- __tests__/domain/LottoFactory.test.js | 12 +++++------- src/domain/LottoFacotry.js | 24 +++++++++--------------- src/domain/strategy/FixedPicker.js | 6 ++++++ src/domain/strategy/RandomPicker.js | 12 ++++++++++++ src/utils/picker.js | 9 --------- 5 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 src/domain/strategy/FixedPicker.js create mode 100644 src/domain/strategy/RandomPicker.js delete mode 100644 src/utils/picker.js diff --git a/__tests__/domain/LottoFactory.test.js b/__tests__/domain/LottoFactory.test.js index 661a97f87..08ebb0da4 100644 --- a/__tests__/domain/LottoFactory.test.js +++ b/__tests__/domain/LottoFactory.test.js @@ -1,18 +1,16 @@ import LottoFactory from '../../src/domain/LottoFacotry.js'; +import FixedPicker from '../../src/domain/strategy/FixedPicker.js'; -function pickTestNumber(a, b, c) { - return [2, 1, 3, 4, 5, 6]; -} const mockLottoNumberFactory = { getLottoNumber: jest.fn((number) => ({ getNumber: () => number })), }; - +const fixed = new FixedPicker(); describe('LottoFactory 테스트', () => { describe('생성 테스트', () => { - test('같은 인스턴스여야함', () => { - const factory = new LottoFactory(pickTestNumber, mockLottoNumberFactory); - const lotto = factory.createLotto(); + test('', () => { + const factory = new LottoFactory(fixed, mockLottoNumberFactory); const result = [1, 2, 3, 4, 5, 6]; + const lotto = factory.createLotto(result); expect(lotto.getNumbers()).toEqual(result); }); }); // 생성 테스트 diff --git a/src/domain/LottoFacotry.js b/src/domain/LottoFacotry.js index c73330dfe..92891e0b6 100644 --- a/src/domain/LottoFacotry.js +++ b/src/domain/LottoFacotry.js @@ -1,26 +1,20 @@ -import LOTTO_SETTING from '../constants/lottoSetting.js'; import Lotto from './Lotto.js'; -import LottoNumber from './LottoNumber.js'; class LottoFactory { - #picker; + #strategy; #lottoNumberFactory; - constructor(picker, lottoNumberFactory) { - this.#picker = picker; + constructor(strategy, lottoNumberFactory) { + this.#strategy = strategy; this.#lottoNumberFactory = lottoNumberFactory; } - createLotto() { - const newArray = this.#generateNumbers(); - const numbers = newArray.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); - return new Lotto(numbers); - } - - #generateNumbers() { - const [min, max] = Object.values(LottoNumber.getRange()); - const quan = LOTTO_SETTING.MAX_QUANTITY; - return this.#picker(min, max, quan); + createLotto(numbers) { + const newArray = this.#strategy.pick(numbers); + const creatdeNumbers = newArray.map((number) => + this.#lottoNumberFactory.getLottoNumber(number), + ); + return new Lotto(creatdeNumbers); } } diff --git a/src/domain/strategy/FixedPicker.js b/src/domain/strategy/FixedPicker.js new file mode 100644 index 000000000..f5929778e --- /dev/null +++ b/src/domain/strategy/FixedPicker.js @@ -0,0 +1,6 @@ +class FixedPicker { + pick(numbers) { + return numbers; + } +} +export default FixedPicker; diff --git a/src/domain/strategy/RandomPicker.js b/src/domain/strategy/RandomPicker.js new file mode 100644 index 000000000..463663136 --- /dev/null +++ b/src/domain/strategy/RandomPicker.js @@ -0,0 +1,12 @@ +import { Random } from '@woowacourse/mission-utils'; +import LottoNumber from '../LottoNumber.js'; +import LOTTO_SETTING from '../../constants/lottoSetting.js'; + +class RandomPicker { + pick() { + const [min, max] = Object.values(LottoNumber.getRange()); + const quan = LOTTO_SETTING.MAX_QUANTITY; + return Random.pickUniqueNumbersInRange(min, max, quan); + } +} +export default RandomPicker; diff --git a/src/utils/picker.js b/src/utils/picker.js deleted file mode 100644 index 6a2c94d9c..000000000 --- a/src/utils/picker.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Random } from '@woowacourse/mission-utils'; - -export function randomPicker(min, max, quan) { - return Random.pickUniqueNumbersInRange(min, max, quan); -} - -export function testingPicker() { - return [1, 2, 3, 4, 5, 6]; -} From c7575368fbe5fa263ff05bcb459823780af5b19b Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:20:44 +0900 Subject: [PATCH 060/109] =?UTF-8?q?feat(LottoRequestDto):=20LottoRequestDt?= =?UTF-8?q?o=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컨트롤러에서 서비스로 데이터를 전송하기 위해 추가 - 유효성 검사와 형변환을 진행 --- src/dto/LottoRequestDto.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/dto/LottoRequestDto.js diff --git a/src/dto/LottoRequestDto.js b/src/dto/LottoRequestDto.js new file mode 100644 index 000000000..25749a3d8 --- /dev/null +++ b/src/dto/LottoRequestDto.js @@ -0,0 +1,14 @@ +import PurchaseValidator from '../validator/PurchaseValidator.js'; + +class LottoRequestDto { + #purchaseAmount; + constructor({ purchaseAmount }) { + PurchaseValidator.validate(purchaseAmount); + this.#purchaseAmount = Number(purchaseAmount); + } + + get purchaseAmount() { + return this.#purchaseAmount; + } +} +export default LottoRequestDto; From a4bf0c1ba4cdabd89a6946cb5b3a37dd46717843 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:22:16 +0900 Subject: [PATCH 061/109] =?UTF-8?q?feat(LottoResponseDto):=20LottoResponse?= =?UTF-8?q?Dto=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서비스에서 컨트롤러로 데이터 이동을 위해 구현 - 컨트롤러는 Lottos의 값을 배열상태로 확인가능하다 --- src/dto/LottoResponseDto.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/dto/LottoResponseDto.js diff --git a/src/dto/LottoResponseDto.js b/src/dto/LottoResponseDto.js new file mode 100644 index 000000000..c9c480a82 --- /dev/null +++ b/src/dto/LottoResponseDto.js @@ -0,0 +1,15 @@ +class LottoResponseDto { + #lottos; + + constructor({ lottos }) { + this.#lottos = lottos; + } + + toJSON() { + const lottosToArray = [...this.#lottos].map((lotto) => lotto.getNumbers()); + return { + lottos: lottosToArray, + }; + } +} +export default LottoResponseDto; From f8e28d2eeee636043ef310140bfd08a5009c42fd Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:28:58 +0900 Subject: [PATCH 062/109] =?UTF-8?q?feat(LottoService):=20LottoService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력까지의 구현 - purchaseLotto: 구매금액을 입력받고 로또 객체를 반환한다 --- src/services/LottoService.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/services/LottoService.js diff --git a/src/services/LottoService.js b/src/services/LottoService.js new file mode 100644 index 000000000..d5bdfbacc --- /dev/null +++ b/src/services/LottoService.js @@ -0,0 +1,29 @@ +import LottoPrice from '../domain/LottoPrice.js'; +import LottoResponseDto from '../dto/LottoResponseDto.js'; + +class LottoService { + constructor(lottoStore, lottoWinning, lottoRepository) { + this.lottoStore = lottoStore; + this.lottoWinning = lottoWinning; + this.lottoRepository = lottoRepository; + } + + purchaseLotto(requestDTO) { + const { purchaseAmount } = requestDTO; + const lottoCount = LottoPrice.exchange(purchaseAmount); + const lottos = this.lottoStore.buyLotto(lottoCount); + + this.lottoRepository.save('admin', lottos); + return new LottoResponseDto({ lottos }); + } + + getWinningRate(winningNumbers, bonusNumber) { + // validate + this.lottoWinning(winningNumbers, bonusNumber); + const lottos = this.lottoRepository.findAll('admin'); + const winningRate = this.lottoWinning.getMatchWinningRate(lottos); + return winningRate; + } +} + +export default LottoService; From eab64f9f672c95f7b02c03480dcbd47e813f4086 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:30:08 +0900 Subject: [PATCH 063/109] =?UTF-8?q?feat(LottoController):=20LottoControlle?= =?UTF-8?q?r=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자에게 입력을 받고 로또 구매를 실행 후 반환 값을 뷰 레이어에 전달 --- src/controller/LottoController.js | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/controller/LottoController.js diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js new file mode 100644 index 000000000..def5d6c00 --- /dev/null +++ b/src/controller/LottoController.js @@ -0,0 +1,40 @@ +import LottoRequestDto from '../dto/LottoRequestDto.js'; + +class LottoController { + #lottoService; + #lottoView; + + constructor(lottoService, lottoView) { + this.#lottoService = lottoService; + this.#lottoView = lottoView; + } + + async purchase() { + try { + const purchaseAmount = await this.#lottoView.readPurchaseAmount(); + const requestDTO = new LottoRequestDto({ purchaseAmount }); + const responseDto = this.#lottoService.purchaseLotto(requestDTO); + const resultLottos = responseDto.toJSON(); + this.#lottoView.printPurchaseLottos(resultLottos); + return this.getWinningRate(); + } catch (err) { + // 에러 출력 메세지 컴포넌트만들기 ㅡ ㅡ ㅡ ㅡㅡ ㅡㅡㅡ ㅡㅡ ㅡ ㅡ ㅡㅡㅡ ㅡㅡ + console.log(err); + return this.purchase(); + } + } + + // 메서드명 고민해보기 + async getWinningRate() { + // try { + // const winningNumbers = await this.#lottoView.readWinningNumbers(); + // const bonusNumber = await this.#lottoView.readBonusNumber(); + // const result = this.#lottoService.getWinningRate(winningNumbers, bonusNumber); + // console.log(result); + // } catch (err) { + // console.log(err); + // } + } +} + +export default LottoController; From cdac09ee408f0efb37e98c38b3f8a9382d0535cd Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:32:58 +0900 Subject: [PATCH 064/109] =?UTF-8?q?feat(LottoView):=20LottoView=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - readPurchaseAmount: 구매금액을 입력받음 - readWinningNumbers: 당첨 숫자를 입력받음 - readBonusNumber: 보너스 숫자를 입력받음 - printPurchaseLottos: 생성된 로또 배열을 형식에 맞게 출력 --- src/view/LottoView.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/view/LottoView.js diff --git a/src/view/LottoView.js b/src/view/LottoView.js new file mode 100644 index 000000000..254d9fb34 --- /dev/null +++ b/src/view/LottoView.js @@ -0,0 +1,34 @@ +import { Console } from '@woowacourse/mission-utils'; + +const INFO_MEESAGE = { + INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', + INFO_WINNING_NUMBERS: '당첨 번호를 입력해 주세요.\n', + INFO_BONUS_NUMBER: '보너스 번호를 입력해 주세요.\n', +}; + +class LottoView { + async readPurchaseAmount() { + const purchaseAmount = await Console.readLineAsync(INFO_MEESAGE.INFO_PURCHASE_AMOUNT); + return purchaseAmount; + } + + async readWinningNumbers() { + const winningNumbers = await Console.readLineAsync(INFO_MEESAGE.INFO_WINNING_NUMBERS); + return winningNumbers; + } + + async readBonusNumber() { + const bonusNumber = await Console.readLineAsync(INFO_MEESAGE.INFO_BONUS_NUMBER); + return bonusNumber; + } + + printPurchaseLottos(lottosData) { + const { lottos } = lottosData; + Console.print(`${lottos.length}개를 구매했습니다.`); + lottos.forEach((lotto) => { + Console.print(`[${lotto.join(', ')}]`); + }); + } +} + +export default LottoView; From 7b78fada01869bbe3e301c4ef27123c257adb050 Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:33:58 +0900 Subject: [PATCH 065/109] =?UTF-8?q?feat(App):=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App의 생성자 부에서 인스턴스를 생성하고 의존성을 주입 --- src/App.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a5d..a11feacbc 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,41 @@ +import LottoController from './controller/LottoController.js'; +import LottoFactory from './domain/LottoFacotry.js'; +import LottoService from './services/LottoService.js'; +import LottoView from './view/LottoView.js'; +import LottoStore from './domain/LottoStore.js'; +import LottoNumberFactory from './domain/LottoNumberFactory.js'; +import LottoRepository from './repository/LottoRepository.js'; +import RandomPicker from './domain/strategy/RandomPicker.js'; +import FixedPicker from './domain/strategy/FixedPicker.js'; +import LottoWinning from './domain/LottoWinning.js'; + class App { - async run() {} + #lottoController; + + constructor() { + const lottoNumberFactory = LottoNumberFactory; + + // lottoStore: 랜덤 전략 팩토리 주입 + const randomStrategy = new RandomPicker(); + const randomLottoFactory = new LottoFactory(randomStrategy, lottoNumberFactory); + const lottoStore = new LottoStore(randomLottoFactory); + + // lottoWinning: 고정값 전략 팩토리 주입 + const fixedStrategy = new FixedPicker(); + const fixedLottoFactory = new LottoFactory(fixedStrategy, lottoNumberFactory); + const lottoWinning = new LottoWinning(fixedLottoFactory); + + // lottoSerivce 주입단계 + const lottoRepository = new LottoRepository(); + const lottoService = new LottoService(lottoStore, lottoWinning, lottoRepository); + + const lottoView = new LottoView(); + this.#lottoController = new LottoController(lottoService, lottoView); + } + + async run() { + await this.#lottoController.purchase(); + } } export default App; From 28944bac3b53d755326401c23b14ca3368909ebb Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 19:35:35 +0900 Subject: [PATCH 066/109] =?UTF-8?q?feat(LottoRepository):=20LottoRepositor?= =?UTF-8?q?y=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DB역할을 하는 클래스 구현 - 그에 따른 LottoState 삭제 --- src/domain/LottoState.js | 46 ------------------------------- src/repository/LottoRepository.js | 19 +++++++++++++ 2 files changed, 19 insertions(+), 46 deletions(-) delete mode 100644 src/domain/LottoState.js create mode 100644 src/repository/LottoRepository.js diff --git a/src/domain/LottoState.js b/src/domain/LottoState.js deleted file mode 100644 index f0bfeb6a7..000000000 --- a/src/domain/LottoState.js +++ /dev/null @@ -1,46 +0,0 @@ -class LottoState { - #purchaseAmount; - #lottos; - #winningNumbers; - #bonusNumber; - #winningRate; - - constructor() { - this.#purchaseAmount = 0; - this.#lottos = []; - this.#winningNumbers = []; - this.#bonusNumber = 0; - this.#winningRate = []; - } - - setPurchaseAmount(input) { - this.#purchaseAmount = input; - } - - setLottos(input) { - this.#lottos = input; - } - - setWinningNumbers(input) { - this.#winningNumbers = input; - } - - setBonusNumber(input) { - this.#bonusNumber = input; - } - - setWinningRate(input) { - this.#winningRate = input; - } - - getState() { - return { - purchaseAmount: this.#purchaseAmount, - lottos: this.#lottos, - winningNumbers: this.#winningNumbers, - bonusNumber: this.#bonusNumber, - winningRate: this.#winningRate, - }; - } -} -export default LottoState; diff --git a/src/repository/LottoRepository.js b/src/repository/LottoRepository.js new file mode 100644 index 000000000..e27d83798 --- /dev/null +++ b/src/repository/LottoRepository.js @@ -0,0 +1,19 @@ +class LottoRepository { + #lottoDB; + + constructor() { + this.#lottoDB = new Map(); + } + + save(id, Lottos) { + this.#lottoDB.set(id, Lottos); + } + + findAll(id) { + if (!this.#lottoDB.has(id)) { + throw new Error('저장된 데이터가 없습니다'); + } + return this.#lottoDB.get(id); + } +} +export default LottoRepository; From c0a4f57f53759585f4cdfa00a0537d3dccdfb9ad Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 20:35:36 +0900 Subject: [PATCH 067/109] =?UTF-8?q?refactor(Dto):=20Dto=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LottoResponseDto -> LottosResponseDto - LottoRequestDto ->PurchaseAmountDto --- .../{LottoRequestDto.js => requestDto/PurchaseAmountDto.js} | 6 +++--- src/dto/{ => responseDto}/LottoResponseDto.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/dto/{LottoRequestDto.js => requestDto/PurchaseAmountDto.js} (63%) rename src/dto/{ => responseDto}/LottoResponseDto.js (78%) diff --git a/src/dto/LottoRequestDto.js b/src/dto/requestDto/PurchaseAmountDto.js similarity index 63% rename from src/dto/LottoRequestDto.js rename to src/dto/requestDto/PurchaseAmountDto.js index 25749a3d8..6807e1533 100644 --- a/src/dto/LottoRequestDto.js +++ b/src/dto/requestDto/PurchaseAmountDto.js @@ -1,6 +1,6 @@ -import PurchaseValidator from '../validator/PurchaseValidator.js'; +import PurchaseValidator from '../../validator/PurchaseValidator.js'; -class LottoRequestDto { +class PurchaseAmountDto { #purchaseAmount; constructor({ purchaseAmount }) { PurchaseValidator.validate(purchaseAmount); @@ -11,4 +11,4 @@ class LottoRequestDto { return this.#purchaseAmount; } } -export default LottoRequestDto; +export default PurchaseAmountDto; diff --git a/src/dto/LottoResponseDto.js b/src/dto/responseDto/LottoResponseDto.js similarity index 78% rename from src/dto/LottoResponseDto.js rename to src/dto/responseDto/LottoResponseDto.js index c9c480a82..552b7ab9a 100644 --- a/src/dto/LottoResponseDto.js +++ b/src/dto/responseDto/LottoResponseDto.js @@ -1,4 +1,4 @@ -class LottoResponseDto { +class LottosResponseDto { #lottos; constructor({ lottos }) { @@ -12,4 +12,4 @@ class LottoResponseDto { }; } } -export default LottoResponseDto; +export default LottosResponseDto; From aaa9ff99717a5bea0d2d630130eba283cf101c4c Mon Sep 17 00:00:00 2001 From: iftype Date: Sat, 1 Nov 2025 20:36:13 +0900 Subject: [PATCH 068/109] =?UTF-8?q?feat(Parser):=20Parser=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stringToNumberArray: 문자열을 쉼표 기준으로 나누고 숫자배열로 바꿈 --- src/utils/Parser.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/utils/Parser.js diff --git a/src/utils/Parser.js b/src/utils/Parser.js new file mode 100644 index 000000000..afa4305d4 --- /dev/null +++ b/src/utils/Parser.js @@ -0,0 +1,14 @@ +class Parser { + static stringToNumberArray(str) { + if (typeof str !== 'string') return null; + + const numberArray = str + .split(',') + .map((e) => e.trim()) + .map(Number); + + if (numberArray.some(Number.isNaN)) return null; + return numberArray; + } +} +export default Parser; From ce74a991d0a6c6d5b29e063b84f4f8915f55930b Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:09:11 +0900 Subject: [PATCH 069/109] =?UTF-8?q?feat:=20InputBonusNumberValidator=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DTO 에서 유효성 검사를 수행 - isConvertNumber: 숫자로 변환 가능한지 - isInteger: 정수인지 --- .../InputBonusNumberValidator.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/validator/inputValidator/InputBonusNumberValidator.js diff --git a/src/validator/inputValidator/InputBonusNumberValidator.js b/src/validator/inputValidator/InputBonusNumberValidator.js new file mode 100644 index 000000000..414836bb9 --- /dev/null +++ b/src/validator/inputValidator/InputBonusNumberValidator.js @@ -0,0 +1,31 @@ +import ERROR_MESSAGES from '../../constants/errorMessages.js'; + +class InputBonusNumberValidator { + static validate(bonusNumber) { + if (!InputBonusNumberValidator.#isConvertNumber(bonusNumber)) { + throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); + } + if (!InputBonusNumberValidator.#isInteger(bonusNumber)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + } + + static #isConvertNumber(bonusNumber) { + if (bonusNumber === null || typeof bonusNumber === 'undefined') { + return false; + } + if (String(bonusNumber).trim() === '') { + return false; + } + if (Number.isNaN(Number(bonusNumber))) { + return false; + } + return true; + } + + static #isInteger(bonusNumber) { + return Number.isInteger(Number(bonusNumber)); + } +} + +export default InputBonusNumberValidator; From 1c4b3785ed615039a45b52c152f5dbfb2b5fc0c4 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:10:08 +0900 Subject: [PATCH 070/109] =?UTF-8?q?feat:=20InputWinningNumberValidator=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DTO 에서 유효성 검사를 수행 - isArrray: 타입이 배열인지 - isInteger: 원소들이 정수인지 --- .../InputWinningNumberValidator.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/validator/inputValidator/InputWinningNumberValidator.js diff --git a/src/validator/inputValidator/InputWinningNumberValidator.js b/src/validator/inputValidator/InputWinningNumberValidator.js new file mode 100644 index 000000000..454b21fa2 --- /dev/null +++ b/src/validator/inputValidator/InputWinningNumberValidator.js @@ -0,0 +1,22 @@ +import ERROR_MESSAGES from '../../constants/errorMessages.js'; + +class InputWinningNumberValidator { + static validate(winningNumbers) { + if (!InputWinningNumberValidator.#isArrray(winningNumbers)) { + throw new Error(ERROR_MESSAGES.NOT_ARRAY); + } + if (!InputWinningNumberValidator.#isInteger(winningNumbers)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + } + + static #isArrray(winningNumbers) { + return Array.isArray(winningNumbers); + } + + static #isInteger(winningNumbers) { + return winningNumbers.every((num) => Number.isInteger(num)); + } +} + +export default InputWinningNumberValidator; From 193ca0baa542878b6c4b2ee996a90d2f847b6a23 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:12:48 +0900 Subject: [PATCH 071/109] =?UTF-8?q?refactor(PurchaseValidator):=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PurchaseValidator를 서비스와 DTO에서 각각 검증하도록 분리 - InputPurchaseAmountValidator: 숫자인지? 정수인지? - PurchaseValidator: 단위 값으로 모듈러 연산했을때 나머지가 0인지 --- src/validator/PurchaseValidator.js | 23 -------------- .../InputPurchaseAmountValidator.js | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 src/validator/inputValidator/InputPurchaseAmountValidator.js diff --git a/src/validator/PurchaseValidator.js b/src/validator/PurchaseValidator.js index 6f2dbc88a..6e6a0d40f 100644 --- a/src/validator/PurchaseValidator.js +++ b/src/validator/PurchaseValidator.js @@ -3,34 +3,11 @@ import LottoPrice from '../domain/LottoPrice.js'; class PurchaseValidator { static validate(purchaseAmount) { - if (!PurchaseValidator.#isConvertNumber(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); - } - if (!PurchaseValidator.#isInteger(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.INTEGER); - } if (!PurchaseValidator.#isModUnit(purchaseAmount)) { throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); } } - static #isConvertNumber(purchaseAmount) { - if (purchaseAmount === null || typeof purchaseAmount === 'undefined') { - return false; - } - if (String(purchaseAmount).trim() === '') { - return false; - } - if (Number.isNaN(Number(purchaseAmount))) { - return false; - } - return true; - } - - static #isInteger(purchaseAmount) { - return Number.isInteger(Number(purchaseAmount)); - } - static #isModUnit(purchaseAmount) { return LottoPrice.modUnit(purchaseAmount) === 0; } diff --git a/src/validator/inputValidator/InputPurchaseAmountValidator.js b/src/validator/inputValidator/InputPurchaseAmountValidator.js new file mode 100644 index 000000000..d8330eb2e --- /dev/null +++ b/src/validator/inputValidator/InputPurchaseAmountValidator.js @@ -0,0 +1,31 @@ +import ERROR_MESSAGES from '../../constants/errorMessages.js'; + +class InputPurchaseAmountValidator { + static validate(purchaseAmount) { + if (!InputPurchaseAmountValidator.#isConvertNumber(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.FORMAT_NOT_NUM); + } + if (!InputPurchaseAmountValidator.#isInteger(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + } + + static #isConvertNumber(purchaseAmount) { + if (purchaseAmount === null || typeof purchaseAmount === 'undefined') { + return false; + } + if (String(purchaseAmount).trim() === '') { + return false; + } + if (Number.isNaN(Number(purchaseAmount))) { + return false; + } + return true; + } + + static #isInteger(purchaseAmount) { + return Number.isInteger(Number(purchaseAmount)); + } +} + +export default InputPurchaseAmountValidator; From 6bceda94b788e4262fb7e06cb5237094d072ad3c Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:14:51 +0900 Subject: [PATCH 072/109] =?UTF-8?q?feat(Dto):=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=98=95=EC=8B=9D=EC=9D=98=20DTO=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 변환과 검증을 수행 - 변환과 검증을 하고 입력 검증을함 --- src/dto/requestDto/BonusNumberDto.js | 16 ++++++++++++++++ src/dto/requestDto/PurchaseAmountDto.js | 10 ++++++---- src/dto/requestDto/WinningNumbersDto.js | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/dto/requestDto/BonusNumberDto.js create mode 100644 src/dto/requestDto/WinningNumbersDto.js diff --git a/src/dto/requestDto/BonusNumberDto.js b/src/dto/requestDto/BonusNumberDto.js new file mode 100644 index 000000000..19302dc66 --- /dev/null +++ b/src/dto/requestDto/BonusNumberDto.js @@ -0,0 +1,16 @@ +import InputBonusNumberValidator from '../../validator/inputValidator/InputBonusNumberValidator.js'; + +class BonusNumberDto { + #bonusNumber; + + constructor(bonusNumber) { + const convertBonusNumber = Number(bonusNumber); + InputBonusNumberValidator.validate(convertBonusNumber); + this.#bonusNumber = convertBonusNumber; + } + + get bonusNumber() { + return this.#bonusNumber; + } +} +export default BonusNumberDto; diff --git a/src/dto/requestDto/PurchaseAmountDto.js b/src/dto/requestDto/PurchaseAmountDto.js index 6807e1533..0d35b14fc 100644 --- a/src/dto/requestDto/PurchaseAmountDto.js +++ b/src/dto/requestDto/PurchaseAmountDto.js @@ -1,10 +1,12 @@ -import PurchaseValidator from '../../validator/PurchaseValidator.js'; +import InputPurchaseAmountValidator from '../../validator/inputValidator/InputPurchaseAmountValidator.js'; class PurchaseAmountDto { #purchaseAmount; - constructor({ purchaseAmount }) { - PurchaseValidator.validate(purchaseAmount); - this.#purchaseAmount = Number(purchaseAmount); + + constructor(purchaseAmount) { + const convertedPurchaseAmount = Number(purchaseAmount); + InputPurchaseAmountValidator.validate(convertedPurchaseAmount); + this.#purchaseAmount = convertedPurchaseAmount; } get purchaseAmount() { diff --git a/src/dto/requestDto/WinningNumbersDto.js b/src/dto/requestDto/WinningNumbersDto.js new file mode 100644 index 000000000..aa5fee42b --- /dev/null +++ b/src/dto/requestDto/WinningNumbersDto.js @@ -0,0 +1,17 @@ +import Parser from '../../utils/Parser.js'; +import InputWinningNumberValidator from '../../validator/inputValidator/InputWinningNumberValidator.js'; + +class WinningNumbersDto { + #winningNumbers; + + constructor(winningNumbers) { + const parseWinningNumbers = Parser.stringToNumberArray(winningNumbers); + InputWinningNumberValidator.validate(parseWinningNumbers); + this.#winningNumbers = parseWinningNumbers; + } + + get winningNumbers() { + return this.#winningNumbers; + } +} +export default WinningNumbersDto; From 44f8abee0ee23a706106aec3e5ee71cc73b90166 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:17:03 +0900 Subject: [PATCH 073/109] =?UTF-8?q?fix(LottoNumber):=20=EC=98=A4=ED=83=88?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MAX->MAX_RANGE --- src/domain/LottoNumber.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/LottoNumber.js b/src/domain/LottoNumber.js index e3d2a620b..33a92714b 100644 --- a/src/domain/LottoNumber.js +++ b/src/domain/LottoNumber.js @@ -28,7 +28,7 @@ class LottoNumber { } static getRange() { - return { MIN_RANGE: this.#MIN_RANGE, MAX: this.#MAX_RANGE }; + return { MIN_RANGE: this.#MIN_RANGE, MAX_RANGE: this.#MAX_RANGE }; } getNumber() { From 2d384c90fd57c1d23314e8dbdda89f89837bdf6d Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:20:27 +0900 Subject: [PATCH 074/109] =?UTF-8?q?refactor(PurchaseValidator):=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validator/{ => domain}/PurchaseValidator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/validator/{ => domain}/PurchaseValidator.js (73%) diff --git a/src/validator/PurchaseValidator.js b/src/validator/domain/PurchaseValidator.js similarity index 73% rename from src/validator/PurchaseValidator.js rename to src/validator/domain/PurchaseValidator.js index 6e6a0d40f..0a2748303 100644 --- a/src/validator/PurchaseValidator.js +++ b/src/validator/domain/PurchaseValidator.js @@ -1,5 +1,5 @@ -import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LottoPrice from '../domain/LottoPrice.js'; +import ERROR_MESSAGES from '../../constants/errorMessages.js'; +import LottoPrice from '../../domain/LottoPrice.js'; class PurchaseValidator { static validate(purchaseAmount) { From fe231d7a42ab42c015e21abda4579bda7d22903e Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:37:28 +0900 Subject: [PATCH 075/109] =?UTF-8?q?feat(WinningNumbersValidator):=20?= =?UTF-8?q?=EB=8B=B9=EC=B2=A8=20=EB=B2=88=ED=98=B8=EC=9D=98=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수량 중복 범위를 검사 --- .../domain/WinningNumbersValidator.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/validator/domain/WinningNumbersValidator.js diff --git a/src/validator/domain/WinningNumbersValidator.js b/src/validator/domain/WinningNumbersValidator.js new file mode 100644 index 000000000..ce3eb745a --- /dev/null +++ b/src/validator/domain/WinningNumbersValidator.js @@ -0,0 +1,33 @@ +import ERROR_MESSAGES from '../../constants/errorMessages.js'; +import LOTTO_SETTING from '../../constants/lottoSetting.js'; +import LottoNumber from '../../domain/LottoNumber.js'; + +class WinningNumbersValidator { + static validate(winningNumbers) { + if (!WinningNumbersValidator.#isQuantity(winningNumbers)) { + throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); + } + if (!WinningNumbersValidator.#isDuplicate(winningNumbers)) { + throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); + } + if (!WinningNumbersValidator.#isInRange(winningNumbers)) { + throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + } + } + + static #isInRange(winningNumbers) { + const [MIN_RANGE, MAX_RANGE] = Object.values(LottoNumber.getRange()); + return winningNumbers.every((number) => MIN_RANGE <= number && number <= MAX_RANGE); + } + + static #isDuplicate(winningNumbers) { + const setNumbers = new Set(winningNumbers); + return setNumbers.size === winningNumbers.length; + } + + static #isQuantity(winningNumbers) { + return winningNumbers.length === LOTTO_SETTING.MAX_QUANTITY; + } +} + +export default WinningNumbersValidator; From cfa68077bfd32208f1d09e7676af09e2c23cd45f Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:40:06 +0900 Subject: [PATCH 076/109] =?UTF-8?q?feat(BonusNumberValidator):=20=EB=B3=B4?= =?UTF-8?q?=EB=84=88=EC=8A=A4=20=EB=B2=88=ED=98=B8=EC=9D=98=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 당첨번호와의 중복을 검사 - 당첨번호의 범위를 검사 --- src/validator/domain/BonusNumberValidator.js | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/validator/domain/BonusNumberValidator.js diff --git a/src/validator/domain/BonusNumberValidator.js b/src/validator/domain/BonusNumberValidator.js new file mode 100644 index 000000000..f5a59a8cf --- /dev/null +++ b/src/validator/domain/BonusNumberValidator.js @@ -0,0 +1,24 @@ +import ERROR_MESSAGES from '../../constants/errorMessages.js'; +import LottoNumber from '../../domain/LottoNumber.js'; + +class BonusNumberValidator { + static validate(winningNumbers, bonusNumber) { + if (!BonusNumberValidator.#isInRange(bonusNumber)) { + throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + } + if (!BonusNumberValidator.#isDuplicate(winningNumbers, bonusNumber)) { + throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); + } + } + + static #isInRange(bonusNumber) { + const [MIN_RANGE, MAX_RANGE] = Object.values(LottoNumber.getRange()); + return MIN_RANGE <= bonusNumber && bonusNumber <= MAX_RANGE; + } + + static #isDuplicate(winningNumbers, bonusNumber) { + return winningNumbers.includes(bonusNumber); + } +} + +export default BonusNumberValidator; From dcf40dce506e1fcca614387a2b7976b285ebe031 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:41:40 +0900 Subject: [PATCH 077/109] =?UTF-8?q?feat(LottoRepository):=20update=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 주어진 키 값에 해당하는 값이 없으면 데이터를 추가 --- src/repository/LottoRepository.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/repository/LottoRepository.js b/src/repository/LottoRepository.js index e27d83798..bae8288de 100644 --- a/src/repository/LottoRepository.js +++ b/src/repository/LottoRepository.js @@ -5,8 +5,17 @@ class LottoRepository { this.#lottoDB = new Map(); } - save(id, Lottos) { - this.#lottoDB.set(id, Lottos); + save(id, data) { + this.#lottoDB.set(id, data); + } + + update(id, data) { + if (!this.#lottoDB.has(id)) { + throw new Error('저장된 데이터가 없습니다'); + } + const repoData = this.#lottoDB.get(id); + const insertData = { ...repoData, ...data }; + this.#lottoDB.set(id, insertData); } findAll(id) { From 1a3c81dfdeefa919825c46d7dd8c5a4ed7139025 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:45:00 +0900 Subject: [PATCH 078/109] =?UTF-8?q?feat(LottoWinningFactory):=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=EB=B2=88=ED=98=B8=20=EB=B0=8F=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EC=83=9D=EC=84=B1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지정 값을 lottoNumber에 참조시켜 반환하는 클래스 - 입력 받은 당첨번호와 보너스 번호를 LottoNumber 객체로 반환 --- src/domain/LottoWinningFactory.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/domain/LottoWinningFactory.js diff --git a/src/domain/LottoWinningFactory.js b/src/domain/LottoWinningFactory.js new file mode 100644 index 000000000..7cf18bac2 --- /dev/null +++ b/src/domain/LottoWinningFactory.js @@ -0,0 +1,18 @@ +class LottoWinningFactory { + #lottoNumberFactory; + + constructor(lottoNumberFactory) { + this.#lottoNumberFactory = lottoNumberFactory; + } + + createWinningLotto(numbers) { + const newArray = numbers; + return newArray.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); + } + + createBonusLotto(number) { + return this.#lottoNumberFactory.getLottoNumber(number); + } +} + +export default LottoWinningFactory; From 75075a4add29c9acaac1fab4279ab5f909b3ee41 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 09:56:39 +0900 Subject: [PATCH 079/109] =?UTF-8?q?fix(BonusNumberValidator):=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 보너스번호의 값을 받아와서 검증하는 것으로 수정 - 확실한 구분을 위해 변수이름 수정 numbers->LottoNubmers --- src/validator/domain/BonusNumberValidator.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/validator/domain/BonusNumberValidator.js b/src/validator/domain/BonusNumberValidator.js index f5a59a8cf..4ce36efc8 100644 --- a/src/validator/domain/BonusNumberValidator.js +++ b/src/validator/domain/BonusNumberValidator.js @@ -2,22 +2,25 @@ import ERROR_MESSAGES from '../../constants/errorMessages.js'; import LottoNumber from '../../domain/LottoNumber.js'; class BonusNumberValidator { - static validate(winningNumbers, bonusNumber) { - if (!BonusNumberValidator.#isInRange(bonusNumber)) { + static validate(winningLottoNumbers, bonusLottoNumber) { + if (!BonusNumberValidator.#isInRange(bonusLottoNumber)) { throw new Error(ERROR_MESSAGES.LOTTO_RANGE); } - if (!BonusNumberValidator.#isDuplicate(winningNumbers, bonusNumber)) { + if (!BonusNumberValidator.#isDuplicate(winningLottoNumbers, bonusLottoNumber)) { throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); } } - static #isInRange(bonusNumber) { + static #isInRange(bonusLottoNumber) { + const bonusNumber = bonusLottoNumber.getNumber(); const [MIN_RANGE, MAX_RANGE] = Object.values(LottoNumber.getRange()); return MIN_RANGE <= bonusNumber && bonusNumber <= MAX_RANGE; } - static #isDuplicate(winningNumbers, bonusNumber) { - return winningNumbers.includes(bonusNumber); + static #isDuplicate(winningLottoNumbers, bonusLottoNumber) { + console.log(winningLottoNumbers, bonusLottoNumber); + console.log(winningLottoNumbers.includes(bonusLottoNumber)); + return !winningLottoNumbers.includes(bonusLottoNumber); } } From 4db02f2b4ecb9b6343624c619f4d04ebe5d55570 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 11:58:37 +0900 Subject: [PATCH 080/109] =?UTF-8?q?feat(LottoWinningResult):=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EA=B2=B0=EA=B3=BC=EC=99=80=20=EC=88=98=EC=9D=B5?= =?UTF-8?q?=EB=A5=A0=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상수에 상금 추가 - getWinningStats: 등수의 총합과 총상금을 반환 - getWinningRate: 수익률을 반환 --- src/constants/lottoSetting.js | 9 +++++ src/domain/LottoWinningResult.js | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/domain/LottoWinningResult.js diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 4988362bd..e71ae06bc 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -1,4 +1,13 @@ const LOTTO_SETTING = Object.freeze({ MAX_QUANTITY: 6, + PRIZES: Object.freeze({ + FIRST: 2000000000, + SECOND: 30000000, + THIRD: 1500000, + FOURTH: 50000, + FIFTH: 5000, + OTHER: 0, + }), }); + export default LOTTO_SETTING; diff --git a/src/domain/LottoWinningResult.js b/src/domain/LottoWinningResult.js new file mode 100644 index 000000000..6d70c0289 --- /dev/null +++ b/src/domain/LottoWinningResult.js @@ -0,0 +1,56 @@ +import LOTTO_SETTING from '../constants/lottoSetting.js'; + +class LottoWinningResult { + // 당첨 카운트 [일치갯수, 보너스여부] 필수 1 + static getWinningStats(lottos, winnintLottos, bonusLotto) { + const matchs = LottoWinningResult.#getWinningMatch(lottos, winnintLottos, bonusLotto); + const ranks = LottoWinningResult.#getWinningRank(matchs); + + const winningStats = LottoWinningResult.#getWinningCount(ranks); + const totalWinningAmount = LottoWinningResult.#getWinningTotalGains(ranks); + + return { winningStats, totalWinningAmount }; + } + + // 수익률 + static getWinningRate(purchaseAmount, totalWinningAmount) { + if (totalWinningAmount === 0) return 0; + return ((totalWinningAmount / purchaseAmount) * 100).toFixed(1); + } + + // 당첨 카운트 [일치갯수, 보너스여부] 필수 1 + static #getWinningMatch(lottos, winnintLottos, bonusLotto) { + return lottos.map((lotto) => ({ + winning: lotto.countNumbers(winnintLottos), + bonus: lotto.hasLottoNumber(bonusLotto), + })); + } + + // 상금 매칭 배열리턴 [ 번호일치갯수, 보너스여부]로 판별 필수 2 + static #getWinningRank(winningStats) { + return winningStats.map(({ winning, bonus }) => { + if (winning === 6) return { rank: 'FIRST' }; + if (winning === 5 && bonus) return { rank: 'SECOND' }; + if (winning === 5) return { rank: 'THIRD' }; + if (winning === 4) return { rank: 'FOURTH' }; + if (winning === 3) return { rank: 'FIFTH' }; + return { rank: 'OTHER' }; + }); + } + + // 랭크로 계산로직 카운트 반환해야할 값 + static #getWinningCount(winningRank) { + const count = { FIRST: 0, SECOND: 0, THIRD: 0, FOURTH: 0, FIFTH: 0, OTHER: 0 }; + winningRank.forEach(({ rank }) => { + count[rank] += 1; + }); + return count; + } + + // 랭크로 계산로직 수익 + static #getWinningTotalGains(winningRank) { + const { PRIZES } = LOTTO_SETTING; + return winningRank.reduce((total, { rank }) => total + PRIZES[rank], 0); + } +} +export default LottoWinningResult; From 4fa4ca888def6c09c096c0db14e1136d22fa2bc5 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 12:13:31 +0900 Subject: [PATCH 081/109] =?UTF-8?q?refactor(Lottos):=20Repository=EC=9D=98?= =?UTF-8?q?=20=EB=8F=84=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?Lottos=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lotto 배열을 보관하는 Lottos 삭제 --- __tests__/domain/Lottos.test.js | 21 --------------------- src/domain/Lottos.js | 17 ----------------- 2 files changed, 38 deletions(-) delete mode 100644 __tests__/domain/Lottos.test.js delete mode 100644 src/domain/Lottos.js diff --git a/__tests__/domain/Lottos.test.js b/__tests__/domain/Lottos.test.js deleted file mode 100644 index 840e4a1b8..000000000 --- a/__tests__/domain/Lottos.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import Lottos from '../../src/domain/Lottos.js'; -import LottoFactory from '../../src/domain/LottoFacotry.js'; - -function pickTestNumber() { - return [2, 1, 3, 4, 5, 6]; -} -const factory = new LottoFactory(pickTestNumber); -const lottoArray = []; -lottoArray.push(factory.createLotto()); -lottoArray.push(factory.createLotto()); -const lottos = new Lottos(lottoArray); -describe('Lottos 테스트', () => { - const result = [ - [1, 2, 3, 4, 5, 6], - [1, 2, 3, 4, 5, 6], - ]; - test('getLottosData는 valude 배열을 출력', () => { - console.log(result); - expect(lottos.getLottosData()).toEqual(result); - }); //test -}); // Lottos 테스트 diff --git a/src/domain/Lottos.js b/src/domain/Lottos.js deleted file mode 100644 index da2e2d859..000000000 --- a/src/domain/Lottos.js +++ /dev/null @@ -1,17 +0,0 @@ -class Lottos { - #lottos = []; - - constructor(lottos) { - this.#lottos = lottos; - } - - getLottosData() { - const data = []; - this.#lottos.forEach((lotto) => { - data.push(lotto.getNumbers()); - }); - return data; - } -} - -export default Lottos; From b28ddb9c320da3693c6eec52a496cc69cc7e20b8 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 13:26:58 +0900 Subject: [PATCH 082/109] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{LottoResponseDto.js => PurchasedLottosDto.js} | 4 ++-- .../{domain => domainValidator}/BonusNumberValidator.js | 2 -- .../{domain => domainValidator}/PurchaseValidator.js | 0 src/view/LottoView.js | 4 ++++ 4 files changed, 6 insertions(+), 4 deletions(-) rename src/dto/responseDto/{LottoResponseDto.js => PurchasedLottosDto.js} (78%) rename src/validator/{domain => domainValidator}/BonusNumberValidator.js (87%) rename src/validator/{domain => domainValidator}/PurchaseValidator.js (100%) diff --git a/src/dto/responseDto/LottoResponseDto.js b/src/dto/responseDto/PurchasedLottosDto.js similarity index 78% rename from src/dto/responseDto/LottoResponseDto.js rename to src/dto/responseDto/PurchasedLottosDto.js index 552b7ab9a..5767dd5be 100644 --- a/src/dto/responseDto/LottoResponseDto.js +++ b/src/dto/responseDto/PurchasedLottosDto.js @@ -1,4 +1,4 @@ -class LottosResponseDto { +class PurchasedLottosDto { #lottos; constructor({ lottos }) { @@ -12,4 +12,4 @@ class LottosResponseDto { }; } } -export default LottosResponseDto; +export default PurchasedLottosDto; diff --git a/src/validator/domain/BonusNumberValidator.js b/src/validator/domainValidator/BonusNumberValidator.js similarity index 87% rename from src/validator/domain/BonusNumberValidator.js rename to src/validator/domainValidator/BonusNumberValidator.js index 4ce36efc8..e84841c1a 100644 --- a/src/validator/domain/BonusNumberValidator.js +++ b/src/validator/domainValidator/BonusNumberValidator.js @@ -18,8 +18,6 @@ class BonusNumberValidator { } static #isDuplicate(winningLottoNumbers, bonusLottoNumber) { - console.log(winningLottoNumbers, bonusLottoNumber); - console.log(winningLottoNumbers.includes(bonusLottoNumber)); return !winningLottoNumbers.includes(bonusLottoNumber); } } diff --git a/src/validator/domain/PurchaseValidator.js b/src/validator/domainValidator/PurchaseValidator.js similarity index 100% rename from src/validator/domain/PurchaseValidator.js rename to src/validator/domainValidator/PurchaseValidator.js diff --git a/src/view/LottoView.js b/src/view/LottoView.js index 254d9fb34..2c5d800fd 100644 --- a/src/view/LottoView.js +++ b/src/view/LottoView.js @@ -29,6 +29,10 @@ class LottoView { Console.print(`[${lotto.join(', ')}]`); }); } + + printError(err) { + Console.print(err.message); + } } export default LottoView; From e0666b18fcb8f29850a407bd8b18fd06d4890116 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 13:29:45 +0900 Subject: [PATCH 083/109] =?UTF-8?q?feat(Purchase):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EA=B5=AC=EB=A7=A4=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App: 의존성 주입 변경 - Controller: 로직 설정 - service: 두 메서드로 구매금액 확인 및 로또구매 - Repository: 다른 요소까지 덮어씌우기 방지 --- src/App.js | 15 +++++------ src/controller/LottoController.js | 39 ++++++++++++---------------- src/repository/LottoRepository.js | 4 ++- src/services/LottoPurchaseService.js | 26 +++++++++++++++++++ src/services/LottoService.js | 29 --------------------- 5 files changed, 51 insertions(+), 62 deletions(-) create mode 100644 src/services/LottoPurchaseService.js delete mode 100644 src/services/LottoService.js diff --git a/src/App.js b/src/App.js index a11feacbc..c643708ee 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,12 @@ import LottoController from './controller/LottoController.js'; import LottoFactory from './domain/LottoFacotry.js'; -import LottoService from './services/LottoService.js'; import LottoView from './view/LottoView.js'; import LottoStore from './domain/LottoStore.js'; import LottoNumberFactory from './domain/LottoNumberFactory.js'; import LottoRepository from './repository/LottoRepository.js'; import RandomPicker from './domain/strategy/RandomPicker.js'; -import FixedPicker from './domain/strategy/FixedPicker.js'; -import LottoWinning from './domain/LottoWinning.js'; +import LottoWinningFactory from './domain/LottoWinningFactory.js'; +import LottoPurchaseService from './services/LottoPurchaseService.js'; class App { #lottoController; @@ -21,20 +20,18 @@ class App { const lottoStore = new LottoStore(randomLottoFactory); // lottoWinning: 고정값 전략 팩토리 주입 - const fixedStrategy = new FixedPicker(); - const fixedLottoFactory = new LottoFactory(fixedStrategy, lottoNumberFactory); - const lottoWinning = new LottoWinning(fixedLottoFactory); + // const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); // lottoSerivce 주입단계 const lottoRepository = new LottoRepository(); - const lottoService = new LottoService(lottoStore, lottoWinning, lottoRepository); + const lottoPurchaseService = new LottoPurchaseService(lottoStore, lottoRepository); const lottoView = new LottoView(); - this.#lottoController = new LottoController(lottoService, lottoView); + this.#lottoController = new LottoController(lottoPurchaseService, lottoView); } async run() { - await this.#lottoController.purchase(); + await this.#lottoController.processLottoPurchase(); } } diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index def5d6c00..f683c8620 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,40 +1,33 @@ -import LottoRequestDto from '../dto/LottoRequestDto.js'; +import PurchaseAmountDto from '../dto/requestDto/PurchaseAmountDto.js'; class LottoController { - #lottoService; + #lottoPurchaseService; #lottoView; - constructor(lottoService, lottoView) { - this.#lottoService = lottoService; + constructor(lottoPurchaseService, lottoView) { + this.#lottoPurchaseService = lottoPurchaseService; this.#lottoView = lottoView; } - async purchase() { + async processLottoPurchase() { try { const purchaseAmount = await this.#lottoView.readPurchaseAmount(); - const requestDTO = new LottoRequestDto({ purchaseAmount }); - const responseDto = this.#lottoService.purchaseLotto(requestDTO); - const resultLottos = responseDto.toJSON(); - this.#lottoView.printPurchaseLottos(resultLottos); - return this.getWinningRate(); + const purchaseAmountDto = new PurchaseAmountDto(purchaseAmount); + this.#lottoPurchaseService.savePurchaseAmount(purchaseAmountDto); + + const purchasedLottos = this.#lottoPurchaseService.getPurchasedLottos(); + const purchasedLottosDto = purchasedLottos.toJSON(); + this.#lottoView.printPurchaseLottos(purchasedLottosDto); + return ''; + // return this.getWinningRate(); } catch (err) { - // 에러 출력 메세지 컴포넌트만들기 ㅡ ㅡ ㅡ ㅡㅡ ㅡㅡㅡ ㅡㅡ ㅡ ㅡ ㅡㅡㅡ ㅡㅡ - console.log(err); - return this.purchase(); + this.#lottoView.printError(err); + return this.processLottoPurchase(); } } // 메서드명 고민해보기 - async getWinningRate() { - // try { - // const winningNumbers = await this.#lottoView.readWinningNumbers(); - // const bonusNumber = await this.#lottoView.readBonusNumber(); - // const result = this.#lottoService.getWinningRate(winningNumbers, bonusNumber); - // console.log(result); - // } catch (err) { - // console.log(err); - // } - } + async getWinningRate() {} } export default LottoController; diff --git a/src/repository/LottoRepository.js b/src/repository/LottoRepository.js index bae8288de..3fa87ed74 100644 --- a/src/repository/LottoRepository.js +++ b/src/repository/LottoRepository.js @@ -6,7 +6,9 @@ class LottoRepository { } save(id, data) { - this.#lottoDB.set(id, data); + const repoData = this.#lottoDB.get(id); + const insertData = { ...repoData, ...data }; + this.#lottoDB.set(id, insertData); } update(id, data) { diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js new file mode 100644 index 000000000..d8acaf83c --- /dev/null +++ b/src/services/LottoPurchaseService.js @@ -0,0 +1,26 @@ +import LottoPrice from '../domain/LottoPrice.js'; +import PurchasedLottosDto from '../dto/responseDto/PurchasedLottosDto.js'; +import PurchaseValidator from '../validator/domainValidator/PurchaseValidator.js'; + +class LottoPurchaseService { + constructor(lottoStore, lottoRepository) { + this.lottoStore = lottoStore; + this.lottoRepository = lottoRepository; + } + + savePurchaseAmount(requestDTO) { + const { purchaseAmount } = requestDTO; + PurchaseValidator.validate(purchaseAmount); + this.lottoRepository.save('admin', { purchaseAmount }); + } + + getPurchasedLottos() { + const { purchaseAmount } = this.lottoRepository.findAll('admin'); + const lottoCount = LottoPrice.exchange(purchaseAmount); + const lottos = this.lottoStore.buyLotto(lottoCount); + this.lottoRepository.save('admin', { lottos }); + return new PurchasedLottosDto({ lottos }); + } +} + +export default LottoPurchaseService; diff --git a/src/services/LottoService.js b/src/services/LottoService.js deleted file mode 100644 index d5bdfbacc..000000000 --- a/src/services/LottoService.js +++ /dev/null @@ -1,29 +0,0 @@ -import LottoPrice from '../domain/LottoPrice.js'; -import LottoResponseDto from '../dto/LottoResponseDto.js'; - -class LottoService { - constructor(lottoStore, lottoWinning, lottoRepository) { - this.lottoStore = lottoStore; - this.lottoWinning = lottoWinning; - this.lottoRepository = lottoRepository; - } - - purchaseLotto(requestDTO) { - const { purchaseAmount } = requestDTO; - const lottoCount = LottoPrice.exchange(purchaseAmount); - const lottos = this.lottoStore.buyLotto(lottoCount); - - this.lottoRepository.save('admin', lottos); - return new LottoResponseDto({ lottos }); - } - - getWinningRate(winningNumbers, bonusNumber) { - // validate - this.lottoWinning(winningNumbers, bonusNumber); - const lottos = this.lottoRepository.findAll('admin'); - const winningRate = this.lottoWinning.getMatchWinningRate(lottos); - return winningRate; - } -} - -export default LottoService; From 7ddde27b22ef453d87d43ddadd704464b1614dc1 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 13:45:20 +0900 Subject: [PATCH 084/109] =?UTF-8?q?refactor(LottoNumber):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EB=B2=94=EC=9C=84=EB=A5=BC=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/domain/LottoNumber.test.js | 31 ++++++++----------- __tests__/domain/LottoNumberFactory.test.js | 9 +++--- src/App.js | 3 +- src/constants/lottoSetting.js | 2 ++ src/controller/LottoController.js | 1 + src/domain/LottoNumber.js | 10 ++---- src/domain/LottoNumberFactory.js | 5 +-- src/domain/strategy/FixedPicker.js | 6 ---- src/domain/strategy/RandomPicker.js | 12 ------- src/dto/responseDto/WinningResultDto.js | 17 ++++++++++ src/utils/RandomPicker.js | 10 ++++++ .../domainValidator/BonusNumberValidator.js | 4 +-- .../WinningNumbersValidator.js | 0 13 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 src/domain/strategy/FixedPicker.js delete mode 100644 src/domain/strategy/RandomPicker.js create mode 100644 src/dto/responseDto/WinningResultDto.js create mode 100644 src/utils/RandomPicker.js rename src/validator/{domain => domainValidator}/WinningNumbersValidator.js (100%) diff --git a/__tests__/domain/LottoNumber.test.js b/__tests__/domain/LottoNumber.test.js index 8d7d34d5d..8eb951508 100644 --- a/__tests__/domain/LottoNumber.test.js +++ b/__tests__/domain/LottoNumber.test.js @@ -1,37 +1,32 @@ +import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; import LottoNumber from '../../src/domain/LottoNumber.js'; +const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; describe('LottoNumber 클래스 테스트', () => { - let min; - let max; - - beforeEach(() => { - [min, max] = Object.values(LottoNumber.getRange()); - }); - describe('생성자 에러 검사', () => { - test(`최소범위 테스트 ${min - 1}`, () => { - expect(() => new LottoNumber(min - 1)).toThrow('[ERROR]'); + test(`최소범위 테스트 ${MIN_RANGE - 1}`, () => { + expect(() => new LottoNumber(MIN_RANGE - 1)).toThrow('[ERROR]'); }); //test - test(`최대범위 테스트 ${max - 1}`, () => { - expect(() => new LottoNumber(max + 1)).toThrow('[ERROR]'); + test(`최대범위 테스트 ${MAX_RANGE - 1}`, () => { + expect(() => new LottoNumber(MAX_RANGE + 1)).toThrow('[ERROR]'); }); //test }); //describe 생성자 에러 검사 describe('생성자 성공 검사', () => { - test(`최소범위 테스트 ${min}`, () => { - expect(() => new LottoNumber(min)).not.toThrow(); + test(`최소범위 테스트 ${MIN_RANGE}`, () => { + expect(() => new LottoNumber(MIN_RANGE)).not.toThrow(); }); //test - test(`최대범위 테스트 ${max}`, () => { - expect(() => new LottoNumber(max)).not.toThrow(); + test(`최대범위 테스트 ${MAX_RANGE}`, () => { + expect(() => new LottoNumber(MAX_RANGE)).not.toThrow(); }); //test }); //describe 생성자 성공 검사 describe('생성자 성공 검사', () => { - test(`최소범위 테스트 ${min}`, () => { - const lottoNum = new LottoNumber(min); - expect(lottoNum.getNumber()).toBe(min); + test(`최소범위 테스트 ${MIN_RANGE}`, () => { + const lottoNum = new LottoNumber(MIN_RANGE); + expect(lottoNum.getNumber()).toBe(MIN_RANGE); }); //test }); //describe 메서드 검사 }); //describe diff --git a/__tests__/domain/LottoNumberFactory.test.js b/__tests__/domain/LottoNumberFactory.test.js index 6dacaed8a..8168a1ae8 100644 --- a/__tests__/domain/LottoNumberFactory.test.js +++ b/__tests__/domain/LottoNumberFactory.test.js @@ -1,8 +1,9 @@ import LottoNumber from '../../src/domain/LottoNumber.js'; import LottoNumberFactory from '../../src/domain/LottoNumberFactory.js'; import LottoNumberFactoryCopy from '../../src/domain/LottoNumberFactory.js'; +import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; -const [min, max] = Object.values(LottoNumber.getRange()); +const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; describe('LottoNumberFactory 테스트', () => { beforeEach(() => {}); @@ -14,12 +15,12 @@ describe('LottoNumberFactory 테스트', () => { }); // 생성 테스트 describe('메서드 getLottoNumber 에러테스트', () => { - console.log(min, 'asd'); - test.each([[min - 1], [max + 1]])('❌에러 테스트(%s) throw Error %s', (test) => { + console.log(MIN_RANGE, 'asd'); + test.each([[MIN_RANGE - 1], [MAX_RANGE + 1]])('❌에러 테스트(%s) throw Error %s', (test) => { expect(() => LottoNumberFactory.getLottoNumber(test)).toThrow('[ERROR]'); }); // 실패테스트 - test.each([[min], [max]])('⭕성공 테스트(%s)', (number) => { + test.each([[MIN_RANGE], [MAX_RANGE]])('⭕성공 테스트(%s)', (number) => { const lottoNumber = LottoNumberFactory.getLottoNumber(number); expect(lottoNumber.getNumber(number)).toBe(number); }); // 성공테스트 diff --git a/src/App.js b/src/App.js index c643708ee..dce77bf5b 100644 --- a/src/App.js +++ b/src/App.js @@ -4,9 +4,8 @@ import LottoView from './view/LottoView.js'; import LottoStore from './domain/LottoStore.js'; import LottoNumberFactory from './domain/LottoNumberFactory.js'; import LottoRepository from './repository/LottoRepository.js'; -import RandomPicker from './domain/strategy/RandomPicker.js'; -import LottoWinningFactory from './domain/LottoWinningFactory.js'; import LottoPurchaseService from './services/LottoPurchaseService.js'; +import RandomPicker from './utils/RandomPicker.js'; class App { #lottoController; diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index e71ae06bc..f7b8f0229 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -8,6 +8,8 @@ const LOTTO_SETTING = Object.freeze({ FIFTH: 5000, OTHER: 0, }), + MIN_RANGE: 1, + MAX_RANGE: 45, }); export default LOTTO_SETTING; diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index f683c8620..a060ea9e9 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -21,6 +21,7 @@ class LottoController { return ''; // return this.getWinningRate(); } catch (err) { + console.log(err); this.#lottoView.printError(err); return this.processLottoPurchase(); } diff --git a/src/domain/LottoNumber.js b/src/domain/LottoNumber.js index 33a92714b..afcb976ef 100644 --- a/src/domain/LottoNumber.js +++ b/src/domain/LottoNumber.js @@ -1,9 +1,8 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; class LottoNumber { #number; - static #MIN_RANGE = 1; - static #MAX_RANGE = 45; constructor(number) { this.#validate(number); @@ -24,11 +23,8 @@ class LottoNumber { } #isInRange(number) { - return LottoNumber.#MIN_RANGE <= number && number <= LottoNumber.#MAX_RANGE; - } - - static getRange() { - return { MIN_RANGE: this.#MIN_RANGE, MAX_RANGE: this.#MAX_RANGE }; + const { MAX_RANGE, MIN_RANGE } = LOTTO_SETTING; + return MIN_RANGE <= number && number <= MAX_RANGE; } getNumber() { diff --git a/src/domain/LottoNumberFactory.js b/src/domain/LottoNumberFactory.js index 35524bdf4..6f1beef90 100644 --- a/src/domain/LottoNumberFactory.js +++ b/src/domain/LottoNumberFactory.js @@ -1,4 +1,5 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; import LottoNumber from './LottoNumber.js'; class LottoNumberFactory { @@ -9,8 +10,8 @@ class LottoNumberFactory { } #initNumberMap() { - const [min, max] = Object.values(LottoNumber.getRange()); - for (let number = min; number <= max; number += 1) { + const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; + for (let number = MIN_RANGE; number <= MAX_RANGE; number += 1) { this.#numberMap.set(number, new LottoNumber(number)); } } diff --git a/src/domain/strategy/FixedPicker.js b/src/domain/strategy/FixedPicker.js deleted file mode 100644 index f5929778e..000000000 --- a/src/domain/strategy/FixedPicker.js +++ /dev/null @@ -1,6 +0,0 @@ -class FixedPicker { - pick(numbers) { - return numbers; - } -} -export default FixedPicker; diff --git a/src/domain/strategy/RandomPicker.js b/src/domain/strategy/RandomPicker.js deleted file mode 100644 index 463663136..000000000 --- a/src/domain/strategy/RandomPicker.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Random } from '@woowacourse/mission-utils'; -import LottoNumber from '../LottoNumber.js'; -import LOTTO_SETTING from '../../constants/lottoSetting.js'; - -class RandomPicker { - pick() { - const [min, max] = Object.values(LottoNumber.getRange()); - const quan = LOTTO_SETTING.MAX_QUANTITY; - return Random.pickUniqueNumbersInRange(min, max, quan); - } -} -export default RandomPicker; diff --git a/src/dto/responseDto/WinningResultDto.js b/src/dto/responseDto/WinningResultDto.js new file mode 100644 index 000000000..ebcd4351f --- /dev/null +++ b/src/dto/responseDto/WinningResultDto.js @@ -0,0 +1,17 @@ +class WinningResultDto { + #winningStats; + #winningRate; + + constructor({ winningStats, winningRate }) { + this.#winningStats = winningStats; + this.#winningRate = winningRate; + } + + toJSON() { + return { + winningStats: this.#winningStats, + winningRate: this.#winningRate, + }; + } +} +export default WinningResultDto; diff --git a/src/utils/RandomPicker.js b/src/utils/RandomPicker.js new file mode 100644 index 000000000..e74bf5202 --- /dev/null +++ b/src/utils/RandomPicker.js @@ -0,0 +1,10 @@ +import { Random } from '@woowacourse/mission-utils'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; + +class RandomPicker { + pick() { + const { MIN_RANGE, MAX_RANGE, MAX_QUANTITY } = LOTTO_SETTING; + return Random.pickUniqueNumbersInRange(MIN_RANGE, MAX_RANGE, MAX_QUANTITY); + } +} +export default RandomPicker; diff --git a/src/validator/domainValidator/BonusNumberValidator.js b/src/validator/domainValidator/BonusNumberValidator.js index e84841c1a..53a0e4c90 100644 --- a/src/validator/domainValidator/BonusNumberValidator.js +++ b/src/validator/domainValidator/BonusNumberValidator.js @@ -1,5 +1,5 @@ import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LottoNumber from '../../domain/LottoNumber.js'; +import LOTTO_SETTING from '../../constants/lottoSetting.js'; class BonusNumberValidator { static validate(winningLottoNumbers, bonusLottoNumber) { @@ -13,7 +13,7 @@ class BonusNumberValidator { static #isInRange(bonusLottoNumber) { const bonusNumber = bonusLottoNumber.getNumber(); - const [MIN_RANGE, MAX_RANGE] = Object.values(LottoNumber.getRange()); + const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; return MIN_RANGE <= bonusNumber && bonusNumber <= MAX_RANGE; } diff --git a/src/validator/domain/WinningNumbersValidator.js b/src/validator/domainValidator/WinningNumbersValidator.js similarity index 100% rename from src/validator/domain/WinningNumbersValidator.js rename to src/validator/domainValidator/WinningNumbersValidator.js From 36a1aa2795336b722fcdf3dc4c0dd80152f0f233 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 14:02:23 +0900 Subject: [PATCH 085/109] =?UTF-8?q?refactor(LottoPrice):=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20=EB=8B=A8=EC=9C=84=EB=A5=BC=20=EA=B0=80=EC=A7=80?= =?UTF-8?q?=EB=8D=98=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EA=B3=A0=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 결합도를 낮추기 위해 상수화 --- __tests__/domain/LottoPrice.test.js | 17 ----------------- src/constants/errorMessages.js | 1 + src/constants/lottoSetting.js | 1 + src/controller/LottoController.js | 1 - src/domain/LottoPrice.js | 12 ------------ src/services/LottoPurchaseService.js | 4 ++-- .../domainValidator/PurchaseValidator.js | 4 ++-- 7 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 __tests__/domain/LottoPrice.test.js delete mode 100644 src/domain/LottoPrice.js diff --git a/__tests__/domain/LottoPrice.test.js b/__tests__/domain/LottoPrice.test.js deleted file mode 100644 index 27358106c..000000000 --- a/__tests__/domain/LottoPrice.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import LottoPrice from '../../src/domain/LottoPrice'; - -describe('LottoPrice 테스트', () => { - describe('메서드 테스트', () => { - const PURCHASE_UNIT = 1000; - const purchaseAmount = 8000; - test('exchange(purchaseAmount) 단위로 구매금액을 나눈 count를 반환', () => { - const result = purchaseAmount / PURCHASE_UNIT; - expect(LottoPrice.exchange(purchaseAmount)).toBe(result); - }); //test - - test('modUnit(purchaseAmount) 단위로 구매금액의 나머지연산값을 반환', () => { - const result = purchaseAmount % PURCHASE_UNIT; - expect(LottoPrice.modUnit(purchaseAmount)).toBe(result); - }); //test - }); // 생성 테스트 -}); // LottoNumberFactory 테스트 diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 9559d3bb3..31b1524a6 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -3,6 +3,7 @@ const PREFIX = '[ERROR]'; const ERROR_MESSAGES = Object.freeze({ POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, + BLANK: `${PREFIX}입력 값이 비었습니다`, FORMAT_NOT_NUM: `${PREFIX} 숫자를 입력해야 합니다`, PURCHASE_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index f7b8f0229..7436847db 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -10,6 +10,7 @@ const LOTTO_SETTING = Object.freeze({ }), MIN_RANGE: 1, MAX_RANGE: 45, + PURCHASE_UNIT: 1000, }); export default LOTTO_SETTING; diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index a060ea9e9..f683c8620 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -21,7 +21,6 @@ class LottoController { return ''; // return this.getWinningRate(); } catch (err) { - console.log(err); this.#lottoView.printError(err); return this.processLottoPurchase(); } diff --git a/src/domain/LottoPrice.js b/src/domain/LottoPrice.js deleted file mode 100644 index c5da12564..000000000 --- a/src/domain/LottoPrice.js +++ /dev/null @@ -1,12 +0,0 @@ -class LottoPrice { - static #PURCHASE_UNIT = 1000; - - static exchange(purchaseAmount) { - return purchaseAmount / LottoPrice.#PURCHASE_UNIT; - } - - static modUnit(purchaseAmount) { - return purchaseAmount % LottoPrice.#PURCHASE_UNIT; - } -} -export default LottoPrice; diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index d8acaf83c..062a6357e 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -1,4 +1,4 @@ -import LottoPrice from '../domain/LottoPrice.js'; +import LOTTO_SETTING from '../constants/lottoSetting.js'; import PurchasedLottosDto from '../dto/responseDto/PurchasedLottosDto.js'; import PurchaseValidator from '../validator/domainValidator/PurchaseValidator.js'; @@ -16,7 +16,7 @@ class LottoPurchaseService { getPurchasedLottos() { const { purchaseAmount } = this.lottoRepository.findAll('admin'); - const lottoCount = LottoPrice.exchange(purchaseAmount); + const lottoCount = purchaseAmount / LOTTO_SETTING.PURCHASE_UNIT; const lottos = this.lottoStore.buyLotto(lottoCount); this.lottoRepository.save('admin', { lottos }); return new PurchasedLottosDto({ lottos }); diff --git a/src/validator/domainValidator/PurchaseValidator.js b/src/validator/domainValidator/PurchaseValidator.js index 0a2748303..df95c1de7 100644 --- a/src/validator/domainValidator/PurchaseValidator.js +++ b/src/validator/domainValidator/PurchaseValidator.js @@ -1,5 +1,5 @@ import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LottoPrice from '../../domain/LottoPrice.js'; +import LOTTO_SETTING from '../../constants/lottoSetting.js'; class PurchaseValidator { static validate(purchaseAmount) { @@ -9,7 +9,7 @@ class PurchaseValidator { } static #isModUnit(purchaseAmount) { - return LottoPrice.modUnit(purchaseAmount) === 0; + return purchaseAmount % LOTTO_SETTING.PURCHASE_UNIT === 0; } } From 5bf0ce584d06d9b2829d7dd7e7715133e7157ea8 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 14:06:38 +0900 Subject: [PATCH 086/109] =?UTF-8?q?feat(PurchaseValidator):=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EB=8B=A8=EC=9C=84=EB=B3=B4=EB=8B=A4=20=EC=BB=A4?= =?UTF-8?q?=EC=95=BC=ED=95=98=EB=8A=94=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/errorMessages.js | 4 +++- src/validator/domainValidator/PurchaseValidator.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 31b1524a6..6aaab3782 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,4 +1,4 @@ -const PREFIX = '[ERROR]'; +const PREFIX = '[ERROR] '; const ERROR_MESSAGES = Object.freeze({ POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, @@ -6,7 +6,9 @@ const ERROR_MESSAGES = Object.freeze({ BLANK: `${PREFIX}입력 값이 비었습니다`, FORMAT_NOT_NUM: `${PREFIX} 숫자를 입력해야 합니다`, + PURCHASE_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, + PURCHASE_LESS: `${PREFIX}구매 금액은 1,000원 보다 커야합니다`, LOTTO_NOT_NUMBER: `${PREFIX}로또 번호는 숫자만 추가 할 수 있습니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, diff --git a/src/validator/domainValidator/PurchaseValidator.js b/src/validator/domainValidator/PurchaseValidator.js index df95c1de7..3ec9aa9ee 100644 --- a/src/validator/domainValidator/PurchaseValidator.js +++ b/src/validator/domainValidator/PurchaseValidator.js @@ -3,11 +3,19 @@ import LOTTO_SETTING from '../../constants/lottoSetting.js'; class PurchaseValidator { static validate(purchaseAmount) { + if (PurchaseValidator.#isLessUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_LESS); + } + if (!PurchaseValidator.#isModUnit(purchaseAmount)) { throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); } } + static #isLessUnit(purchaseAmount) { + return purchaseAmount < LOTTO_SETTING.PURCHASE_UNIT; + } + static #isModUnit(purchaseAmount) { return purchaseAmount % LOTTO_SETTING.PURCHASE_UNIT === 0; } From 63613e0c1ecc099c8e3eb26a97d3b64cdd4ce61f Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 15:12:05 +0900 Subject: [PATCH 087/109] =?UTF-8?q?feat(WinningResult):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EA=B2=B0=EA=B3=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력 받은 숫자로 같은 인스턴스를 참조하게 만들어 비교 --- src/App.js | 18 +++++--- src/controller/LottoController.js | 30 +++++++++++-- src/domain/LottoWinningFactory.js | 3 +- src/services/WinningResultService.js | 42 +++++++++++++++++++ .../WinningNumbersValidator.js | 3 +- src/view/LottoView.js | 19 ++++++++- 6 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 src/services/WinningResultService.js diff --git a/src/App.js b/src/App.js index dce77bf5b..1e612724f 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,8 @@ import LottoNumberFactory from './domain/LottoNumberFactory.js'; import LottoRepository from './repository/LottoRepository.js'; import LottoPurchaseService from './services/LottoPurchaseService.js'; import RandomPicker from './utils/RandomPicker.js'; +import LottoWinningFactory from './domain/LottoWinningFactory.js'; +import WinningResultService from './services/WinningResultService.js'; class App { #lottoController; @@ -17,16 +19,20 @@ class App { const randomStrategy = new RandomPicker(); const randomLottoFactory = new LottoFactory(randomStrategy, lottoNumberFactory); const lottoStore = new LottoStore(randomLottoFactory); - - // lottoWinning: 고정값 전략 팩토리 주입 - // const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); - - // lottoSerivce 주입단계 const lottoRepository = new LottoRepository(); const lottoPurchaseService = new LottoPurchaseService(lottoStore, lottoRepository); + // lottoSerivce 주입단계 + const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); + const winningResultService = new WinningResultService(lottoWinningFactory, lottoRepository); + const lottoView = new LottoView(); - this.#lottoController = new LottoController(lottoPurchaseService, lottoView); + + this.#lottoController = new LottoController( + lottoPurchaseService, + winningResultService, + lottoView, + ); } async run() { diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index f683c8620..86d4eaeb2 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,11 +1,15 @@ +import BonusNumberDto from '../dto/requestDto/BonusNumberDto.js'; import PurchaseAmountDto from '../dto/requestDto/PurchaseAmountDto.js'; +import WinningNumbersDto from '../dto/requestDto/WinningNumbersDto.js'; class LottoController { #lottoPurchaseService; #lottoView; + #winningResultService; - constructor(lottoPurchaseService, lottoView) { + constructor(lottoPurchaseService, winningResultService, lottoView) { this.#lottoPurchaseService = lottoPurchaseService; + this.#winningResultService = winningResultService; this.#lottoView = lottoView; } @@ -18,8 +22,7 @@ class LottoController { const purchasedLottos = this.#lottoPurchaseService.getPurchasedLottos(); const purchasedLottosDto = purchasedLottos.toJSON(); this.#lottoView.printPurchaseLottos(purchasedLottosDto); - return ''; - // return this.getWinningRate(); + return this.#processWinningResult(); } catch (err) { this.#lottoView.printError(err); return this.processLottoPurchase(); @@ -27,7 +30,26 @@ class LottoController { } // 메서드명 고민해보기 - async getWinningRate() {} + async #processWinningResult() { + try { + const winningNumbers = await this.#lottoView.readWinningNumbers(); + const purchaseAmountDto = new WinningNumbersDto(winningNumbers); + this.#winningResultService.saveWinningNumbers(purchaseAmountDto); + + const bonusNumber = await this.#lottoView.readBonusNumber(); + const bonusNumberDto = new BonusNumberDto(bonusNumber); + this.#winningResultService.saveBonusNumber(bonusNumberDto); + + const winningResult = this.#winningResultService.getWinningResult(); + const winningResultDto = winningResult.toJSON(); + this.#lottoView.printWinningResult(winningResultDto); + + return true; + } catch (err) { + this.#lottoView.printError(err); + return this.#processWinningResult(); + } + } } export default LottoController; diff --git a/src/domain/LottoWinningFactory.js b/src/domain/LottoWinningFactory.js index 7cf18bac2..749c14b66 100644 --- a/src/domain/LottoWinningFactory.js +++ b/src/domain/LottoWinningFactory.js @@ -6,8 +6,7 @@ class LottoWinningFactory { } createWinningLotto(numbers) { - const newArray = numbers; - return newArray.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); + return numbers.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); } createBonusLotto(number) { diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js new file mode 100644 index 000000000..b8215b705 --- /dev/null +++ b/src/services/WinningResultService.js @@ -0,0 +1,42 @@ +import WinningNumbersValidator from '../validator/domainValidator/WinningNumbersValidator.js'; +import BonusNumberValidator from '../validator/domainValidator/BonusNumberValidator.js'; +import LottoWinningResult from '../domain/LottoWinningResult.js'; +import WinningResultDto from '../dto/responseDto/WinningResultDto.js'; + +class WinningResultService { + constructor(winningFactory, lottoRepository) { + this.winningFactory = winningFactory; + this.lottoRepository = lottoRepository; + } + + saveWinningNumbers(requestDTO) { + const { winningNumbers } = requestDTO; + WinningNumbersValidator.validate(winningNumbers); + const winningLotto = this.winningFactory.createWinningLotto(winningNumbers); + this.lottoRepository.save('admin', { winningLotto }); + } + + saveBonusNumber(requestDTO) { + const { bonusNumber } = requestDTO; + const { winningLotto } = this.lottoRepository.findAll('admin'); + // 객체끼리의 비교를 위해 먼저 생성 + const bonusLotto = this.winningFactory.createBonusLotto(bonusNumber); + BonusNumberValidator.validate(winningLotto, bonusLotto); + this.lottoRepository.save('admin', { bonusLotto }); + } + + getWinningResult() { + const db = this.lottoRepository.findAll('admin'); + const { purchaseAmount, lottos, winningLotto, bonusLotto } = db; + const { winningStats, totalWinningAmount } = LottoWinningResult.getWinningStats( + lottos, + winningLotto, + bonusLotto, + ); + const winningRate = LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount); + this.lottoRepository.save('admin', { winningStats, winningRate }); + return new WinningResultDto({ winningStats, winningRate }); + } +} + +export default WinningResultService; diff --git a/src/validator/domainValidator/WinningNumbersValidator.js b/src/validator/domainValidator/WinningNumbersValidator.js index ce3eb745a..36e1dc770 100644 --- a/src/validator/domainValidator/WinningNumbersValidator.js +++ b/src/validator/domainValidator/WinningNumbersValidator.js @@ -1,6 +1,5 @@ import ERROR_MESSAGES from '../../constants/errorMessages.js'; import LOTTO_SETTING from '../../constants/lottoSetting.js'; -import LottoNumber from '../../domain/LottoNumber.js'; class WinningNumbersValidator { static validate(winningNumbers) { @@ -16,7 +15,7 @@ class WinningNumbersValidator { } static #isInRange(winningNumbers) { - const [MIN_RANGE, MAX_RANGE] = Object.values(LottoNumber.getRange()); + const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; return winningNumbers.every((number) => MIN_RANGE <= number && number <= MAX_RANGE); } diff --git a/src/view/LottoView.js b/src/view/LottoView.js index 2c5d800fd..a9482fe74 100644 --- a/src/view/LottoView.js +++ b/src/view/LottoView.js @@ -2,8 +2,8 @@ import { Console } from '@woowacourse/mission-utils'; const INFO_MEESAGE = { INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', - INFO_WINNING_NUMBERS: '당첨 번호를 입력해 주세요.\n', - INFO_BONUS_NUMBER: '보너스 번호를 입력해 주세요.\n', + INFO_WINNING_NUMBERS: '\n당첨 번호를 입력해 주세요.\n', + INFO_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n', }; class LottoView { @@ -30,6 +30,21 @@ class LottoView { }); } + printWinningResult(winngResultData) { + const { winningStats, winningRate } = winngResultData; + const { FIRST, SECOND, THIRD, FOURTH, FIFTH } = winningStats; + const template = `\n당첨 통계 +--- +3개 일치 (5,000원) - ${FIFTH}개 +4개 일치 (50,000원) - ${FOURTH}개 +5개 일치 (1,500,000원) - ${THIRD}개 +5개 일치, 보너스 볼 일치 (30,000,000원) - ${SECOND}개 +6개 일치 (2,000,000,000원) - ${FIRST}개 +총 수익률은 ${winningRate}%입니다.`; + + Console.print(template); + } + printError(err) { Console.print(err.message); } From 52ec0901c8dde13e3195d50257c0d2c100d86e86 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 15:22:18 +0900 Subject: [PATCH 088/109] =?UTF-8?q?fix(LottoFacotry):=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전략이 하나이기 때문에 변수명 변경 --- __tests__/domain/LottoFactory.test.js | 5 +++-- src/App.js | 10 +++++----- src/domain/LottoFacotry.js | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/__tests__/domain/LottoFactory.test.js b/__tests__/domain/LottoFactory.test.js index 08ebb0da4..51ffadf10 100644 --- a/__tests__/domain/LottoFactory.test.js +++ b/__tests__/domain/LottoFactory.test.js @@ -1,10 +1,11 @@ import LottoFactory from '../../src/domain/LottoFacotry.js'; -import FixedPicker from '../../src/domain/strategy/FixedPicker.js'; const mockLottoNumberFactory = { getLottoNumber: jest.fn((number) => ({ getNumber: () => number })), }; -const fixed = new FixedPicker(); +const fixed = { + pick: (arr) => arr, +}; describe('LottoFactory 테스트', () => { describe('생성 테스트', () => { test('', () => { diff --git a/src/App.js b/src/App.js index 1e612724f..12436e49e 100644 --- a/src/App.js +++ b/src/App.js @@ -14,15 +14,15 @@ class App { constructor() { const lottoNumberFactory = LottoNumberFactory; + const lottoRepository = new LottoRepository(); - // lottoStore: 랜덤 전략 팩토리 주입 - const randomStrategy = new RandomPicker(); - const randomLottoFactory = new LottoFactory(randomStrategy, lottoNumberFactory); + // 구매 의존성, 랜덤조건 주입 + const randomPiker = new RandomPicker(); + const randomLottoFactory = new LottoFactory(randomPiker, lottoNumberFactory); const lottoStore = new LottoStore(randomLottoFactory); - const lottoRepository = new LottoRepository(); const lottoPurchaseService = new LottoPurchaseService(lottoStore, lottoRepository); - // lottoSerivce 주입단계 + // 당첨 의존성, 고정 생성 팩토리 주입 const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); const winningResultService = new WinningResultService(lottoWinningFactory, lottoRepository); diff --git a/src/domain/LottoFacotry.js b/src/domain/LottoFacotry.js index 92891e0b6..93568caae 100644 --- a/src/domain/LottoFacotry.js +++ b/src/domain/LottoFacotry.js @@ -1,16 +1,16 @@ import Lotto from './Lotto.js'; class LottoFactory { - #strategy; + #piker; #lottoNumberFactory; - constructor(strategy, lottoNumberFactory) { - this.#strategy = strategy; + constructor(piker, lottoNumberFactory) { + this.#piker = piker; this.#lottoNumberFactory = lottoNumberFactory; } createLotto(numbers) { - const newArray = this.#strategy.pick(numbers); + const newArray = this.#piker.pick(numbers); const creatdeNumbers = newArray.map((number) => this.#lottoNumberFactory.getLottoNumber(number), ); From 219acf0e48a5f5c2195e8c13acbfcae75e366fd0 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 15:40:42 +0900 Subject: [PATCH 089/109] =?UTF-8?q?refactor(view):=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 13 ++++++---- src/controller/LottoController.js | 22 +++++++++------- src/view/LottoInputView.js | 26 +++++++++++++++++++ src/view/{LottoView.js => LottoOutputView.js} | 25 ++---------------- 4 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 src/view/LottoInputView.js rename src/view/{LottoView.js => LottoOutputView.js} (54%) diff --git a/src/App.js b/src/App.js index 12436e49e..7c0a284d8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,5 @@ import LottoController from './controller/LottoController.js'; import LottoFactory from './domain/LottoFacotry.js'; -import LottoView from './view/LottoView.js'; import LottoStore from './domain/LottoStore.js'; import LottoNumberFactory from './domain/LottoNumberFactory.js'; import LottoRepository from './repository/LottoRepository.js'; @@ -8,6 +7,8 @@ import LottoPurchaseService from './services/LottoPurchaseService.js'; import RandomPicker from './utils/RandomPicker.js'; import LottoWinningFactory from './domain/LottoWinningFactory.js'; import WinningResultService from './services/WinningResultService.js'; +import LottoInputView from './view/LottoInputView.js'; +import LottoOutputView from './view/LottoOutputView.js'; class App { #lottoController; @@ -26,13 +27,15 @@ class App { const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); const winningResultService = new WinningResultService(lottoWinningFactory, lottoRepository); - const lottoView = new LottoView(); + const lottoInputView = new LottoInputView(); + const lottoOutputView = new LottoOutputView(); - this.#lottoController = new LottoController( + this.#lottoController = new LottoController({ lottoPurchaseService, winningResultService, - lottoView, - ); + lottoOutputView, + lottoInputView, + }); } async run() { diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index 86d4eaeb2..1a0ccd08e 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -4,27 +4,29 @@ import WinningNumbersDto from '../dto/requestDto/WinningNumbersDto.js'; class LottoController { #lottoPurchaseService; - #lottoView; #winningResultService; + #lottoInputView; + #lottoOutputView; - constructor(lottoPurchaseService, winningResultService, lottoView) { + constructor({ lottoPurchaseService, winningResultService, lottoOutputView, lottoInputView }) { this.#lottoPurchaseService = lottoPurchaseService; this.#winningResultService = winningResultService; - this.#lottoView = lottoView; + this.#lottoInputView = lottoInputView; + this.#lottoOutputView = lottoOutputView; } async processLottoPurchase() { try { - const purchaseAmount = await this.#lottoView.readPurchaseAmount(); + const purchaseAmount = await this.#lottoInputView.readPurchaseAmount(); const purchaseAmountDto = new PurchaseAmountDto(purchaseAmount); this.#lottoPurchaseService.savePurchaseAmount(purchaseAmountDto); const purchasedLottos = this.#lottoPurchaseService.getPurchasedLottos(); const purchasedLottosDto = purchasedLottos.toJSON(); - this.#lottoView.printPurchaseLottos(purchasedLottosDto); + this.#lottoOutputView.printPurchaseLottos(purchasedLottosDto); return this.#processWinningResult(); } catch (err) { - this.#lottoView.printError(err); + this.#lottoOutputView.printError(err); return this.processLottoPurchase(); } } @@ -32,21 +34,21 @@ class LottoController { // 메서드명 고민해보기 async #processWinningResult() { try { - const winningNumbers = await this.#lottoView.readWinningNumbers(); + const winningNumbers = await this.#lottoInputView.readWinningNumbers(); const purchaseAmountDto = new WinningNumbersDto(winningNumbers); this.#winningResultService.saveWinningNumbers(purchaseAmountDto); - const bonusNumber = await this.#lottoView.readBonusNumber(); + const bonusNumber = await this.#lottoInputView.readBonusNumber(); const bonusNumberDto = new BonusNumberDto(bonusNumber); this.#winningResultService.saveBonusNumber(bonusNumberDto); const winningResult = this.#winningResultService.getWinningResult(); const winningResultDto = winningResult.toJSON(); - this.#lottoView.printWinningResult(winningResultDto); + this.#lottoOutputView.printWinningResult(winningResultDto); return true; } catch (err) { - this.#lottoView.printError(err); + this.#lottoOutputView.printError(err); return this.#processWinningResult(); } } diff --git a/src/view/LottoInputView.js b/src/view/LottoInputView.js new file mode 100644 index 000000000..9c03c66ac --- /dev/null +++ b/src/view/LottoInputView.js @@ -0,0 +1,26 @@ +import { Console } from '@woowacourse/mission-utils'; + +const INFO_MEESAGE = { + INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', + INFO_WINNING_NUMBERS: '\n당첨 번호를 입력해 주세요.\n', + INFO_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n', +}; + +class LottoInputView { + async readPurchaseAmount() { + const purchaseAmount = await Console.readLineAsync(INFO_MEESAGE.INFO_PURCHASE_AMOUNT); + return purchaseAmount; + } + + async readWinningNumbers() { + const winningNumbers = await Console.readLineAsync(INFO_MEESAGE.INFO_WINNING_NUMBERS); + return winningNumbers; + } + + async readBonusNumber() { + const bonusNumber = await Console.readLineAsync(INFO_MEESAGE.INFO_BONUS_NUMBER); + return bonusNumber; + } +} + +export default LottoInputView; diff --git a/src/view/LottoView.js b/src/view/LottoOutputView.js similarity index 54% rename from src/view/LottoView.js rename to src/view/LottoOutputView.js index a9482fe74..47e418c4d 100644 --- a/src/view/LottoView.js +++ b/src/view/LottoOutputView.js @@ -1,27 +1,6 @@ import { Console } from '@woowacourse/mission-utils'; -const INFO_MEESAGE = { - INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', - INFO_WINNING_NUMBERS: '\n당첨 번호를 입력해 주세요.\n', - INFO_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n', -}; - -class LottoView { - async readPurchaseAmount() { - const purchaseAmount = await Console.readLineAsync(INFO_MEESAGE.INFO_PURCHASE_AMOUNT); - return purchaseAmount; - } - - async readWinningNumbers() { - const winningNumbers = await Console.readLineAsync(INFO_MEESAGE.INFO_WINNING_NUMBERS); - return winningNumbers; - } - - async readBonusNumber() { - const bonusNumber = await Console.readLineAsync(INFO_MEESAGE.INFO_BONUS_NUMBER); - return bonusNumber; - } - +class LottoOutputView { printPurchaseLottos(lottosData) { const { lottos } = lottosData; Console.print(`${lottos.length}개를 구매했습니다.`); @@ -50,4 +29,4 @@ class LottoView { } } -export default LottoView; +export default LottoOutputView; From 423d3a6093001bb82e0ef654f7f47d45c6516cb4 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 15:52:00 +0900 Subject: [PATCH 090/109] =?UTF-8?q?refactor:=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=B5=EC=88=98=ED=98=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/{domain => domains}/Lotto.test.js | 2 +- .../{domain => domains}/LottoFactory.test.js | 2 +- .../{domain => domains}/LottoNumber.test.js | 2 +- .../LottoNumberFactory.test.js | 5 ++--- .../{domain => domains}/LottoStore.test.js | 4 ++-- src/App.js | 18 +++++++++--------- src/constants/lottoSetting.js | 2 +- .../LottoController.js | 6 +++--- src/{domain => domains}/Lotto.js | 0 src/{domain => domains}/LottoFacotry.js | 0 src/{domain => domains}/LottoNumber.js | 0 src/{domain => domains}/LottoNumberFactory.js | 0 src/{domain => domains}/LottoStore.js | 0 src/{domain => domains}/LottoWinningFactory.js | 0 src/{domain => domains}/LottoWinningResult.js | 0 src/{dto => dtos}/requestDto/BonusNumberDto.js | 2 +- .../requestDto/PurchaseAmountDto.js | 2 +- .../requestDto/WinningNumbersDto.js | 2 +- .../responseDto/PurchasedLottosDto.js | 0 .../responseDto/WinningResultDto.js | 0 .../LottoRepository.js | 0 src/services/LottoPurchaseService.js | 4 ++-- src/services/WinningResultService.js | 8 ++++---- src/{validator => validators}/UtilValidator.js | 0 .../domain}/BonusNumberValidator.js | 0 .../domain}/PurchaseValidator.js | 0 .../domain}/WinningNumbersValidator.js | 0 .../input}/InputBonusNumberValidator.js | 0 .../input}/InputPurchaseAmountValidator.js | 0 .../input}/InputWinningNumberValidator.js | 0 src/{view => views}/LottoInputView.js | 0 src/{view => views}/LottoOutputView.js | 0 32 files changed, 29 insertions(+), 30 deletions(-) rename __tests__/{domain => domains}/Lotto.test.js (97%) rename __tests__/{domain => domains}/LottoFactory.test.js (89%) rename __tests__/{domain => domains}/LottoNumber.test.js (95%) rename __tests__/{domain => domains}/LottoNumberFactory.test.js (83%) rename __tests__/{domain => domains}/LottoStore.test.js (88%) rename src/{controller => controllers}/LottoController.js (90%) rename src/{domain => domains}/Lotto.js (100%) rename src/{domain => domains}/LottoFacotry.js (100%) rename src/{domain => domains}/LottoNumber.js (100%) rename src/{domain => domains}/LottoNumberFactory.js (100%) rename src/{domain => domains}/LottoStore.js (100%) rename src/{domain => domains}/LottoWinningFactory.js (100%) rename src/{domain => domains}/LottoWinningResult.js (100%) rename src/{dto => dtos}/requestDto/BonusNumberDto.js (76%) rename src/{dto => dtos}/requestDto/PurchaseAmountDto.js (77%) rename src/{dto => dtos}/requestDto/WinningNumbersDto.js (79%) rename src/{dto => dtos}/responseDto/PurchasedLottosDto.js (100%) rename src/{dto => dtos}/responseDto/WinningResultDto.js (100%) rename src/{repository => repositories}/LottoRepository.js (100%) rename src/{validator => validators}/UtilValidator.js (100%) rename src/{validator/domainValidator => validators/domain}/BonusNumberValidator.js (100%) rename src/{validator/domainValidator => validators/domain}/PurchaseValidator.js (100%) rename src/{validator/domainValidator => validators/domain}/WinningNumbersValidator.js (100%) rename src/{validator/inputValidator => validators/input}/InputBonusNumberValidator.js (100%) rename src/{validator/inputValidator => validators/input}/InputPurchaseAmountValidator.js (100%) rename src/{validator/inputValidator => validators/input}/InputWinningNumberValidator.js (100%) rename src/{view => views}/LottoInputView.js (100%) rename src/{view => views}/LottoOutputView.js (100%) diff --git a/__tests__/domain/Lotto.test.js b/__tests__/domains/Lotto.test.js similarity index 97% rename from __tests__/domain/Lotto.test.js rename to __tests__/domains/Lotto.test.js index 258d4179e..a20b7aecc 100644 --- a/__tests__/domain/Lotto.test.js +++ b/__tests__/domains/Lotto.test.js @@ -1,4 +1,4 @@ -import Lotto from '../../src/domain/Lotto.js'; +import Lotto from '../../src/domains/Lotto.js'; import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; const MOCK_NUMBERS = [2, 1, 3, 4, 5, 6]; diff --git a/__tests__/domain/LottoFactory.test.js b/__tests__/domains/LottoFactory.test.js similarity index 89% rename from __tests__/domain/LottoFactory.test.js rename to __tests__/domains/LottoFactory.test.js index 51ffadf10..4149f13a2 100644 --- a/__tests__/domain/LottoFactory.test.js +++ b/__tests__/domains/LottoFactory.test.js @@ -1,4 +1,4 @@ -import LottoFactory from '../../src/domain/LottoFacotry.js'; +import LottoFactory from '../../src/domains/LottoFacotry.js'; const mockLottoNumberFactory = { getLottoNumber: jest.fn((number) => ({ getNumber: () => number })), diff --git a/__tests__/domain/LottoNumber.test.js b/__tests__/domains/LottoNumber.test.js similarity index 95% rename from __tests__/domain/LottoNumber.test.js rename to __tests__/domains/LottoNumber.test.js index 8eb951508..cf2f69be1 100644 --- a/__tests__/domain/LottoNumber.test.js +++ b/__tests__/domains/LottoNumber.test.js @@ -1,5 +1,5 @@ import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; -import LottoNumber from '../../src/domain/LottoNumber.js'; +import LottoNumber from '../../src/domains/LottoNumber.js'; const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; describe('LottoNumber 클래스 테스트', () => { diff --git a/__tests__/domain/LottoNumberFactory.test.js b/__tests__/domains/LottoNumberFactory.test.js similarity index 83% rename from __tests__/domain/LottoNumberFactory.test.js rename to __tests__/domains/LottoNumberFactory.test.js index 8168a1ae8..d4bf0c52d 100644 --- a/__tests__/domain/LottoNumberFactory.test.js +++ b/__tests__/domains/LottoNumberFactory.test.js @@ -1,6 +1,5 @@ -import LottoNumber from '../../src/domain/LottoNumber.js'; -import LottoNumberFactory from '../../src/domain/LottoNumberFactory.js'; -import LottoNumberFactoryCopy from '../../src/domain/LottoNumberFactory.js'; +import LottoNumberFactory from '../../src/domains/LottoNumberFactory.js'; +import LottoNumberFactoryCopy from '../../src/domains/LottoNumberFactory.js'; import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; diff --git a/__tests__/domain/LottoStore.test.js b/__tests__/domains/LottoStore.test.js similarity index 88% rename from __tests__/domain/LottoStore.test.js rename to __tests__/domains/LottoStore.test.js index 46654df33..6e2bbf974 100644 --- a/__tests__/domain/LottoStore.test.js +++ b/__tests__/domains/LottoStore.test.js @@ -1,5 +1,5 @@ -import LottoStore from '../../src/domain/LottoStore.js'; -import Lotto from '../../src/domain/Lotto.js'; +import LottoStore from '../../src/domains/LottoStore.js'; +import Lotto from '../../src/domains/Lotto.js'; const MOCK_NUMBERS = [1, 2, 3, 4, 5, 6]; const mockLottoArray = MOCK_NUMBERS.map((num) => ({ getNumber: () => num })); diff --git a/src/App.js b/src/App.js index 7c0a284d8..b118526b4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,14 +1,14 @@ -import LottoController from './controller/LottoController.js'; -import LottoFactory from './domain/LottoFacotry.js'; -import LottoStore from './domain/LottoStore.js'; -import LottoNumberFactory from './domain/LottoNumberFactory.js'; -import LottoRepository from './repository/LottoRepository.js'; -import LottoPurchaseService from './services/LottoPurchaseService.js'; +import LottoNumberFactory from './domains/LottoNumberFactory.js'; +import LottoFactory from './domains/LottoFacotry.js'; import RandomPicker from './utils/RandomPicker.js'; -import LottoWinningFactory from './domain/LottoWinningFactory.js'; +import LottoStore from './domains/LottoStore.js'; +import LottoRepository from './repositories/LottoRepository.js'; +import LottoWinningFactory from './domains/LottoWinningFactory.js'; +import LottoPurchaseService from './services/LottoPurchaseService.js'; import WinningResultService from './services/WinningResultService.js'; -import LottoInputView from './view/LottoInputView.js'; -import LottoOutputView from './view/LottoOutputView.js'; +import LottoInputView from './views/LottoInputView.js'; +import LottoOutputView from './views/LottoOutputView.js'; +import LottoController from './controllers/LottoController.js'; class App { #lottoController; diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 7436847db..6d05d00f3 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -1,5 +1,4 @@ const LOTTO_SETTING = Object.freeze({ - MAX_QUANTITY: 6, PRIZES: Object.freeze({ FIRST: 2000000000, SECOND: 30000000, @@ -8,6 +7,7 @@ const LOTTO_SETTING = Object.freeze({ FIFTH: 5000, OTHER: 0, }), + MAX_QUANTITY: 6, MIN_RANGE: 1, MAX_RANGE: 45, PURCHASE_UNIT: 1000, diff --git a/src/controller/LottoController.js b/src/controllers/LottoController.js similarity index 90% rename from src/controller/LottoController.js rename to src/controllers/LottoController.js index 1a0ccd08e..4de620bd3 100644 --- a/src/controller/LottoController.js +++ b/src/controllers/LottoController.js @@ -1,6 +1,6 @@ -import BonusNumberDto from '../dto/requestDto/BonusNumberDto.js'; -import PurchaseAmountDto from '../dto/requestDto/PurchaseAmountDto.js'; -import WinningNumbersDto from '../dto/requestDto/WinningNumbersDto.js'; +import BonusNumberDto from '../dtos/requestDto/BonusNumberDto.js'; +import PurchaseAmountDto from '../dtos/requestDto/PurchaseAmountDto.js'; +import WinningNumbersDto from '../dtos/requestDto/WinningNumbersDto.js'; class LottoController { #lottoPurchaseService; diff --git a/src/domain/Lotto.js b/src/domains/Lotto.js similarity index 100% rename from src/domain/Lotto.js rename to src/domains/Lotto.js diff --git a/src/domain/LottoFacotry.js b/src/domains/LottoFacotry.js similarity index 100% rename from src/domain/LottoFacotry.js rename to src/domains/LottoFacotry.js diff --git a/src/domain/LottoNumber.js b/src/domains/LottoNumber.js similarity index 100% rename from src/domain/LottoNumber.js rename to src/domains/LottoNumber.js diff --git a/src/domain/LottoNumberFactory.js b/src/domains/LottoNumberFactory.js similarity index 100% rename from src/domain/LottoNumberFactory.js rename to src/domains/LottoNumberFactory.js diff --git a/src/domain/LottoStore.js b/src/domains/LottoStore.js similarity index 100% rename from src/domain/LottoStore.js rename to src/domains/LottoStore.js diff --git a/src/domain/LottoWinningFactory.js b/src/domains/LottoWinningFactory.js similarity index 100% rename from src/domain/LottoWinningFactory.js rename to src/domains/LottoWinningFactory.js diff --git a/src/domain/LottoWinningResult.js b/src/domains/LottoWinningResult.js similarity index 100% rename from src/domain/LottoWinningResult.js rename to src/domains/LottoWinningResult.js diff --git a/src/dto/requestDto/BonusNumberDto.js b/src/dtos/requestDto/BonusNumberDto.js similarity index 76% rename from src/dto/requestDto/BonusNumberDto.js rename to src/dtos/requestDto/BonusNumberDto.js index 19302dc66..1c356b1e1 100644 --- a/src/dto/requestDto/BonusNumberDto.js +++ b/src/dtos/requestDto/BonusNumberDto.js @@ -1,4 +1,4 @@ -import InputBonusNumberValidator from '../../validator/inputValidator/InputBonusNumberValidator.js'; +import InputBonusNumberValidator from '../../validators/input/InputBonusNumberValidator.js'; class BonusNumberDto { #bonusNumber; diff --git a/src/dto/requestDto/PurchaseAmountDto.js b/src/dtos/requestDto/PurchaseAmountDto.js similarity index 77% rename from src/dto/requestDto/PurchaseAmountDto.js rename to src/dtos/requestDto/PurchaseAmountDto.js index 0d35b14fc..4eb3cb0f9 100644 --- a/src/dto/requestDto/PurchaseAmountDto.js +++ b/src/dtos/requestDto/PurchaseAmountDto.js @@ -1,4 +1,4 @@ -import InputPurchaseAmountValidator from '../../validator/inputValidator/InputPurchaseAmountValidator.js'; +import InputPurchaseAmountValidator from '../../validators/input/InputPurchaseAmountValidator.js'; class PurchaseAmountDto { #purchaseAmount; diff --git a/src/dto/requestDto/WinningNumbersDto.js b/src/dtos/requestDto/WinningNumbersDto.js similarity index 79% rename from src/dto/requestDto/WinningNumbersDto.js rename to src/dtos/requestDto/WinningNumbersDto.js index aa5fee42b..f745860b1 100644 --- a/src/dto/requestDto/WinningNumbersDto.js +++ b/src/dtos/requestDto/WinningNumbersDto.js @@ -1,5 +1,5 @@ import Parser from '../../utils/Parser.js'; -import InputWinningNumberValidator from '../../validator/inputValidator/InputWinningNumberValidator.js'; +import InputWinningNumberValidator from '../../validators/input/InputWinningNumberValidator.js'; class WinningNumbersDto { #winningNumbers; diff --git a/src/dto/responseDto/PurchasedLottosDto.js b/src/dtos/responseDto/PurchasedLottosDto.js similarity index 100% rename from src/dto/responseDto/PurchasedLottosDto.js rename to src/dtos/responseDto/PurchasedLottosDto.js diff --git a/src/dto/responseDto/WinningResultDto.js b/src/dtos/responseDto/WinningResultDto.js similarity index 100% rename from src/dto/responseDto/WinningResultDto.js rename to src/dtos/responseDto/WinningResultDto.js diff --git a/src/repository/LottoRepository.js b/src/repositories/LottoRepository.js similarity index 100% rename from src/repository/LottoRepository.js rename to src/repositories/LottoRepository.js diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index 062a6357e..34971388d 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -1,6 +1,6 @@ import LOTTO_SETTING from '../constants/lottoSetting.js'; -import PurchasedLottosDto from '../dto/responseDto/PurchasedLottosDto.js'; -import PurchaseValidator from '../validator/domainValidator/PurchaseValidator.js'; +import PurchasedLottosDto from '../dtos/responseDto/PurchasedLottosDto.js'; +import PurchaseValidator from '../validators/domain/PurchaseValidator.js'; class LottoPurchaseService { constructor(lottoStore, lottoRepository) { diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index b8215b705..f5fe9d21a 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -1,7 +1,7 @@ -import WinningNumbersValidator from '../validator/domainValidator/WinningNumbersValidator.js'; -import BonusNumberValidator from '../validator/domainValidator/BonusNumberValidator.js'; -import LottoWinningResult from '../domain/LottoWinningResult.js'; -import WinningResultDto from '../dto/responseDto/WinningResultDto.js'; +import WinningNumbersValidator from '../validators/domain/WinningNumbersValidator.js'; +import BonusNumberValidator from '../validators/domain/BonusNumberValidator.js'; +import LottoWinningResult from '../domains/LottoWinningResult.js'; +import WinningResultDto from '../dtos/responseDto/WinningResultDto.js'; class WinningResultService { constructor(winningFactory, lottoRepository) { diff --git a/src/validator/UtilValidator.js b/src/validators/UtilValidator.js similarity index 100% rename from src/validator/UtilValidator.js rename to src/validators/UtilValidator.js diff --git a/src/validator/domainValidator/BonusNumberValidator.js b/src/validators/domain/BonusNumberValidator.js similarity index 100% rename from src/validator/domainValidator/BonusNumberValidator.js rename to src/validators/domain/BonusNumberValidator.js diff --git a/src/validator/domainValidator/PurchaseValidator.js b/src/validators/domain/PurchaseValidator.js similarity index 100% rename from src/validator/domainValidator/PurchaseValidator.js rename to src/validators/domain/PurchaseValidator.js diff --git a/src/validator/domainValidator/WinningNumbersValidator.js b/src/validators/domain/WinningNumbersValidator.js similarity index 100% rename from src/validator/domainValidator/WinningNumbersValidator.js rename to src/validators/domain/WinningNumbersValidator.js diff --git a/src/validator/inputValidator/InputBonusNumberValidator.js b/src/validators/input/InputBonusNumberValidator.js similarity index 100% rename from src/validator/inputValidator/InputBonusNumberValidator.js rename to src/validators/input/InputBonusNumberValidator.js diff --git a/src/validator/inputValidator/InputPurchaseAmountValidator.js b/src/validators/input/InputPurchaseAmountValidator.js similarity index 100% rename from src/validator/inputValidator/InputPurchaseAmountValidator.js rename to src/validators/input/InputPurchaseAmountValidator.js diff --git a/src/validator/inputValidator/InputWinningNumberValidator.js b/src/validators/input/InputWinningNumberValidator.js similarity index 100% rename from src/validator/inputValidator/InputWinningNumberValidator.js rename to src/validators/input/InputWinningNumberValidator.js diff --git a/src/view/LottoInputView.js b/src/views/LottoInputView.js similarity index 100% rename from src/view/LottoInputView.js rename to src/views/LottoInputView.js diff --git a/src/view/LottoOutputView.js b/src/views/LottoOutputView.js similarity index 100% rename from src/view/LottoOutputView.js rename to src/views/LottoOutputView.js From 0968cba988f7288004b1b45eed3bae992479c58c Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 18:28:31 +0900 Subject: [PATCH 091/109] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=EC=95=88?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=B2=A8=EB=A6=AC=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validators/UtilValidator.js | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/validators/UtilValidator.js diff --git a/src/validators/UtilValidator.js b/src/validators/UtilValidator.js deleted file mode 100644 index 2cd1f0c8c..000000000 --- a/src/validators/UtilValidator.js +++ /dev/null @@ -1,31 +0,0 @@ -class UtilValidator { - static isConvertNum(param) { - if (param === null || typeof param === 'undefined') { - return false; - } - if (String(param).trim() === '') { - return false; - } - if (Number.isNaN(Number(param))) { - return false; - } - return true; - } - - static isNum(param) { - return typeof param === 'number'; - } - - static isPositive(param) { - if (!UtilValidator.isConvertNum(param)) return false; - if (Number(param) <= 0) return false; - return true; - } - - static isInteger(param) { - if (!UtilValidator.isConvertNum(param)) return false; - return Number.isInteger(Number(param)); - } -} - -export default UtilValidator; From 9e461167791c6380edd940eac73576e2e64b7f29 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 19:11:25 +0900 Subject: [PATCH 092/109] =?UTF-8?q?test(PurchasValidator):=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dto내에서 타입 유효성 검사 - Domain 내에서 구매 단위 기준 검사 --- __tests__/domains/LottoNumberFactory.test.js | 1 - .../domain}/PurchaseValidator.test.js | 13 ++++------- .../InputPurchaseAmountValidator.test.js | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) rename __tests__/{validator => validators/domain}/PurchaseValidator.test.js (63%) create mode 100644 __tests__/validators/input/InputPurchaseAmountValidator.test.js diff --git a/__tests__/domains/LottoNumberFactory.test.js b/__tests__/domains/LottoNumberFactory.test.js index d4bf0c52d..91ff378c1 100644 --- a/__tests__/domains/LottoNumberFactory.test.js +++ b/__tests__/domains/LottoNumberFactory.test.js @@ -14,7 +14,6 @@ describe('LottoNumberFactory 테스트', () => { }); // 생성 테스트 describe('메서드 getLottoNumber 에러테스트', () => { - console.log(MIN_RANGE, 'asd'); test.each([[MIN_RANGE - 1], [MAX_RANGE + 1]])('❌에러 테스트(%s) throw Error %s', (test) => { expect(() => LottoNumberFactory.getLottoNumber(test)).toThrow('[ERROR]'); }); // 실패테스트 diff --git a/__tests__/validator/PurchaseValidator.test.js b/__tests__/validators/domain/PurchaseValidator.test.js similarity index 63% rename from __tests__/validator/PurchaseValidator.test.js rename to __tests__/validators/domain/PurchaseValidator.test.js index fb53f3b01..f958d648c 100644 --- a/__tests__/validator/PurchaseValidator.test.js +++ b/__tests__/validators/domain/PurchaseValidator.test.js @@ -1,16 +1,11 @@ -import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; -import PurchaseValidator from '../../src/validator/PurchaseValidator.js'; +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import PurchaseValidator from '../../../src/validators/domain/PurchaseValidator.js'; describe('PurchaseValidator 클래스 테스트', () => { describe('구매금액 유효성 검사 ⭕실패 테스트', () => { test.each([ - // 숫자변환검사 - ['1,000', ERROR_MESSAGES.FORMAT_NOT_NUM], - // 정수검사 - ['10.5', ERROR_MESSAGES.INTEGER], - // 나머지 테스트 - [-1500, ERROR_MESSAGES.PURCHASE_UNIT], - [900, ERROR_MESSAGES.PURCHASE_UNIT], + [999, ERROR_MESSAGES.PURCHASE_LESS], + [1500, ERROR_MESSAGES.PURCHASE_UNIT], ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { expect(() => PurchaseValidator.validate(amount)).toThrow(errorMessage); diff --git a/__tests__/validators/input/InputPurchaseAmountValidator.test.js b/__tests__/validators/input/InputPurchaseAmountValidator.test.js new file mode 100644 index 000000000..4c123c309 --- /dev/null +++ b/__tests__/validators/input/InputPurchaseAmountValidator.test.js @@ -0,0 +1,23 @@ +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import InputPurchaseAmountValidator from '../../../src/validators/input/InputPurchaseAmountValidator.js'; + +describe('InputPurchaseAmountValidator.test 클래스 테스트', () => { + describe('구매금액 유효성 검사 ⭕실패 테스트', () => { + test.each([ + [NaN, ERROR_MESSAGES.FORMAT_NOT_NUM], + [undefined, ERROR_MESSAGES.FORMAT_NOT_NUM], + [null, ERROR_MESSAGES.FORMAT_NOT_NUM], + ['iftype', ERROR_MESSAGES.FORMAT_NOT_NUM], + + [1000.5, ERROR_MESSAGES.INTEGER], + [0.5, ERROR_MESSAGES.INTEGER], + ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { + expect(() => InputPurchaseAmountValidator.validate(amount)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('구매금액 유효성 검사 ⭕성공테스트', () => { + test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { + expect(() => InputPurchaseAmountValidator.validate(amount)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 From 53338770bcf3bc4cf4acdf3be0907b23d593b904 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 19:38:36 +0900 Subject: [PATCH 093/109] =?UTF-8?q?test(LottoWinningResult):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1등과 2등의 결과값 테스트 - 수익률 테스트 추가 --- __tests__/domains/LottoWinningResult.test.js | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 __tests__/domains/LottoWinningResult.test.js diff --git a/__tests__/domains/LottoWinningResult.test.js b/__tests__/domains/LottoWinningResult.test.js new file mode 100644 index 000000000..39377157b --- /dev/null +++ b/__tests__/domains/LottoWinningResult.test.js @@ -0,0 +1,51 @@ +import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; +import LottoWinningResult from '../../src/domains/LottoWinningResult.js'; + +class MockLotto { + constructor(numbers) { + this.numbers = numbers; + } + hasLottoNumber(number) { + return this.numbers.includes(number); + } + countNumbers(numbers) { + return numbers.filter((num) => this.numbers.includes(num)).length; + } +} +describe('LottoWinningFactory 테스트', () => { + describe('createWinningLotto(numbers) 테스트', () => { + const resulFst = { FIRST: 1, SECOND: 0, THIRD: 0, FOURTH: 0, FIFTH: 0, OTHER: 0 }; + const resulSec = { FIRST: 0, SECOND: 1, THIRD: 0, FOURTH: 0, FIFTH: 0, OTHER: 0 }; + test.each([ + [ + [new MockLotto([1, 2, 3, 4, 5, 6])], + [1, 2, 3, 4, 5, 6], + 7, + { winningStats: resulFst, totalWinningAmount: LOTTO_SETTING.PRIZES.FIRST }, + , + ], + [ + [new MockLotto([1, 2, 3, 4, 5, 7])], + [1, 2, 3, 4, 5, 6], + 7, + { winningStats: resulSec, totalWinningAmount: LOTTO_SETTING.PRIZES.SECOND }, + , + ], + ])( + 'getWinningStats(lottos, winnintLottos, bonusLotto) 테스트 %s throw Error %s', + (lottos, winnintLottos, bonusLotto, Object) => { + expect(LottoWinningResult.getWinningStats(lottos, winnintLottos, bonusLotto)).toEqual( + Object, + ); + }, + ); // 성공 테스트 + }); // 생성 테스트 + describe('getWinningRate(purchaseAmount, totalWinningAmount)테스트', () => { + test.each([ + [8000, 5000, '62.5'], + [12000, 7000, '58.3'], + ])('getWinningRate(%s, %s) 반환 값 %s', (purchaseAmount, totalWinningAmount, result) => { + expect(LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount)).toBe(result); + }); // 실패테스트 + }); // 생성 테스트 +}); // 설명 From ea54a6a68028c650cedd0ff724b649f6822e028c Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 19:40:01 +0900 Subject: [PATCH 094/109] =?UTF-8?q?test(LottoWinningFactory):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 배열을 생성하는지 테스트 - 같은 인스턴스를 참조하는지 단위테스트 --- __tests__/domains/LottoWinningFactory.test.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 __tests__/domains/LottoWinningFactory.test.js diff --git a/__tests__/domains/LottoWinningFactory.test.js b/__tests__/domains/LottoWinningFactory.test.js new file mode 100644 index 000000000..ae079d6ac --- /dev/null +++ b/__tests__/domains/LottoWinningFactory.test.js @@ -0,0 +1,40 @@ +import LottoWinningFactory from '../../src/domains/LottoWinningFactory.js'; + +class MockLottoNumber { + constructor(number) { + this.number = number; + } + getNumber() { + return this.number; + } +} +const mockLottoNumberFactory = { + map: new Map(), + getLottoNumber: jest.fn(function (number) { + if (!this.map.has(number)) { + this.map.set(number, new MockLottoNumber(number)); + } + return this.map.get(number); + }), +}; +const factory = new LottoWinningFactory(mockLottoNumberFactory); +const result = [1, 2, 3, 4, 5, 6]; + +describe('LottoWinningFactory 테스트', () => { + describe('createWinningLotto(numbers) 테스트', () => { + test('', () => { + const winningNumbers = factory.createWinningLotto(result); + const winningNumbersCopy = factory.createWinningLotto(result); + winningNumbers.forEach((winningNumber, index) => { + expect(winningNumber.getNumber()).toBe(result[index]); + }); // forEach + expect(winningNumbers[0]).toBe(winningNumbersCopy[0]); + }); // test + }); // 생성 테스트 + describe('createBonusLotto(number) 테스트', () => { + test('', () => { + const bonusNumber = factory.createBonusLotto(result[0]); + expect(bonusNumber.getNumber()).toBe(result[0]); + }); // test + }); // 생성 테스트 +}); // LottoNumberFactory 테스트 From 0427adf6a367782a65f43e93f6d6b1254a961f72 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 20:41:15 +0900 Subject: [PATCH 095/109] =?UTF-8?q?test(BounusNumberValidator):=20?= =?UTF-8?q?=EB=B3=B4=EB=84=88=EC=8A=A4=EB=84=98=EB=B2=84=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/BonusNumberValidator.test.js | 30 +++++++++++++++++++ .../input/InputBonusNumberValidator.test.js | 19 ++++++++++++ src/validators/domain/BonusNumberValidator.js | 4 +-- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 __tests__/validators/domain/BonusNumberValidator.test.js create mode 100644 __tests__/validators/input/InputBonusNumberValidator.test.js diff --git a/__tests__/validators/domain/BonusNumberValidator.test.js b/__tests__/validators/domain/BonusNumberValidator.test.js new file mode 100644 index 000000000..0f15b31e9 --- /dev/null +++ b/__tests__/validators/domain/BonusNumberValidator.test.js @@ -0,0 +1,30 @@ +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import BonusNumberValidator from '../../../src/validators/domain/BonusNumberValidator.js'; +class MockLottoNumber { + constructor(number) { + this.number = number; + } + getNumber() { + return this.number; + } +} +const createMockLost = (numbers) => numbers.map((n) => new MockLottoNumber(n)); +const testLottoNumber = new MockLottoNumber(0); +describe('BonusNumberValidator 클래스 테스트', () => { + describe('구매금액 유효성 검사 실패 테스트', () => { + const mockLottoList = createMockLost([1, 2, 3, 4, 5, 6]); + test.each([ + [mockLottoList, testLottoNumber, ERROR_MESSAGES.LOTTO_RANGE], + [mockLottoList, mockLottoList[1], ERROR_MESSAGES.LOTTO_DUPLICATE], + ])('❌ validate 테스트 %s %sthrow Error %s', (numbers, number, errorMessage) => { + console.log('object', testLottoNumber.getNumber()); + expect(() => BonusNumberValidator.validate(numbers, number)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('구매금액 유효성 검사 ⭕성공테스트', () => { + test.each([[[1, 2, 3, 4, 5, 6], 7]])('통과해야됨 %s', (numbers, number) => { + const newNum = new MockLottoNumber(number); + expect(() => BonusNumberValidator.validate(numbers, newNum)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 diff --git a/__tests__/validators/input/InputBonusNumberValidator.test.js b/__tests__/validators/input/InputBonusNumberValidator.test.js new file mode 100644 index 000000000..d072d3e3b --- /dev/null +++ b/__tests__/validators/input/InputBonusNumberValidator.test.js @@ -0,0 +1,19 @@ +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import InputBonusNumberValidator from '../../../src/validators/input/InputBonusNumberValidator.js'; + +describe('InputBonusNumberValidator 클래스 테스트', () => { + describe('구매금액 유효성 검사 ⭕실패 테스트', () => { + test.each([ + ['', ERROR_MESSAGES.FORMAT_NOT_NUM], + + [10.5, ERROR_MESSAGES.INTEGER], + ])('❌ validate 테스트 %s throw Error %s', (bonusNumber, errorMessage) => { + expect(() => InputBonusNumberValidator.validate(bonusNumber)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('보너스넘버 유효성 검사 ⭕성공테스트', () => { + test.each([[1000], [15000]])('통과해야됨 %s', (bonusNumber) => { + expect(() => InputBonusNumberValidator.validate(bonusNumber)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 diff --git a/src/validators/domain/BonusNumberValidator.js b/src/validators/domain/BonusNumberValidator.js index 53a0e4c90..f1ea5a4d6 100644 --- a/src/validators/domain/BonusNumberValidator.js +++ b/src/validators/domain/BonusNumberValidator.js @@ -6,7 +6,7 @@ class BonusNumberValidator { if (!BonusNumberValidator.#isInRange(bonusLottoNumber)) { throw new Error(ERROR_MESSAGES.LOTTO_RANGE); } - if (!BonusNumberValidator.#isDuplicate(winningLottoNumbers, bonusLottoNumber)) { + if (BonusNumberValidator.#isDuplicate(winningLottoNumbers, bonusLottoNumber)) { throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); } } @@ -18,7 +18,7 @@ class BonusNumberValidator { } static #isDuplicate(winningLottoNumbers, bonusLottoNumber) { - return !winningLottoNumbers.includes(bonusLottoNumber); + return winningLottoNumbers.includes(bonusLottoNumber); } } From 0c18037a5b531af1addf341ff59e7fc023e212bd Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 20:52:00 +0900 Subject: [PATCH 096/109] =?UTF-8?q?test(WinningNumberValidator):=20?= =?UTF-8?q?=EB=8B=B9=EC=B2=A8=EB=B2=88=ED=98=B8=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/WinningNumbersValidator.test.js | 23 +++++++++++++++++++ .../InputWinningNumbersValidator.test.js | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 __tests__/validators/domain/WinningNumbersValidator.test.js create mode 100644 __tests__/validators/input/InputWinningNumbersValidator.test.js diff --git a/__tests__/validators/domain/WinningNumbersValidator.test.js b/__tests__/validators/domain/WinningNumbersValidator.test.js new file mode 100644 index 000000000..7e923df01 --- /dev/null +++ b/__tests__/validators/domain/WinningNumbersValidator.test.js @@ -0,0 +1,23 @@ +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import WinningNumbersValidator from '../../../src/validators/domain/WinningNumbersValidator.js'; + +describe('WinningNumbersValidator 클래스 테스트', () => { + describe('당첨번호 유효성 검사 ⭕실패 테스트', () => { + test.each([ + [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], + [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], + + [[1, 2, 3, 4, 5, 5], ERROR_MESSAGES.LOTTO_DUPLICATE], + + [[0, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], + [[1, 2, 3, 4, 5, 46], ERROR_MESSAGES.LOTTO_RANGE], + ])('❌ validate 테스트 %s throw Error %s', (numbers, errorMessage) => { + expect(() => WinningNumbersValidator.validate(numbers)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('당첨번호 유효성 검사 ⭕성공테스트', () => { + test.each([[[1, 2, 3, 4, 5, 45]]])('통과해야됨 %s', (numbers) => { + expect(() => WinningNumbersValidator.validate(numbers)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 diff --git a/__tests__/validators/input/InputWinningNumbersValidator.test.js b/__tests__/validators/input/InputWinningNumbersValidator.test.js new file mode 100644 index 000000000..77bb7d12f --- /dev/null +++ b/__tests__/validators/input/InputWinningNumbersValidator.test.js @@ -0,0 +1,19 @@ +import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; +import InputWinningNumberValidator from '../../../src/validators/input/InputWinningNumberValidator.js'; +describe('InputWinningNumberValidator 클래스 테스트', () => { + describe('당첨번호 유효성 검사 ⭕실패 테스트', () => { + test.each([ + [undefined, ERROR_MESSAGES.NOT_ARRAY], + ['', ERROR_MESSAGES.NOT_ARRAY], + + [[1.5], ERROR_MESSAGES.INTEGER], + ])('❌ validate 테스트 %s throw Error %s', (numbers, errorMessage) => { + expect(() => InputWinningNumberValidator.validate(numbers)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('당첨번호 유효성 검사 ⭕성공테스트', () => { + test.each([[[1, 2, 3, 4, 5, 45]]])('통과해야됨 %s', (numbers) => { + expect(() => InputWinningNumberValidator.validate(numbers)).not.toThrow(); + }); // 성공테스트 + }); //describe +}); //describe 클래스 테스트 From 09476c0fe16933713b74f6c90556f31f7a98debe Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 21:08:59 +0900 Subject: [PATCH 097/109] =?UTF-8?q?chore:=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/errorMessages.js | 16 +++++++++------- src/views/LottoInputView.js | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 6aaab3782..edadf781f 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,19 +1,21 @@ +import LOTTO_SETTING from './lottoSetting.js'; + const PREFIX = '[ERROR] '; +const format = (unit) => new Intl.NumberFormat().format(unit); +const { PURCHASE_UNIT, MAX_QUANTITY, MAX_LANGE, MIN_RANGE } = LOTTO_SETTING; const ERROR_MESSAGES = Object.freeze({ - POSITVE: `${PREFIX}입력 값이 양수여야 합니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, BLANK: `${PREFIX}입력 값이 비었습니다`, - FORMAT_NOT_NUM: `${PREFIX} 숫자를 입력해야 합니다`, + FORMAT_NOT_NUM: `${PREFIX}숫자를 입력해야 합니다`, - PURCHASE_UNIT: `${PREFIX}구매 금액은 1,000원 단위로 입력해야됩니다`, - PURCHASE_LESS: `${PREFIX}구매 금액은 1,000원 보다 커야합니다`, + PURCHASE_UNIT: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, + PURCHASE_LESS: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, - LOTTO_NOT_NUMBER: `${PREFIX}로또 번호는 숫자만 추가 할 수 있습니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, - LOTTO_QUANTITY: `${PREFIX}로또 번호는 정의된 만큼 생성되어야 합니다 `, - LOTTO_RANGE: `${PREFIX}로또 번호는 1-45여야 합니다`, + LOTTO_QUANTITY: `${PREFIX}로또 번호는 ${MAX_QUANTITY} 만큼 생성되어야 합니다 `, + LOTTO_RANGE: `${PREFIX}로또 번호는 ${MIN_RANGE}부터${MAX_LANGE}여야 합니다`, }); export default ERROR_MESSAGES; diff --git a/src/views/LottoInputView.js b/src/views/LottoInputView.js index 9c03c66ac..cba4b40db 100644 --- a/src/views/LottoInputView.js +++ b/src/views/LottoInputView.js @@ -1,10 +1,10 @@ import { Console } from '@woowacourse/mission-utils'; -const INFO_MEESAGE = { +const INFO_MEESAGE = Object.freeze({ INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', INFO_WINNING_NUMBERS: '\n당첨 번호를 입력해 주세요.\n', INFO_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n', -}; +}); class LottoInputView { async readPurchaseAmount() { From 0470352a6cb3e9da41396ebba8df27d35bb4c288 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 21:22:07 +0900 Subject: [PATCH 098/109] =?UTF-8?q?chore(Service):=20=ED=94=84=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B9=97=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/LottoPurchaseService.js | 15 +++++++++------ src/services/WinningResultService.js | 21 ++++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index 34971388d..cfa134f48 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -3,22 +3,25 @@ import PurchasedLottosDto from '../dtos/responseDto/PurchasedLottosDto.js'; import PurchaseValidator from '../validators/domain/PurchaseValidator.js'; class LottoPurchaseService { + #lottoStore; + #lottoRepository; + constructor(lottoStore, lottoRepository) { - this.lottoStore = lottoStore; - this.lottoRepository = lottoRepository; + this.#lottoStore = lottoStore; + this.#lottoRepository = lottoRepository; } savePurchaseAmount(requestDTO) { const { purchaseAmount } = requestDTO; PurchaseValidator.validate(purchaseAmount); - this.lottoRepository.save('admin', { purchaseAmount }); + this.#lottoRepository.save('admin', { purchaseAmount }); } getPurchasedLottos() { - const { purchaseAmount } = this.lottoRepository.findAll('admin'); + const { purchaseAmount } = this.#lottoRepository.findAll('admin'); const lottoCount = purchaseAmount / LOTTO_SETTING.PURCHASE_UNIT; - const lottos = this.lottoStore.buyLotto(lottoCount); - this.lottoRepository.save('admin', { lottos }); + const lottos = this.#lottoStore.buyLotto(lottoCount); + this.#lottoRepository.save('admin', { lottos }); return new PurchasedLottosDto({ lottos }); } } diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index f5fe9d21a..322aea315 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -4,29 +4,32 @@ import LottoWinningResult from '../domains/LottoWinningResult.js'; import WinningResultDto from '../dtos/responseDto/WinningResultDto.js'; class WinningResultService { + #winningFactory; + #lottoRepository; + constructor(winningFactory, lottoRepository) { - this.winningFactory = winningFactory; - this.lottoRepository = lottoRepository; + this.#winningFactory = winningFactory; + this.#lottoRepository = lottoRepository; } saveWinningNumbers(requestDTO) { const { winningNumbers } = requestDTO; WinningNumbersValidator.validate(winningNumbers); - const winningLotto = this.winningFactory.createWinningLotto(winningNumbers); - this.lottoRepository.save('admin', { winningLotto }); + const winningLotto = this.#winningFactory.createWinningLotto(winningNumbers); + this.#lottoRepository.save('admin', { winningLotto }); } saveBonusNumber(requestDTO) { const { bonusNumber } = requestDTO; - const { winningLotto } = this.lottoRepository.findAll('admin'); + const { winningLotto } = this.#lottoRepository.findAll('admin'); // 객체끼리의 비교를 위해 먼저 생성 - const bonusLotto = this.winningFactory.createBonusLotto(bonusNumber); + const bonusLotto = this.#winningFactory.createBonusLotto(bonusNumber); BonusNumberValidator.validate(winningLotto, bonusLotto); - this.lottoRepository.save('admin', { bonusLotto }); + this.#lottoRepository.save('admin', { bonusLotto }); } getWinningResult() { - const db = this.lottoRepository.findAll('admin'); + const db = this.#lottoRepository.findAll('admin'); const { purchaseAmount, lottos, winningLotto, bonusLotto } = db; const { winningStats, totalWinningAmount } = LottoWinningResult.getWinningStats( lottos, @@ -34,7 +37,7 @@ class WinningResultService { bonusLotto, ); const winningRate = LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount); - this.lottoRepository.save('admin', { winningStats, winningRate }); + this.#lottoRepository.save('admin', { winningStats, winningRate }); return new WinningResultDto({ winningStats, winningRate }); } } From be376309786d3bc8e0b23446c66e64ecccdb1d02 Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 21:34:12 +0900 Subject: [PATCH 099/109] =?UTF-8?q?feat(LottoinputView):=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/LottoInputView.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/views/LottoInputView.js b/src/views/LottoInputView.js index cba4b40db..25f3c8b4e 100644 --- a/src/views/LottoInputView.js +++ b/src/views/LottoInputView.js @@ -1,4 +1,5 @@ import { Console } from '@woowacourse/mission-utils'; +import ERROR_MESSAGES from '../constants/errorMessages.js'; const INFO_MEESAGE = Object.freeze({ INFO_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', @@ -9,17 +10,22 @@ const INFO_MEESAGE = Object.freeze({ class LottoInputView { async readPurchaseAmount() { const purchaseAmount = await Console.readLineAsync(INFO_MEESAGE.INFO_PURCHASE_AMOUNT); - return purchaseAmount; + return LottoInputView.#checkBlank(purchaseAmount); } async readWinningNumbers() { const winningNumbers = await Console.readLineAsync(INFO_MEESAGE.INFO_WINNING_NUMBERS); - return winningNumbers; + return LottoInputView.#checkBlank(winningNumbers); } async readBonusNumber() { const bonusNumber = await Console.readLineAsync(INFO_MEESAGE.INFO_BONUS_NUMBER); - return bonusNumber; + return LottoInputView.#checkBlank(bonusNumber); + } + + static #checkBlank(input) { + if (input.trim() === '') throw new Error(ERROR_MESSAGES.BLANK); + return input; } } From b65f3bf6b92aff7f1e03fe5fb6f76d28706cafec Mon Sep 17 00:00:00 2001 From: iftype Date: Sun, 2 Nov 2025 21:35:32 +0900 Subject: [PATCH 100/109] =?UTF-8?q?chore:=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/errorMessages.js | 2 +- src/controllers/LottoController.js | 1 - src/domains/LottoWinningResult.js | 10 +++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index edadf781f..cc35aa89a 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,6 +1,6 @@ import LOTTO_SETTING from './lottoSetting.js'; -const PREFIX = '[ERROR] '; +const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); const { PURCHASE_UNIT, MAX_QUANTITY, MAX_LANGE, MIN_RANGE } = LOTTO_SETTING; diff --git a/src/controllers/LottoController.js b/src/controllers/LottoController.js index 4de620bd3..1fc94cdaf 100644 --- a/src/controllers/LottoController.js +++ b/src/controllers/LottoController.js @@ -31,7 +31,6 @@ class LottoController { } } - // 메서드명 고민해보기 async #processWinningResult() { try { const winningNumbers = await this.#lottoInputView.readWinningNumbers(); diff --git a/src/domains/LottoWinningResult.js b/src/domains/LottoWinningResult.js index 6d70c0289..411649c99 100644 --- a/src/domains/LottoWinningResult.js +++ b/src/domains/LottoWinningResult.js @@ -1,7 +1,7 @@ import LOTTO_SETTING from '../constants/lottoSetting.js'; class LottoWinningResult { - // 당첨 카운트 [일치갯수, 보너스여부] 필수 1 + // 우승자 랭킹 카운트와 총 상금 반환 static getWinningStats(lottos, winnintLottos, bonusLotto) { const matchs = LottoWinningResult.#getWinningMatch(lottos, winnintLottos, bonusLotto); const ranks = LottoWinningResult.#getWinningRank(matchs); @@ -18,7 +18,7 @@ class LottoWinningResult { return ((totalWinningAmount / purchaseAmount) * 100).toFixed(1); } - // 당첨 카운트 [일치갯수, 보너스여부] 필수 1 + // #당첨 카운트 반환 형식 [winning:일치 갯수, bonus: 보너스여부] static #getWinningMatch(lottos, winnintLottos, bonusLotto) { return lottos.map((lotto) => ({ winning: lotto.countNumbers(winnintLottos), @@ -26,7 +26,7 @@ class LottoWinningResult { })); } - // 상금 매칭 배열리턴 [ 번호일치갯수, 보너스여부]로 판별 필수 2 + // #순위 정하는 메서드 static #getWinningRank(winningStats) { return winningStats.map(({ winning, bonus }) => { if (winning === 6) return { rank: 'FIRST' }; @@ -38,7 +38,7 @@ class LottoWinningResult { }); } - // 랭크로 계산로직 카운트 반환해야할 값 + // #랭크로 총 순위 카운트 static #getWinningCount(winningRank) { const count = { FIRST: 0, SECOND: 0, THIRD: 0, FOURTH: 0, FIFTH: 0, OTHER: 0 }; winningRank.forEach(({ rank }) => { @@ -47,7 +47,7 @@ class LottoWinningResult { return count; } - // 랭크로 계산로직 수익 + // #랭크로 총 상금 계산 static #getWinningTotalGains(winningRank) { const { PRIZES } = LOTTO_SETTING; return winningRank.reduce((total, { rank }) => total + PRIZES[rank], 0); From e31fcda91aa18d7afd00c75a64239e2d7f17ce86 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 10:10:08 +0900 Subject: [PATCH 101/109] =?UTF-8?q?refactor(LottoNumber):=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EA=B2=80=EC=A6=9D=EC=9D=98=20=EC=B1=85=EC=9E=84?= =?UTF-8?q?=EC=9D=84=20=EC=9C=A0=EC=9D=BC=ED=95=98=EA=B2=8C=20=EA=B0=96?= =?UTF-8?q?=EA=B2=8C=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Validator: 범위 검사를 제거, LottoNumber 을 신뢰 - errorMessage: 상수 메세지 템플릿으로 변경 --- __tests__/domains/LottoNumber.test.js | 3 +-- __tests__/domains/LottoNumberFactory.test.js | 4 ++-- .../validators/domain/BonusNumberValidator.test.js | 14 +++++++------- .../domain/WinningNumbersValidator.test.js | 3 --- src/constants/errorMessages.js | 5 +++-- src/constants/lottoSetting.js | 2 -- src/domains/LottoNumber.js | 12 ++++++++---- src/domains/LottoNumberFactory.js | 6 +++--- src/utils/RandomPicker.js | 4 +++- src/validators/domain/BonusNumberValidator.js | 10 ---------- src/validators/domain/WinningNumbersValidator.js | 8 -------- 11 files changed, 27 insertions(+), 44 deletions(-) diff --git a/__tests__/domains/LottoNumber.test.js b/__tests__/domains/LottoNumber.test.js index cf2f69be1..a30bee740 100644 --- a/__tests__/domains/LottoNumber.test.js +++ b/__tests__/domains/LottoNumber.test.js @@ -1,7 +1,6 @@ -import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; import LottoNumber from '../../src/domains/LottoNumber.js'; -const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; +const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); describe('LottoNumber 클래스 테스트', () => { describe('생성자 에러 검사', () => { test(`최소범위 테스트 ${MIN_RANGE - 1}`, () => { diff --git a/__tests__/domains/LottoNumberFactory.test.js b/__tests__/domains/LottoNumberFactory.test.js index 91ff378c1..f5f339fbf 100644 --- a/__tests__/domains/LottoNumberFactory.test.js +++ b/__tests__/domains/LottoNumberFactory.test.js @@ -1,8 +1,8 @@ import LottoNumberFactory from '../../src/domains/LottoNumberFactory.js'; import LottoNumberFactoryCopy from '../../src/domains/LottoNumberFactory.js'; -import LOTTO_SETTING from '../../src/constants/lottoSetting.js'; +import LottoNumber from '../../src/domains/LottoNumber.js'; -const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; +const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); describe('LottoNumberFactory 테스트', () => { beforeEach(() => {}); diff --git a/__tests__/validators/domain/BonusNumberValidator.test.js b/__tests__/validators/domain/BonusNumberValidator.test.js index 0f15b31e9..0bf880536 100644 --- a/__tests__/validators/domain/BonusNumberValidator.test.js +++ b/__tests__/validators/domain/BonusNumberValidator.test.js @@ -13,13 +13,13 @@ const testLottoNumber = new MockLottoNumber(0); describe('BonusNumberValidator 클래스 테스트', () => { describe('구매금액 유효성 검사 실패 테스트', () => { const mockLottoList = createMockLost([1, 2, 3, 4, 5, 6]); - test.each([ - [mockLottoList, testLottoNumber, ERROR_MESSAGES.LOTTO_RANGE], - [mockLottoList, mockLottoList[1], ERROR_MESSAGES.LOTTO_DUPLICATE], - ])('❌ validate 테스트 %s %sthrow Error %s', (numbers, number, errorMessage) => { - console.log('object', testLottoNumber.getNumber()); - expect(() => BonusNumberValidator.validate(numbers, number)).toThrow(errorMessage); - }); // 실패테스트 + test.each([[mockLottoList, mockLottoList[1], ERROR_MESSAGES.LOTTO_DUPLICATE]])( + '❌ validate 테스트 %s %sthrow Error %s', + (numbers, number, errorMessage) => { + console.log('object', testLottoNumber.getNumber()); + expect(() => BonusNumberValidator.validate(numbers, number)).toThrow(errorMessage); + }, + ); // 실패테스트 }); //describe describe('구매금액 유효성 검사 ⭕성공테스트', () => { test.each([[[1, 2, 3, 4, 5, 6], 7]])('통과해야됨 %s', (numbers, number) => { diff --git a/__tests__/validators/domain/WinningNumbersValidator.test.js b/__tests__/validators/domain/WinningNumbersValidator.test.js index 7e923df01..269697db8 100644 --- a/__tests__/validators/domain/WinningNumbersValidator.test.js +++ b/__tests__/validators/domain/WinningNumbersValidator.test.js @@ -8,9 +8,6 @@ describe('WinningNumbersValidator 클래스 테스트', () => { [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], [[1, 2, 3, 4, 5, 5], ERROR_MESSAGES.LOTTO_DUPLICATE], - - [[0, 2, 3, 4, 5, 6], ERROR_MESSAGES.LOTTO_RANGE], - [[1, 2, 3, 4, 5, 46], ERROR_MESSAGES.LOTTO_RANGE], ])('❌ validate 테스트 %s throw Error %s', (numbers, errorMessage) => { expect(() => WinningNumbersValidator.validate(numbers)).toThrow(errorMessage); }); // 실패테스트 diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index cc35aa89a..8e3cc96ef 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -2,7 +2,7 @@ import LOTTO_SETTING from './lottoSetting.js'; const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); -const { PURCHASE_UNIT, MAX_QUANTITY, MAX_LANGE, MIN_RANGE } = LOTTO_SETTING; +const { PURCHASE_UNIT, MAX_QUANTITY } = LOTTO_SETTING; const ERROR_MESSAGES = Object.freeze({ INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, @@ -15,7 +15,8 @@ const ERROR_MESSAGES = Object.freeze({ LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, LOTTO_QUANTITY: `${PREFIX}로또 번호는 ${MAX_QUANTITY} 만큼 생성되어야 합니다 `, - LOTTO_RANGE: `${PREFIX}로또 번호는 ${MIN_RANGE}부터${MAX_LANGE}여야 합니다`, + LOTTO_RANGE: (MIN_RANGE, MAX_RANGE) => + `${PREFIX}로또 번호는 ${MIN_RANGE}부터 ${MAX_RANGE}여야 합니다`, }); export default ERROR_MESSAGES; diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 6d05d00f3..1a993800f 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -8,8 +8,6 @@ const LOTTO_SETTING = Object.freeze({ OTHER: 0, }), MAX_QUANTITY: 6, - MIN_RANGE: 1, - MAX_RANGE: 45, PURCHASE_UNIT: 1000, }); diff --git a/src/domains/LottoNumber.js b/src/domains/LottoNumber.js index afcb976ef..125646ace 100644 --- a/src/domains/LottoNumber.js +++ b/src/domains/LottoNumber.js @@ -1,7 +1,8 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; class LottoNumber { + static #MIN_RANGE = 1; + static #MAX_RANGE = 45; #number; constructor(number) { @@ -14,7 +15,7 @@ class LottoNumber { throw new Error(ERROR_MESSAGES.INTEGER); } if (!this.#isInRange(number)) { - throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + throw new Error(ERROR_MESSAGES.LOTTO_RANGE(LottoNumber.#MIN_RANGE, LottoNumber.#MAX_RANGE)); } } @@ -23,8 +24,11 @@ class LottoNumber { } #isInRange(number) { - const { MAX_RANGE, MIN_RANGE } = LOTTO_SETTING; - return MIN_RANGE <= number && number <= MAX_RANGE; + return LottoNumber.#MIN_RANGE <= number && number <= LottoNumber.#MAX_RANGE; + } + + static getRange() { + return { MIN_RANGE: LottoNumber.#MIN_RANGE, MAX_RANGE: LottoNumber.#MAX_RANGE }; } getNumber() { diff --git a/src/domains/LottoNumberFactory.js b/src/domains/LottoNumberFactory.js index 6f1beef90..499228045 100644 --- a/src/domains/LottoNumberFactory.js +++ b/src/domains/LottoNumberFactory.js @@ -1,5 +1,4 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; import LottoNumber from './LottoNumber.js'; class LottoNumberFactory { @@ -10,7 +9,7 @@ class LottoNumberFactory { } #initNumberMap() { - const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; + const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); for (let number = MIN_RANGE; number <= MAX_RANGE; number += 1) { this.#numberMap.set(number, new LottoNumber(number)); } @@ -18,7 +17,8 @@ class LottoNumberFactory { getLottoNumber(number) { if (!this.#numberMap.has(number)) { - throw new Error(ERROR_MESSAGES.LOTTO_RANGE); + const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); + throw new Error(ERROR_MESSAGES.LOTTO_RANGE(MIN_RANGE, MAX_RANGE)); } return this.#numberMap.get(number); } diff --git a/src/utils/RandomPicker.js b/src/utils/RandomPicker.js index e74bf5202..57ff7c48b 100644 --- a/src/utils/RandomPicker.js +++ b/src/utils/RandomPicker.js @@ -1,9 +1,11 @@ import { Random } from '@woowacourse/mission-utils'; import LOTTO_SETTING from '../constants/lottoSetting.js'; +import LottoNumber from '../domains/LottoNumber.js'; class RandomPicker { pick() { - const { MIN_RANGE, MAX_RANGE, MAX_QUANTITY } = LOTTO_SETTING; + const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); + const { MAX_QUANTITY } = LOTTO_SETTING; return Random.pickUniqueNumbersInRange(MIN_RANGE, MAX_RANGE, MAX_QUANTITY); } } diff --git a/src/validators/domain/BonusNumberValidator.js b/src/validators/domain/BonusNumberValidator.js index f1ea5a4d6..596e18873 100644 --- a/src/validators/domain/BonusNumberValidator.js +++ b/src/validators/domain/BonusNumberValidator.js @@ -1,22 +1,12 @@ import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LOTTO_SETTING from '../../constants/lottoSetting.js'; class BonusNumberValidator { static validate(winningLottoNumbers, bonusLottoNumber) { - if (!BonusNumberValidator.#isInRange(bonusLottoNumber)) { - throw new Error(ERROR_MESSAGES.LOTTO_RANGE); - } if (BonusNumberValidator.#isDuplicate(winningLottoNumbers, bonusLottoNumber)) { throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); } } - static #isInRange(bonusLottoNumber) { - const bonusNumber = bonusLottoNumber.getNumber(); - const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; - return MIN_RANGE <= bonusNumber && bonusNumber <= MAX_RANGE; - } - static #isDuplicate(winningLottoNumbers, bonusLottoNumber) { return winningLottoNumbers.includes(bonusLottoNumber); } diff --git a/src/validators/domain/WinningNumbersValidator.js b/src/validators/domain/WinningNumbersValidator.js index 36e1dc770..bc2f266ea 100644 --- a/src/validators/domain/WinningNumbersValidator.js +++ b/src/validators/domain/WinningNumbersValidator.js @@ -9,14 +9,6 @@ class WinningNumbersValidator { if (!WinningNumbersValidator.#isDuplicate(winningNumbers)) { throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); } - if (!WinningNumbersValidator.#isInRange(winningNumbers)) { - throw new Error(ERROR_MESSAGES.LOTTO_RANGE); - } - } - - static #isInRange(winningNumbers) { - const { MIN_RANGE, MAX_RANGE } = LOTTO_SETTING; - return winningNumbers.every((number) => MIN_RANGE <= number && number <= MAX_RANGE); } static #isDuplicate(winningNumbers) { From 71a040ead2de562ea1d38957d1f0c0ff6929182d Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 11:18:59 +0900 Subject: [PATCH 102/109] =?UTF-8?q?refactor(Lotto):=20=EC=88=98=EB=9F=89?= =?UTF-8?q?=EC=9D=98=20=EC=A1=B0=EA=B1=B4=EC=9D=84=20=EB=A1=9C=EB=98=90?= =?UTF-8?q?=EC=97=90=EA=B2=8C=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Validator: 범위 검사를 제거 - Lotto 생성에서 수량 신뢰 - errorMessage: 상수 메세지 템플릿으로 변경 --- __tests__/domains/Lotto.test.js | 11 +++---- __tests__/domains/LottoWinningFactory.test.js | 3 -- .../domain/BonusNumberValidator.test.js | 30 ------------------- .../domain/WinningNumbersValidator.test.js | 20 ------------- src/constants/errorMessages.js | 4 +-- src/constants/lottoSetting.js | 1 - src/domains/Lotto.js | 13 +++++--- src/domains/LottoWinningFactory.js | 5 +++- src/services/WinningResultService.js | 2 -- src/utils/RandomPicker.js | 4 +-- src/validators/domain/BonusNumberValidator.js | 2 +- .../domain/WinningNumbersValidator.js | 24 --------------- 12 files changed, 22 insertions(+), 97 deletions(-) delete mode 100644 __tests__/validators/domain/BonusNumberValidator.test.js delete mode 100644 __tests__/validators/domain/WinningNumbersValidator.test.js delete mode 100644 src/validators/domain/WinningNumbersValidator.js diff --git a/__tests__/domains/Lotto.test.js b/__tests__/domains/Lotto.test.js index a20b7aecc..3f0fad773 100644 --- a/__tests__/domains/Lotto.test.js +++ b/__tests__/domains/Lotto.test.js @@ -2,15 +2,16 @@ import Lotto from '../../src/domains/Lotto.js'; import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; const MOCK_NUMBERS = [2, 1, 3, 4, 5, 6]; -const mockLottoNumber = (num) => ({ getNumber: () => num }); +const mockLottoNumber = (num) => ({ getNumber: () => num, hasLottoNumber: (n) => n === num }); const mockLottoArray = MOCK_NUMBERS.map((num) => mockLottoNumber(num)); const mockLotto = new Lotto(mockLottoArray); +const { MAX_QUANTITY } = Lotto.getQuan(); describe('로또 클래스 테스트', () => { describe('생성 유효성 테스트', () => { test.each([ - [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], - [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], + [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY(MAX_QUANTITY)], + [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY(MAX_QUANTITY)], ])('❌수량 에러 테스트(%s) throw Error %s', (numbers, errorMessage) => { expect(() => new Lotto(numbers)).toThrow(errorMessage); }); // 실패테스트 수량 @@ -33,9 +34,5 @@ describe('로또 클래스 테스트', () => { const test = mockLottoArray.find((obj) => obj.getNumber() === 3); expect(mockLotto.hasLottoNumber(test)).toBe(true); }); // 성공 테스트 - - test('⭕메서드 테스트 countNumbers(numbers)', () => { - expect(mockLotto.countNumbers(mockLottoArray)).toBe(6); - }); // 성공 테스트 }); }); diff --git a/__tests__/domains/LottoWinningFactory.test.js b/__tests__/domains/LottoWinningFactory.test.js index ae079d6ac..bae9a69d0 100644 --- a/__tests__/domains/LottoWinningFactory.test.js +++ b/__tests__/domains/LottoWinningFactory.test.js @@ -25,9 +25,6 @@ describe('LottoWinningFactory 테스트', () => { test('', () => { const winningNumbers = factory.createWinningLotto(result); const winningNumbersCopy = factory.createWinningLotto(result); - winningNumbers.forEach((winningNumber, index) => { - expect(winningNumber.getNumber()).toBe(result[index]); - }); // forEach expect(winningNumbers[0]).toBe(winningNumbersCopy[0]); }); // test }); // 생성 테스트 diff --git a/__tests__/validators/domain/BonusNumberValidator.test.js b/__tests__/validators/domain/BonusNumberValidator.test.js deleted file mode 100644 index 0bf880536..000000000 --- a/__tests__/validators/domain/BonusNumberValidator.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; -import BonusNumberValidator from '../../../src/validators/domain/BonusNumberValidator.js'; -class MockLottoNumber { - constructor(number) { - this.number = number; - } - getNumber() { - return this.number; - } -} -const createMockLost = (numbers) => numbers.map((n) => new MockLottoNumber(n)); -const testLottoNumber = new MockLottoNumber(0); -describe('BonusNumberValidator 클래스 테스트', () => { - describe('구매금액 유효성 검사 실패 테스트', () => { - const mockLottoList = createMockLost([1, 2, 3, 4, 5, 6]); - test.each([[mockLottoList, mockLottoList[1], ERROR_MESSAGES.LOTTO_DUPLICATE]])( - '❌ validate 테스트 %s %sthrow Error %s', - (numbers, number, errorMessage) => { - console.log('object', testLottoNumber.getNumber()); - expect(() => BonusNumberValidator.validate(numbers, number)).toThrow(errorMessage); - }, - ); // 실패테스트 - }); //describe - describe('구매금액 유효성 검사 ⭕성공테스트', () => { - test.each([[[1, 2, 3, 4, 5, 6], 7]])('통과해야됨 %s', (numbers, number) => { - const newNum = new MockLottoNumber(number); - expect(() => BonusNumberValidator.validate(numbers, newNum)).not.toThrow(); - }); // 성공테스트 - }); //describe -}); //describe 클래스 테스트 diff --git a/__tests__/validators/domain/WinningNumbersValidator.test.js b/__tests__/validators/domain/WinningNumbersValidator.test.js deleted file mode 100644 index 269697db8..000000000 --- a/__tests__/validators/domain/WinningNumbersValidator.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; -import WinningNumbersValidator from '../../../src/validators/domain/WinningNumbersValidator.js'; - -describe('WinningNumbersValidator 클래스 테스트', () => { - describe('당첨번호 유효성 검사 ⭕실패 테스트', () => { - test.each([ - [[1, 2, 3, 4, 5], ERROR_MESSAGES.LOTTO_QUANTITY], - [[1, 2, 3, 4, 5, 6, 7], ERROR_MESSAGES.LOTTO_QUANTITY], - - [[1, 2, 3, 4, 5, 5], ERROR_MESSAGES.LOTTO_DUPLICATE], - ])('❌ validate 테스트 %s throw Error %s', (numbers, errorMessage) => { - expect(() => WinningNumbersValidator.validate(numbers)).toThrow(errorMessage); - }); // 실패테스트 - }); //describe - describe('당첨번호 유효성 검사 ⭕성공테스트', () => { - test.each([[[1, 2, 3, 4, 5, 45]]])('통과해야됨 %s', (numbers) => { - expect(() => WinningNumbersValidator.validate(numbers)).not.toThrow(); - }); // 성공테스트 - }); //describe -}); //describe 클래스 테스트 diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 8e3cc96ef..27ab057fc 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -2,7 +2,7 @@ import LOTTO_SETTING from './lottoSetting.js'; const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); -const { PURCHASE_UNIT, MAX_QUANTITY } = LOTTO_SETTING; +const { PURCHASE_UNIT } = LOTTO_SETTING; const ERROR_MESSAGES = Object.freeze({ INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, @@ -14,7 +14,7 @@ const ERROR_MESSAGES = Object.freeze({ PURCHASE_LESS: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, - LOTTO_QUANTITY: `${PREFIX}로또 번호는 ${MAX_QUANTITY} 만큼 생성되어야 합니다 `, + LOTTO_QUANTITY: (MAX_QUANTITY) => `${PREFIX}로또 번호는 ${MAX_QUANTITY}만큼 생성되어야 합니다 `, LOTTO_RANGE: (MIN_RANGE, MAX_RANGE) => `${PREFIX}로또 번호는 ${MIN_RANGE}부터 ${MAX_RANGE}여야 합니다`, }); diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 1a993800f..5a369fad0 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -7,7 +7,6 @@ const LOTTO_SETTING = Object.freeze({ FIFTH: 5000, OTHER: 0, }), - MAX_QUANTITY: 6, PURCHASE_UNIT: 1000, }); diff --git a/src/domains/Lotto.js b/src/domains/Lotto.js index 858708956..5edcfbf4b 100644 --- a/src/domains/Lotto.js +++ b/src/domains/Lotto.js @@ -1,7 +1,7 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; class Lotto { + static #MAX_QUANTITY = 6; #lottoNumbers; constructor(numbers) { @@ -11,7 +11,7 @@ class Lotto { #validate(numbers) { if (!this.#isQuantity(numbers)) { - throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); + throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY(Lotto.#MAX_QUANTITY)); } if (!this.#isDuplicate(numbers)) { throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); @@ -19,7 +19,7 @@ class Lotto { } #isQuantity(numbers) { - return numbers.length === LOTTO_SETTING.MAX_QUANTITY; + return numbers.length === Lotto.#MAX_QUANTITY; } #isDuplicate(numbers) { @@ -31,7 +31,12 @@ class Lotto { } countNumbers(numbers) { - return numbers.filter((number) => this.#lottoNumbers.has(number)).length; + const intersection = new Set([...this.#lottoNumbers].filter((x) => numbers.hasLottoNumber(x))); + return intersection.size; + } + + static getQuan() { + return { MAX_QUANTITY: this.#MAX_QUANTITY }; } getNumbers() { diff --git a/src/domains/LottoWinningFactory.js b/src/domains/LottoWinningFactory.js index 749c14b66..0ed609078 100644 --- a/src/domains/LottoWinningFactory.js +++ b/src/domains/LottoWinningFactory.js @@ -1,3 +1,5 @@ +import Lotto from './Lotto.js'; + class LottoWinningFactory { #lottoNumberFactory; @@ -6,7 +8,8 @@ class LottoWinningFactory { } createWinningLotto(numbers) { - return numbers.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); + const newNumbers = numbers.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); + return new Lotto(newNumbers); } createBonusLotto(number) { diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index 322aea315..328501eda 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -1,4 +1,3 @@ -import WinningNumbersValidator from '../validators/domain/WinningNumbersValidator.js'; import BonusNumberValidator from '../validators/domain/BonusNumberValidator.js'; import LottoWinningResult from '../domains/LottoWinningResult.js'; import WinningResultDto from '../dtos/responseDto/WinningResultDto.js'; @@ -14,7 +13,6 @@ class WinningResultService { saveWinningNumbers(requestDTO) { const { winningNumbers } = requestDTO; - WinningNumbersValidator.validate(winningNumbers); const winningLotto = this.#winningFactory.createWinningLotto(winningNumbers); this.#lottoRepository.save('admin', { winningLotto }); } diff --git a/src/utils/RandomPicker.js b/src/utils/RandomPicker.js index 57ff7c48b..eec7fa7f5 100644 --- a/src/utils/RandomPicker.js +++ b/src/utils/RandomPicker.js @@ -1,11 +1,11 @@ import { Random } from '@woowacourse/mission-utils'; -import LOTTO_SETTING from '../constants/lottoSetting.js'; import LottoNumber from '../domains/LottoNumber.js'; +import Lotto from '../domains/Lotto.js'; class RandomPicker { pick() { const { MIN_RANGE, MAX_RANGE } = LottoNumber.getRange(); - const { MAX_QUANTITY } = LOTTO_SETTING; + const { MAX_QUANTITY } = Lotto.getQuan(); return Random.pickUniqueNumbersInRange(MIN_RANGE, MAX_RANGE, MAX_QUANTITY); } } diff --git a/src/validators/domain/BonusNumberValidator.js b/src/validators/domain/BonusNumberValidator.js index 596e18873..786286e39 100644 --- a/src/validators/domain/BonusNumberValidator.js +++ b/src/validators/domain/BonusNumberValidator.js @@ -8,7 +8,7 @@ class BonusNumberValidator { } static #isDuplicate(winningLottoNumbers, bonusLottoNumber) { - return winningLottoNumbers.includes(bonusLottoNumber); + return winningLottoNumbers.hasLottoNumber(bonusLottoNumber); } } diff --git a/src/validators/domain/WinningNumbersValidator.js b/src/validators/domain/WinningNumbersValidator.js deleted file mode 100644 index bc2f266ea..000000000 --- a/src/validators/domain/WinningNumbersValidator.js +++ /dev/null @@ -1,24 +0,0 @@ -import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LOTTO_SETTING from '../../constants/lottoSetting.js'; - -class WinningNumbersValidator { - static validate(winningNumbers) { - if (!WinningNumbersValidator.#isQuantity(winningNumbers)) { - throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY); - } - if (!WinningNumbersValidator.#isDuplicate(winningNumbers)) { - throw new Error(ERROR_MESSAGES.LOTTO_DUPLICATE); - } - } - - static #isDuplicate(winningNumbers) { - const setNumbers = new Set(winningNumbers); - return setNumbers.size === winningNumbers.length; - } - - static #isQuantity(winningNumbers) { - return winningNumbers.length === LOTTO_SETTING.MAX_QUANTITY; - } -} - -export default WinningNumbersValidator; From 5cf51c4f25349a4c02f362db45e48a5e75d7fbf2 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 11:52:03 +0900 Subject: [PATCH 103/109] =?UTF-8?q?refactor(LottoPrice):=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20=EA=B8=88=EC=95=A1=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20=ED=95=9C=20=EA=B3=B3=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=AA=A8=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구매금액과 금액단위를 가지는 LottoPrice를 생성 - WinningResult: 총합 계산은 불변객체인 LottoPrice에서 진행 --- __tests__/domains/LottoPrice.test.js | 10 ++++ .../domain/PurchaseValidator.test.js | 19 ------- src/constants/errorMessages.js | 9 ++-- src/constants/lottoSetting.js | 1 - src/domains/LottoPrice.js | 49 +++++++++++++++++++ src/domains/LottoWinningResult.js | 6 --- src/services/LottoPurchaseService.js | 11 ++--- src/services/WinningResultService.js | 4 +- src/validators/domain/PurchaseValidator.js | 24 --------- 9 files changed, 70 insertions(+), 63 deletions(-) create mode 100644 __tests__/domains/LottoPrice.test.js delete mode 100644 __tests__/validators/domain/PurchaseValidator.test.js create mode 100644 src/domains/LottoPrice.js delete mode 100644 src/validators/domain/PurchaseValidator.js diff --git a/__tests__/domains/LottoPrice.test.js b/__tests__/domains/LottoPrice.test.js new file mode 100644 index 000000000..64adbcec4 --- /dev/null +++ b/__tests__/domains/LottoPrice.test.js @@ -0,0 +1,10 @@ +import LottoPrice from '../../src/domains/LottoPrice.js'; + +describe('LottoPrice 테스트', () => { + describe('메서드 테스트', () => { + const purchaseAmount = new LottoPrice(8000); + test('exchange(purchaseAmount) 단위로 구매금액을 나눈 count를 반환', () => { + expect(purchaseAmount.exchange()).toBe(8); + }); //test + }); // 생성 테스트 +}); // LottoNumberFactory 테스트 diff --git a/__tests__/validators/domain/PurchaseValidator.test.js b/__tests__/validators/domain/PurchaseValidator.test.js deleted file mode 100644 index f958d648c..000000000 --- a/__tests__/validators/domain/PurchaseValidator.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; -import PurchaseValidator from '../../../src/validators/domain/PurchaseValidator.js'; - -describe('PurchaseValidator 클래스 테스트', () => { - describe('구매금액 유효성 검사 ⭕실패 테스트', () => { - test.each([ - [999, ERROR_MESSAGES.PURCHASE_LESS], - - [1500, ERROR_MESSAGES.PURCHASE_UNIT], - ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { - expect(() => PurchaseValidator.validate(amount)).toThrow(errorMessage); - }); // 실패테스트 - }); //describe - describe('구매금액 유효성 검사 ⭕성공테스트', () => { - test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { - expect(() => PurchaseValidator.validate(amount)).not.toThrow(); - }); // 성공테스트 - }); //describe -}); //describe 클래스 테스트 diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 27ab057fc..1416dc4f4 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,8 +1,5 @@ -import LOTTO_SETTING from './lottoSetting.js'; - const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); -const { PURCHASE_UNIT } = LOTTO_SETTING; const ERROR_MESSAGES = Object.freeze({ INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, @@ -10,8 +7,10 @@ const ERROR_MESSAGES = Object.freeze({ FORMAT_NOT_NUM: `${PREFIX}숫자를 입력해야 합니다`, - PURCHASE_UNIT: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, - PURCHASE_LESS: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, + PURCHASE_UNIT: (PURCHASE_UNIT) => + `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, + PURCHASE_LESS: (PURCHASE_UNIT) => + `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, LOTTO_QUANTITY: (MAX_QUANTITY) => `${PREFIX}로또 번호는 ${MAX_QUANTITY}만큼 생성되어야 합니다 `, diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 5a369fad0..92a863e9d 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -7,7 +7,6 @@ const LOTTO_SETTING = Object.freeze({ FIFTH: 5000, OTHER: 0, }), - PURCHASE_UNIT: 1000, }); export default LOTTO_SETTING; diff --git a/src/domains/LottoPrice.js b/src/domains/LottoPrice.js new file mode 100644 index 000000000..d6379ce82 --- /dev/null +++ b/src/domains/LottoPrice.js @@ -0,0 +1,49 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; + +class LottoPrice { + static #PURCHASE_UNIT = 1000; + #purchaseAmount; + + constructor(purchaseAmount) { + this.#validate(purchaseAmount); + this.#purchaseAmount = purchaseAmount; + } + + #validate(purchaseAmount) { + if (!this.#isInteger(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + if (this.#isLessUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_LESS(LottoPrice.#PURCHASE_UNIT)); + } + if (!this.#isModUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_UNIT(LottoPrice.#PURCHASE_UNIT)); + } + } + + #isInteger(purchaseAmount) { + return Number.isInteger(purchaseAmount); + } + + #isLessUnit(purchaseAmount) { + return purchaseAmount < LottoPrice.#PURCHASE_UNIT; + } + + #isModUnit(purchaseAmount) { + return purchaseAmount % LottoPrice.#PURCHASE_UNIT === 0; + } + + static getUnit() { + return LottoPrice.#PURCHASE_UNIT; + } + + exchange() { + return this.#purchaseAmount / LottoPrice.#PURCHASE_UNIT; + } + + getWinningRate(totalWinningAmount) { + if (totalWinningAmount === 0) return 0; + return ((totalWinningAmount / this.#purchaseAmount) * 100).toFixed(1); + } +} +export default LottoPrice; diff --git a/src/domains/LottoWinningResult.js b/src/domains/LottoWinningResult.js index 411649c99..835848afa 100644 --- a/src/domains/LottoWinningResult.js +++ b/src/domains/LottoWinningResult.js @@ -12,12 +12,6 @@ class LottoWinningResult { return { winningStats, totalWinningAmount }; } - // 수익률 - static getWinningRate(purchaseAmount, totalWinningAmount) { - if (totalWinningAmount === 0) return 0; - return ((totalWinningAmount / purchaseAmount) * 100).toFixed(1); - } - // #당첨 카운트 반환 형식 [winning:일치 갯수, bonus: 보너스여부] static #getWinningMatch(lottos, winnintLottos, bonusLotto) { return lottos.map((lotto) => ({ diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index cfa134f48..3a917700c 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -1,6 +1,5 @@ -import LOTTO_SETTING from '../constants/lottoSetting.js'; +import LottoPrice from '../domains/LottoPrice.js'; import PurchasedLottosDto from '../dtos/responseDto/PurchasedLottosDto.js'; -import PurchaseValidator from '../validators/domain/PurchaseValidator.js'; class LottoPurchaseService { #lottoStore; @@ -13,13 +12,13 @@ class LottoPurchaseService { savePurchaseAmount(requestDTO) { const { purchaseAmount } = requestDTO; - PurchaseValidator.validate(purchaseAmount); - this.#lottoRepository.save('admin', { purchaseAmount }); + const lottoPrice = new LottoPrice(purchaseAmount); + this.#lottoRepository.save('admin', { lottoPrice }); } getPurchasedLottos() { - const { purchaseAmount } = this.#lottoRepository.findAll('admin'); - const lottoCount = purchaseAmount / LOTTO_SETTING.PURCHASE_UNIT; + const { lottoPrice } = this.#lottoRepository.findAll('admin'); + const lottoCount = lottoPrice.exchange(); const lottos = this.#lottoStore.buyLotto(lottoCount); this.#lottoRepository.save('admin', { lottos }); return new PurchasedLottosDto({ lottos }); diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index 328501eda..b0daa7e47 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -28,13 +28,13 @@ class WinningResultService { getWinningResult() { const db = this.#lottoRepository.findAll('admin'); - const { purchaseAmount, lottos, winningLotto, bonusLotto } = db; + const { lottoPrice, lottos, winningLotto, bonusLotto } = db; const { winningStats, totalWinningAmount } = LottoWinningResult.getWinningStats( lottos, winningLotto, bonusLotto, ); - const winningRate = LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount); + const winningRate = lottoPrice.getWinningRate(totalWinningAmount); this.#lottoRepository.save('admin', { winningStats, winningRate }); return new WinningResultDto({ winningStats, winningRate }); } diff --git a/src/validators/domain/PurchaseValidator.js b/src/validators/domain/PurchaseValidator.js deleted file mode 100644 index 3ec9aa9ee..000000000 --- a/src/validators/domain/PurchaseValidator.js +++ /dev/null @@ -1,24 +0,0 @@ -import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LOTTO_SETTING from '../../constants/lottoSetting.js'; - -class PurchaseValidator { - static validate(purchaseAmount) { - if (PurchaseValidator.#isLessUnit(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.PURCHASE_LESS); - } - - if (!PurchaseValidator.#isModUnit(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); - } - } - - static #isLessUnit(purchaseAmount) { - return purchaseAmount < LOTTO_SETTING.PURCHASE_UNIT; - } - - static #isModUnit(purchaseAmount) { - return purchaseAmount % LOTTO_SETTING.PURCHASE_UNIT === 0; - } -} - -export default PurchaseValidator; From 979cdf891bd371c1d1de2c93a38f55ebffdc6b2f Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 11:52:03 +0900 Subject: [PATCH 104/109] =?UTF-8?q?refactor(LottoPrice):=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=20=EA=B8=88=EC=95=A1=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20=ED=95=9C=20=EA=B3=B3=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=AA=A8=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구매금액과 금액단위를 가지는 LottoPrice를 생성 - WinningResult: 총합 계산은 불변객체인 LottoPrice에서 진행 - 테스트 추가 --- __tests__/domains/LottoPrice.test.js | 35 +++++++++++++ __tests__/domains/LottoWinningResult.test.js | 8 --- .../domain/PurchaseValidator.test.js | 19 ------- src/constants/errorMessages.js | 9 ++-- src/constants/lottoSetting.js | 1 - src/domains/LottoPrice.js | 49 +++++++++++++++++++ src/domains/LottoWinningResult.js | 6 --- src/services/LottoPurchaseService.js | 11 ++--- src/services/WinningResultService.js | 4 +- src/validators/domain/PurchaseValidator.js | 24 --------- 10 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 __tests__/domains/LottoPrice.test.js delete mode 100644 __tests__/validators/domain/PurchaseValidator.test.js create mode 100644 src/domains/LottoPrice.js delete mode 100644 src/validators/domain/PurchaseValidator.js diff --git a/__tests__/domains/LottoPrice.test.js b/__tests__/domains/LottoPrice.test.js new file mode 100644 index 000000000..e8d95e487 --- /dev/null +++ b/__tests__/domains/LottoPrice.test.js @@ -0,0 +1,35 @@ +import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; +import LottoPrice from '../../src/domains/LottoPrice.js'; + +describe('LottoPrice 테스트', () => { + describe('구매금액 유효성 검사 ⭕실패 테스트', () => { + const UNIT = LottoPrice.getUnit(); + test.each([ + [999, ERROR_MESSAGES.PURCHASE_LESS(UNIT)], + + [1500, ERROR_MESSAGES.PURCHASE_UNIT(UNIT)], + ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { + expect(() => new LottoPrice(amount)).toThrow(errorMessage); + }); // 실패테스트 + }); //describe + describe('구매금액 유효성 검사 ⭕성공테스트', () => { + test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { + expect(() => new LottoPrice(amount)).not.toThrow(); + }); // 성공테스트 + }); + describe('메서드 테스트', () => { + const purchaseAmount = new LottoPrice(8000); + test('exchange(purchaseAmount) 단위로 구매금액을 나눈 count를 반환', () => { + expect(purchaseAmount.exchange()).toBe(8); + }); //test + }); // + describe('getWinningRate(totalWinningAmount)테스트', () => { + test.each([ + [8000, 5000, '62.5'], + [12000, 7000, '58.3'], + ])('getWinningRate(%s, %s) 반환 값 %s', (purchaseAmount, totalWinningAmount, result) => { + const lottoPrice = new LottoPrice(purchaseAmount); + expect(lottoPrice.getWinningRate(totalWinningAmount)).toBe(result); + }); // 성공 테스트 + }); // 메서드 테스트 +}); // LottoNumberFactory 테스트 diff --git a/__tests__/domains/LottoWinningResult.test.js b/__tests__/domains/LottoWinningResult.test.js index 39377157b..e8c95a79a 100644 --- a/__tests__/domains/LottoWinningResult.test.js +++ b/__tests__/domains/LottoWinningResult.test.js @@ -40,12 +40,4 @@ describe('LottoWinningFactory 테스트', () => { }, ); // 성공 테스트 }); // 생성 테스트 - describe('getWinningRate(purchaseAmount, totalWinningAmount)테스트', () => { - test.each([ - [8000, 5000, '62.5'], - [12000, 7000, '58.3'], - ])('getWinningRate(%s, %s) 반환 값 %s', (purchaseAmount, totalWinningAmount, result) => { - expect(LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount)).toBe(result); - }); // 실패테스트 - }); // 생성 테스트 }); // 설명 diff --git a/__tests__/validators/domain/PurchaseValidator.test.js b/__tests__/validators/domain/PurchaseValidator.test.js deleted file mode 100644 index f958d648c..000000000 --- a/__tests__/validators/domain/PurchaseValidator.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import ERROR_MESSAGES from '../../../src/constants/errorMessages.js'; -import PurchaseValidator from '../../../src/validators/domain/PurchaseValidator.js'; - -describe('PurchaseValidator 클래스 테스트', () => { - describe('구매금액 유효성 검사 ⭕실패 테스트', () => { - test.each([ - [999, ERROR_MESSAGES.PURCHASE_LESS], - - [1500, ERROR_MESSAGES.PURCHASE_UNIT], - ])('❌ validate 테스트 %s throw Error %s', (amount, errorMessage) => { - expect(() => PurchaseValidator.validate(amount)).toThrow(errorMessage); - }); // 실패테스트 - }); //describe - describe('구매금액 유효성 검사 ⭕성공테스트', () => { - test.each([[1000], [15000]])('통과해야됨 %s', (amount) => { - expect(() => PurchaseValidator.validate(amount)).not.toThrow(); - }); // 성공테스트 - }); //describe -}); //describe 클래스 테스트 diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 27ab057fc..1416dc4f4 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,8 +1,5 @@ -import LOTTO_SETTING from './lottoSetting.js'; - const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); -const { PURCHASE_UNIT } = LOTTO_SETTING; const ERROR_MESSAGES = Object.freeze({ INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, @@ -10,8 +7,10 @@ const ERROR_MESSAGES = Object.freeze({ FORMAT_NOT_NUM: `${PREFIX}숫자를 입력해야 합니다`, - PURCHASE_UNIT: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, - PURCHASE_LESS: `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, + PURCHASE_UNIT: (PURCHASE_UNIT) => + `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, + PURCHASE_LESS: (PURCHASE_UNIT) => + `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 보다 커야합니다`, LOTTO_DUPLICATE: `${PREFIX}로또 번호가 중복됐습니다`, LOTTO_QUANTITY: (MAX_QUANTITY) => `${PREFIX}로또 번호는 ${MAX_QUANTITY}만큼 생성되어야 합니다 `, diff --git a/src/constants/lottoSetting.js b/src/constants/lottoSetting.js index 5a369fad0..92a863e9d 100644 --- a/src/constants/lottoSetting.js +++ b/src/constants/lottoSetting.js @@ -7,7 +7,6 @@ const LOTTO_SETTING = Object.freeze({ FIFTH: 5000, OTHER: 0, }), - PURCHASE_UNIT: 1000, }); export default LOTTO_SETTING; diff --git a/src/domains/LottoPrice.js b/src/domains/LottoPrice.js new file mode 100644 index 000000000..d6379ce82 --- /dev/null +++ b/src/domains/LottoPrice.js @@ -0,0 +1,49 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; + +class LottoPrice { + static #PURCHASE_UNIT = 1000; + #purchaseAmount; + + constructor(purchaseAmount) { + this.#validate(purchaseAmount); + this.#purchaseAmount = purchaseAmount; + } + + #validate(purchaseAmount) { + if (!this.#isInteger(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.INTEGER); + } + if (this.#isLessUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_LESS(LottoPrice.#PURCHASE_UNIT)); + } + if (!this.#isModUnit(purchaseAmount)) { + throw new Error(ERROR_MESSAGES.PURCHASE_UNIT(LottoPrice.#PURCHASE_UNIT)); + } + } + + #isInteger(purchaseAmount) { + return Number.isInteger(purchaseAmount); + } + + #isLessUnit(purchaseAmount) { + return purchaseAmount < LottoPrice.#PURCHASE_UNIT; + } + + #isModUnit(purchaseAmount) { + return purchaseAmount % LottoPrice.#PURCHASE_UNIT === 0; + } + + static getUnit() { + return LottoPrice.#PURCHASE_UNIT; + } + + exchange() { + return this.#purchaseAmount / LottoPrice.#PURCHASE_UNIT; + } + + getWinningRate(totalWinningAmount) { + if (totalWinningAmount === 0) return 0; + return ((totalWinningAmount / this.#purchaseAmount) * 100).toFixed(1); + } +} +export default LottoPrice; diff --git a/src/domains/LottoWinningResult.js b/src/domains/LottoWinningResult.js index 411649c99..835848afa 100644 --- a/src/domains/LottoWinningResult.js +++ b/src/domains/LottoWinningResult.js @@ -12,12 +12,6 @@ class LottoWinningResult { return { winningStats, totalWinningAmount }; } - // 수익률 - static getWinningRate(purchaseAmount, totalWinningAmount) { - if (totalWinningAmount === 0) return 0; - return ((totalWinningAmount / purchaseAmount) * 100).toFixed(1); - } - // #당첨 카운트 반환 형식 [winning:일치 갯수, bonus: 보너스여부] static #getWinningMatch(lottos, winnintLottos, bonusLotto) { return lottos.map((lotto) => ({ diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index cfa134f48..3a917700c 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -1,6 +1,5 @@ -import LOTTO_SETTING from '../constants/lottoSetting.js'; +import LottoPrice from '../domains/LottoPrice.js'; import PurchasedLottosDto from '../dtos/responseDto/PurchasedLottosDto.js'; -import PurchaseValidator from '../validators/domain/PurchaseValidator.js'; class LottoPurchaseService { #lottoStore; @@ -13,13 +12,13 @@ class LottoPurchaseService { savePurchaseAmount(requestDTO) { const { purchaseAmount } = requestDTO; - PurchaseValidator.validate(purchaseAmount); - this.#lottoRepository.save('admin', { purchaseAmount }); + const lottoPrice = new LottoPrice(purchaseAmount); + this.#lottoRepository.save('admin', { lottoPrice }); } getPurchasedLottos() { - const { purchaseAmount } = this.#lottoRepository.findAll('admin'); - const lottoCount = purchaseAmount / LOTTO_SETTING.PURCHASE_UNIT; + const { lottoPrice } = this.#lottoRepository.findAll('admin'); + const lottoCount = lottoPrice.exchange(); const lottos = this.#lottoStore.buyLotto(lottoCount); this.#lottoRepository.save('admin', { lottos }); return new PurchasedLottosDto({ lottos }); diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index 328501eda..b0daa7e47 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -28,13 +28,13 @@ class WinningResultService { getWinningResult() { const db = this.#lottoRepository.findAll('admin'); - const { purchaseAmount, lottos, winningLotto, bonusLotto } = db; + const { lottoPrice, lottos, winningLotto, bonusLotto } = db; const { winningStats, totalWinningAmount } = LottoWinningResult.getWinningStats( lottos, winningLotto, bonusLotto, ); - const winningRate = LottoWinningResult.getWinningRate(purchaseAmount, totalWinningAmount); + const winningRate = lottoPrice.getWinningRate(totalWinningAmount); this.#lottoRepository.save('admin', { winningStats, winningRate }); return new WinningResultDto({ winningStats, winningRate }); } diff --git a/src/validators/domain/PurchaseValidator.js b/src/validators/domain/PurchaseValidator.js deleted file mode 100644 index 3ec9aa9ee..000000000 --- a/src/validators/domain/PurchaseValidator.js +++ /dev/null @@ -1,24 +0,0 @@ -import ERROR_MESSAGES from '../../constants/errorMessages.js'; -import LOTTO_SETTING from '../../constants/lottoSetting.js'; - -class PurchaseValidator { - static validate(purchaseAmount) { - if (PurchaseValidator.#isLessUnit(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.PURCHASE_LESS); - } - - if (!PurchaseValidator.#isModUnit(purchaseAmount)) { - throw new Error(ERROR_MESSAGES.PURCHASE_UNIT); - } - } - - static #isLessUnit(purchaseAmount) { - return purchaseAmount < LOTTO_SETTING.PURCHASE_UNIT; - } - - static #isModUnit(purchaseAmount) { - return purchaseAmount % LOTTO_SETTING.PURCHASE_UNIT === 0; - } -} - -export default PurchaseValidator; From 698d3753e1d9dd698ec2be3fd6470060fc6fe068 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 13:24:12 +0900 Subject: [PATCH 105/109] docs: Update README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 과제 진행 결과 추가 --- README.md | 224 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 196 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index f535ea17f..55ad72005 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,215 @@ # 로또 -## 3주차 과제의 숨겨진 의도를 발견하다 -개발하다 구조의 잘못됨을 깨닫고 전부 날린 뒤 새로 작업을 시작했다 -새로 작업한 이유는 다음과 같다 -1. [프리코스의 한 글](https://velog.io/@hayooncode/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-8%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9C%BC%EB%A1%9C%EC%9D%98-%EC%97%AC%EC%A0%95)을 보고 객체지향에 대해 다시 한번 고민하게 됨 -2. 그러다 로또 자체가 일을 다 하는 방법을 찾아보기 시작함 -3. nextjs로 블로그를 만드려고 공부하다 캐싱이란걸 보게됨 -4. 이 프로젝트에 캐싱을 적용하려고 보니.. -[사진] -5. 모든 퍼즐이 맞춰짐 +# 로또 + +3주차 과제에서 작은 웹 환경을 경험해 보라는 의도를 느꼈습니다 +그 이유는 아래의 조건 때문인데요 + +> 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생 시키고 해당 메시지를 출력한 다음 해당 지점부터 다시 입력을 받는다. + +컨트롤러와 서비스 레이어에 상태를 저장하지 않고 입력이 실패했을 시에도 +사용자의 데이터를 잃지 않기 위해 간단한 저장소를 추가해 데이터를 저장했습니다 + +## 레이어 + +image + +모든 의존성은 프로그램 실행 시 `App.js` 의 생성자에서 주입됩니다. + +기본 MVC패턴에서 상태 저장의 책임을 `LottoRepository`로 분리했습니다. + +`Service`는 `구매 후 로또 출력`과 `당첨 번호와 보너스 번호를 입력 받고 결과 출력`의 두 기능으로 나눴습니다. + +`Controller`에서 `Service` 레이어로 데이터를 전송할 때 `DTO(Data Transfer Object)`로 감싸서 웹 환경을 흉내냈습니다. + +## 워크 플로우 + +서비스- 로또 구매 서비스 +image +서비스- 당첨 결과 서비스 +image +## 로또 기능 구현 +image +`1~45` 까지의 `LottoNumber` 객체를 **모듈이 로드될 때 단 한번 생성되며** +그 뒤의 로또 들은 미리 생성된 `LottoNumber` 들을 참조하게 하여 **캐싱**하였습니다 +`LottoNumber` : 가장 작은 객체로, 1~45 까지의 범위를 검증하고, 그 중 한 값을 담당하며 불변 객체의 역할을 합니다 +`LottoNumberFactory`: 모듈이 로드될 때, `LottoNumber`를 1부터 45까지 생성하고, 들어온 숫자를 미리 생성한 객체로 반환해주는 싱글톤 객체입니다 -## 진짜 구현할 기능 목록 +`Lotto`: 한 장의 로또라는 의미를 가진 객체입니다. `LottoNumber`과 마찬가지로 생성될 때 유효성 검사를 하여 유효성을 유지하게 했습니다. `6개`의 `LottoNumber` 을 참조하며 주어진 값과 일치하는 번호의 갯수를 확인할 수 있습니다 -### LottoNumber -로또넘버 하나 하나를 객체로 본다 -- [ ] 1-45 범위 내에서 정수 값을 프로퍼티로 가짐 +`LottoFactory`: `mission-utils`라이브러리를 이용해 랜덤 숫자 배열을 생성하여 `LottoNumberFactory`로 `Lotto` 객체를 만들어냅니다 -### LottoNumberFactory -로또 넘버를 인스턴스화시킴 -- [ ] 프로그램 시작시 로또 번호들을 인스턴스화시킨다 +`LottoStore`: `LottoFactory`를 이용해 `Lotto` 배열을 만들어 냅니다. -### Lotto -- [ ] 6개의 lottoNumber의 참조를 프로퍼티로 가짐 -- [ ] 당첨 번호와 일치하는지 확인 -- [ ] 보너스 번호와 일치하는지 확인 +`LottoWinningFactory`: 당첨 번호와 보너스 번호를 미리 생성한 `LottoNumber` 에 참조 시키기 위해 `LottoNumberFactory` 만의 의존성을 주입 받은 객체입니다. -### LottoWinning -- [ ] 당첨금을 알려줌 +### 캐싱 -### LottoStroe -- [ ] 로또를 살 수 있음 -- [ ] 랜덤한 숫자를 받아옴 +image -### Account -- [ ] 구매 금액을 저장 -- [ ] 수익률을 계산 +> 큰 금액이 들어오면 어떡하지? +구매 금액에 제한이 없던 점을 고려해서, 불변 객체를 매번 생성하던 로직을 미리 생성해두고 참조하는 로직으로 변경하였습니다. +구매 금액에 얼마가 들어오든, 이미 생성된 인스턴스를 재사용하기 때문에 메모리 관리를 효율적으로 쓸 수 있습니다 +### 모듈 싱글톤 +```javascript +// domains/LottoNumberFactory.js +class LottoNumberFactory { + // ... +} +// 모듈 싱글톤 적용 +const LottoNumberFactoryInstance = new LottoNumberFactory(); +export default LottoNumberFactoryInstance; +``` +`LottoNumber` 객체는 프로그램 실행 중 45개 만 존재할 수 있게 +모듈 내에서 인스턴스를 생성해 그 인스턴스를 `export`함으로서 단 하나의 인스턴스를 갖게 했습니다 + +## 유효성 검사 +image + +각 객체들은 생성될 때 본인의 유효성을 스스로 지켜 항상 사용할 수 있는 상태를 유지합니다 + +그 외로 입력, 데이터 전송, 다른 객체와 검증을 같이해야 하는 경우 (당첨번호와 보너스번호의 중복) 세 부분에서 유효성을 검사합니다. +도메인에 관련된 검사는 서비스 레이어에서만 검사하도록 수정하였습니다. + +#### 1. 입력 부에서 공백인지 확인 + +```js +// view/LottoInputView.js + async readBonusNumber() { + const bonusNumber = await Console.readLineAsync(INFO_MEESAGE.INFO_BONUS_NUMBER); + return LottoInputView.#checkBlank(bonusNumber); + } + static #checkBlank(input) { + if (input.trim() === '') throw new Error(ERROR_MESSAGES.BLANK); + return input; + } +``` +입력 단에서 빈 값이 많이 입력될 것이라 예상되어 빈 값은 우선적으로 검사해줬습니다 + +#### 2. Controller 에서 Service로 입력 값을 전송해줄 때 DTO 안에서 간단한 검사(파싱, 타입변환) + +##### Request +```js +// dtos/requestDto/WinningNumbersDto.js + const parseWinningNumbers = Parser.stringToNumberArray(winningNumbers); + InputWinningNumberValidator.validate(parseWinningNumbers); + this.#winningNumbers = parseWinningNumbers; +``` +`Controller`단에서 데이터를 보낼 때 파싱과 변환을 수행해서 데이터 타입을 변경 후 타입관련 유효성 검사를 진행합니다 +`Service`는 사용할 수 있는 데이터를 전달 받습니다 + +#### Response +```js +// dtos/responseDto/PurchasedLottosDto.js + toJSON() { + const lottosToArray = [...this.#lottos].map((lotto) => lotto.getNumbers()); + return { + lottos: lottosToArray, + } +``` +`Service`에서 `Controller`로 도메인 객체를 보내되, 클라이언트 단에서 사용할 때 값 배열로 변환하여 내부 구조를 숨겼습니다 + + +#### 3. Service에서 값을 받고 도메인 관련 검증 +```js +// services/WinningResultService.js + const bonusLotto = this.#winningFactory.createBonusLotto(bonusNumber); + BonusNumberValidator.validate(winningLotto, bonusLotto); +``` +객체 끼리의 비교는 도메인 규칙이기 때문에 서비스 단에서 검사해줬습니다 + +## 세부 사항 + +### Repository +예외 발생 시 이전 상태를 복구하기 위해 저장소를 도입했습니다. + +```js + getPurchasedLottos() { + const { purchaseAmount } = this.#lottoRepository.findAll('admin'); + // ... + this.#lottoRepository.save('admin', { lottos }); + return new PurchasedLottosDto({ lottos }); + } + +``` +작은 웹 환경이라 생각하고 데이터 베이스를 사용하는 식으로 구성하였습니다 + + +### 템플릿 에러 메세지 +```javascript +// constants/errorMessage.js +const PREFIX = '[ERROR]'; +const format = (unit) => new Intl.NumberFormat().format(unit); +const ERROR_MESSAGES = Object.freeze({ + PURCHASE_UNIT: (PURCHASE_UNIT) => + `${PREFIX}구매 금액은 ${format(PURCHASE_UNIT)}원 단위로 입력해야됩니다`, +// ... +}); +``` +단위, 범위, 수량을 객체가 지니고 있기 때문에 에러 메세지 관리가 필요했습니다 +템플릿 리터럴을 사용해 동적으로 값을 정해줬습니다. + +### DI +```js +// domains/LottoFactory.js + constructor(piker, lottoNumberFactory) { + this.#piker = piker; + // ... + createLotto(numbers) { + const newArray = this.#piker.pick(numbers); + //... +``` +로또 팩토리는 숫자 배열을 생성자에서 주입받습니다 +이를 통해 테스트 시 랜덤한 값이 아닌 고정된 숫자 배열을 사용해 테스트 할 수 있었습니다 + +### Constants +```js +// constans/ +const LOTTO_SETTING = Object.freeze({ + PRIZES: Object.freeze({ + FIRST: 2000000000, + SECOND: 30000000, + THIRD: 1500000, + FOURTH: 50000, + FIFTH: 5000, + OTHER: 0, + }), +}); +``` +상금은 변경될 가능성이 높다고 생각하여 객체가 아닌 상수로 정의하였습니다 + +### 당첨 카운트 +```js +// domians/LottoWinningResult.js + static #getWinningRank(winningStats) { + return winningStats.map(({ winning, bonus }) => { + if (winning === 6) return { rank: 'FIRST' }; + if (winning === 5 && bonus) return { rank: 'SECOND' }; + if (winning === 5) return { rank: 'THIRD' }; + if (winning === 4) return { rank: 'FOURTH' }; + if (winning === 3) return { rank: 'FIFTH' }; + return { rank: 'OTHER' }; + }); + } + + // #랭크로 총 순위 카운트 + static #getWinningCount(winningRank) { + const count = { FIRST: 0, SECOND: 0, THIRD: 0, FOURTH: 0, FIFTH: 0, OTHER: 0 }; + winningRank.forEach(({ rank }) => { + count[rank] += 1; + }); + return count; + } +``` +로또 배열과 당첨 번호를 비교하여 `[winning:일치 갯수, bonus: 보너스여부]` 형태의 객체 배열로 변환했습니다 +그 후 랭크를 상금 상수의 키와 일치 시켜 당첨 카운트를 계산했습니다 From 90a6cba39a445d926aeca000c901a0ab2c48c556 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 18:04:09 +0900 Subject: [PATCH 106/109] =?UTF-8?q?refactor(Lotto):=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=B1=85=EC=9E=84=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LottoFacotory 에서 Lotto 안으로 이동 - LottoFactory는 Lotto를 랜덤 생성하는 팩토리 - 계층을 줄이기 위해 로또 스토어를 없애고 반복횟수를 서비스 로직에서 구함 --- __tests__/domains/Lotto.test.js | 9 ++--- __tests__/domains/LottoStore.test.js | 25 ------------- __tests__/domains/LottoWinningFactory.test.js | 37 ------------------- src/App.js | 12 ++---- src/domains/Lotto.js | 7 +++- src/domains/LottoFacotry.js | 11 ++---- src/domains/LottoStore.js | 16 -------- src/domains/LottoWinningFactory.js | 20 ---------- src/services/LottoPurchaseService.js | 9 ++--- src/services/WinningResultService.js | 10 ++--- 10 files changed, 25 insertions(+), 131 deletions(-) delete mode 100644 __tests__/domains/LottoStore.test.js delete mode 100644 __tests__/domains/LottoWinningFactory.test.js delete mode 100644 src/domains/LottoStore.js delete mode 100644 src/domains/LottoWinningFactory.js diff --git a/__tests__/domains/Lotto.test.js b/__tests__/domains/Lotto.test.js index 3f0fad773..0252d5c1a 100644 --- a/__tests__/domains/Lotto.test.js +++ b/__tests__/domains/Lotto.test.js @@ -1,10 +1,10 @@ import Lotto from '../../src/domains/Lotto.js'; import ERROR_MESSAGES from '../../src/constants/errorMessages.js'; +import LottoNumberFactoryInstance from '../../src/domains/LottoNumberFactory.js'; const MOCK_NUMBERS = [2, 1, 3, 4, 5, 6]; -const mockLottoNumber = (num) => ({ getNumber: () => num, hasLottoNumber: (n) => n === num }); -const mockLottoArray = MOCK_NUMBERS.map((num) => mockLottoNumber(num)); -const mockLotto = new Lotto(mockLottoArray); +const mockLotto = new Lotto(MOCK_NUMBERS); +const mockLottoCopy = LottoNumberFactoryInstance.getLottoNumber(1); const { MAX_QUANTITY } = Lotto.getQuan(); describe('로또 클래스 테스트', () => { @@ -31,8 +31,7 @@ describe('로또 클래스 테스트', () => { }); // 성공 테스트 test('⭕메서드 테스트 hasLottoNumber(lottoNumber)', () => { - const test = mockLottoArray.find((obj) => obj.getNumber() === 3); - expect(mockLotto.hasLottoNumber(test)).toBe(true); + expect(mockLotto.hasLottoNumber(mockLottoCopy)).toBe(true); }); // 성공 테스트 }); }); diff --git a/__tests__/domains/LottoStore.test.js b/__tests__/domains/LottoStore.test.js deleted file mode 100644 index 6e2bbf974..000000000 --- a/__tests__/domains/LottoStore.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import LottoStore from '../../src/domains/LottoStore.js'; -import Lotto from '../../src/domains/Lotto.js'; - -const MOCK_NUMBERS = [1, 2, 3, 4, 5, 6]; -const mockLottoArray = MOCK_NUMBERS.map((num) => ({ getNumber: () => num })); -const mockLotto = new Lotto(mockLottoArray); -const mockLottoFactory = { createLotto: () => mockLotto }; - -const store = new LottoStore(mockLottoFactory); -describe('LottoStore 테스트', () => { - describe('buyLotto(purchaseAmount, count) ⭕성공 테스트', () => { - // 길이 체크 - // 인스턴스체크 - // 반환값 체크 - test('제대로 반환하는지 테스트', () => { - const count = 8; - const lottos = store.buyLotto(count); - expect(lottos).toHaveLength(count); - expect(lottos[0]).toBeInstanceOf(Lotto); - lottos.forEach((lotto) => { - expect(lotto.getNumbers()).toEqual(MOCK_NUMBERS); - }); - }); - }); // 생성 테스트 -}); // LottoNumberFactory 테스트 diff --git a/__tests__/domains/LottoWinningFactory.test.js b/__tests__/domains/LottoWinningFactory.test.js deleted file mode 100644 index bae9a69d0..000000000 --- a/__tests__/domains/LottoWinningFactory.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import LottoWinningFactory from '../../src/domains/LottoWinningFactory.js'; - -class MockLottoNumber { - constructor(number) { - this.number = number; - } - getNumber() { - return this.number; - } -} -const mockLottoNumberFactory = { - map: new Map(), - getLottoNumber: jest.fn(function (number) { - if (!this.map.has(number)) { - this.map.set(number, new MockLottoNumber(number)); - } - return this.map.get(number); - }), -}; -const factory = new LottoWinningFactory(mockLottoNumberFactory); -const result = [1, 2, 3, 4, 5, 6]; - -describe('LottoWinningFactory 테스트', () => { - describe('createWinningLotto(numbers) 테스트', () => { - test('', () => { - const winningNumbers = factory.createWinningLotto(result); - const winningNumbersCopy = factory.createWinningLotto(result); - expect(winningNumbers[0]).toBe(winningNumbersCopy[0]); - }); // test - }); // 생성 테스트 - describe('createBonusLotto(number) 테스트', () => { - test('', () => { - const bonusNumber = factory.createBonusLotto(result[0]); - expect(bonusNumber.getNumber()).toBe(result[0]); - }); // test - }); // 생성 테스트 -}); // LottoNumberFactory 테스트 diff --git a/src/App.js b/src/App.js index 3c636f572..40dcdd9db 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,6 @@ -import LottoNumberFactory from './domains/LottoNumberFactory.js'; import LottoFactory from './domains/LottoFacotry.js'; import RandomPicker from './domains/RandomPicker.js'; -import LottoStore from './domains/LottoStore.js'; import LottoRepository from './repositories/LottoRepository.js'; -import LottoWinningFactory from './domains/LottoWinningFactory.js'; import LottoPurchaseService from './services/LottoPurchaseService.js'; import WinningResultService from './services/WinningResultService.js'; import LottoInputView from './views/LottoInputView.js'; @@ -14,18 +11,15 @@ class App { #lottoController; constructor() { - const lottoNumberFactory = LottoNumberFactory; const lottoRepository = new LottoRepository(); // 구매 의존성, 랜덤조건 주입 const randomPiker = new RandomPicker(); - const randomLottoFactory = new LottoFactory(randomPiker, lottoNumberFactory); - const lottoStore = new LottoStore(randomLottoFactory); - const lottoPurchaseService = new LottoPurchaseService(lottoStore, lottoRepository); + const randomLottoFactory = new LottoFactory(randomPiker); + const lottoPurchaseService = new LottoPurchaseService(randomLottoFactory, lottoRepository); // 당첨 의존성, 고정 생성 팩토리 주입 - const lottoWinningFactory = new LottoWinningFactory(lottoNumberFactory); - const winningResultService = new WinningResultService(lottoWinningFactory, lottoRepository); + const winningResultService = new WinningResultService(lottoRepository); const lottoInputView = new LottoInputView(); const lottoOutputView = new LottoOutputView(); diff --git a/src/domains/Lotto.js b/src/domains/Lotto.js index 5edcfbf4b..3d31a40d6 100644 --- a/src/domains/Lotto.js +++ b/src/domains/Lotto.js @@ -1,14 +1,19 @@ import ERROR_MESSAGES from '../constants/errorMessages.js'; +import LottoNumberFactory from './LottoNumberFactory.js'; class Lotto { static #MAX_QUANTITY = 6; #lottoNumbers; constructor(numbers) { - this.#lottoNumbers = new Set(numbers); + this.#lottoNumbers = new Set(this.#createLotto(numbers)); this.#validate(numbers); } + #createLotto(numbers) { + return numbers.map((num) => LottoNumberFactory.getLottoNumber(num)); + } + #validate(numbers) { if (!this.#isQuantity(numbers)) { throw new Error(ERROR_MESSAGES.LOTTO_QUANTITY(Lotto.#MAX_QUANTITY)); diff --git a/src/domains/LottoFacotry.js b/src/domains/LottoFacotry.js index 93568caae..fca76f29a 100644 --- a/src/domains/LottoFacotry.js +++ b/src/domains/LottoFacotry.js @@ -2,19 +2,14 @@ import Lotto from './Lotto.js'; class LottoFactory { #piker; - #lottoNumberFactory; - constructor(piker, lottoNumberFactory) { + constructor(piker) { this.#piker = piker; - this.#lottoNumberFactory = lottoNumberFactory; } createLotto(numbers) { - const newArray = this.#piker.pick(numbers); - const creatdeNumbers = newArray.map((number) => - this.#lottoNumberFactory.getLottoNumber(number), - ); - return new Lotto(creatdeNumbers); + const newNumbers = this.#piker.pick(numbers); + return new Lotto(newNumbers); } } diff --git a/src/domains/LottoStore.js b/src/domains/LottoStore.js deleted file mode 100644 index 27a31b424..000000000 --- a/src/domains/LottoStore.js +++ /dev/null @@ -1,16 +0,0 @@ -class LottoStore { - #lottofactory; - - constructor(lottofactory) { - this.#lottofactory = lottofactory; - } - - buyLotto(count) { - const lottos = []; - for (let i = 0; i < count; i += 1) { - lottos.push(this.#lottofactory.createLotto()); - } - return lottos; - } -} -export default LottoStore; diff --git a/src/domains/LottoWinningFactory.js b/src/domains/LottoWinningFactory.js deleted file mode 100644 index 0ed609078..000000000 --- a/src/domains/LottoWinningFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -import Lotto from './Lotto.js'; - -class LottoWinningFactory { - #lottoNumberFactory; - - constructor(lottoNumberFactory) { - this.#lottoNumberFactory = lottoNumberFactory; - } - - createWinningLotto(numbers) { - const newNumbers = numbers.map((number) => this.#lottoNumberFactory.getLottoNumber(number)); - return new Lotto(newNumbers); - } - - createBonusLotto(number) { - return this.#lottoNumberFactory.getLottoNumber(number); - } -} - -export default LottoWinningFactory; diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index 3a917700c..81c5badfb 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -2,11 +2,10 @@ import LottoPrice from '../domains/LottoPrice.js'; import PurchasedLottosDto from '../dtos/responseDto/PurchasedLottosDto.js'; class LottoPurchaseService { - #lottoStore; #lottoRepository; - - constructor(lottoStore, lottoRepository) { - this.#lottoStore = lottoStore; + #randomLottoFactory; + constructor(randomLottoFactory, lottoRepository) { + this.#randomLottoFactory = randomLottoFactory; this.#lottoRepository = lottoRepository; } @@ -19,7 +18,7 @@ class LottoPurchaseService { getPurchasedLottos() { const { lottoPrice } = this.#lottoRepository.findAll('admin'); const lottoCount = lottoPrice.exchange(); - const lottos = this.#lottoStore.buyLotto(lottoCount); + const lottos = Array.from({ length: lottoCount }, () => this.#randomLottoFactory.createLotto()); this.#lottoRepository.save('admin', { lottos }); return new PurchasedLottosDto({ lottos }); } diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index b0daa7e47..1dd98c56c 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -1,19 +1,19 @@ import BonusNumberValidator from '../validators/domain/BonusNumberValidator.js'; import LottoWinningResult from '../domains/LottoWinningResult.js'; import WinningResultDto from '../dtos/responseDto/WinningResultDto.js'; +import Lotto from '../domains/Lotto.js'; +import LottoNumberFactory from '../domains/LottoNumberFactory.js'; class WinningResultService { - #winningFactory; #lottoRepository; - constructor(winningFactory, lottoRepository) { - this.#winningFactory = winningFactory; + constructor(lottoRepository) { this.#lottoRepository = lottoRepository; } saveWinningNumbers(requestDTO) { const { winningNumbers } = requestDTO; - const winningLotto = this.#winningFactory.createWinningLotto(winningNumbers); + const winningLotto = new Lotto(winningNumbers); this.#lottoRepository.save('admin', { winningLotto }); } @@ -21,7 +21,7 @@ class WinningResultService { const { bonusNumber } = requestDTO; const { winningLotto } = this.#lottoRepository.findAll('admin'); // 객체끼리의 비교를 위해 먼저 생성 - const bonusLotto = this.#winningFactory.createBonusLotto(bonusNumber); + const bonusLotto = LottoNumberFactory.getLottoNumber(bonusNumber); BonusNumberValidator.validate(winningLotto, bonusLotto); this.#lottoRepository.save('admin', { bonusLotto }); } From 734ebea5c81af2bb2d26c285b9f41958a95d7604 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 18:20:03 +0900 Subject: [PATCH 107/109] =?UTF-8?q?refactor(services):=20DB=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -실제 DB를 다루듯이 영속화를 지킴 --- src/domains/LottoPrice.js | 4 ++++ src/services/LottoPurchaseService.js | 9 ++++++--- src/services/WinningResultService.js | 15 +++++++++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/domains/LottoPrice.js b/src/domains/LottoPrice.js index d6379ce82..855cbbe76 100644 --- a/src/domains/LottoPrice.js +++ b/src/domains/LottoPrice.js @@ -45,5 +45,9 @@ class LottoPrice { if (totalWinningAmount === 0) return 0; return ((totalWinningAmount / this.#purchaseAmount) * 100).toFixed(1); } + + get purchaseAmount() { + return this.#purchaseAmount; + } } export default LottoPrice; diff --git a/src/services/LottoPurchaseService.js b/src/services/LottoPurchaseService.js index 81c5badfb..d50d73936 100644 --- a/src/services/LottoPurchaseService.js +++ b/src/services/LottoPurchaseService.js @@ -12,14 +12,17 @@ class LottoPurchaseService { savePurchaseAmount(requestDTO) { const { purchaseAmount } = requestDTO; const lottoPrice = new LottoPrice(purchaseAmount); - this.#lottoRepository.save('admin', { lottoPrice }); + this.#lottoRepository.save('admin', { purchaseAmount: lottoPrice.purchaseAmount }); } getPurchasedLottos() { - const { lottoPrice } = this.#lottoRepository.findAll('admin'); + const { purchaseAmount } = this.#lottoRepository.findAll('admin'); + const lottoPrice = new LottoPrice(purchaseAmount); const lottoCount = lottoPrice.exchange(); const lottos = Array.from({ length: lottoCount }, () => this.#randomLottoFactory.createLotto()); - this.#lottoRepository.save('admin', { lottos }); + + const insertLotto = lottos.map((lotto) => lotto.getNumbers()); + this.#lottoRepository.save('admin', { lottos: insertLotto }); return new PurchasedLottosDto({ lottos }); } } diff --git a/src/services/WinningResultService.js b/src/services/WinningResultService.js index 1dd98c56c..16df9607e 100644 --- a/src/services/WinningResultService.js +++ b/src/services/WinningResultService.js @@ -3,6 +3,7 @@ import LottoWinningResult from '../domains/LottoWinningResult.js'; import WinningResultDto from '../dtos/responseDto/WinningResultDto.js'; import Lotto from '../domains/Lotto.js'; import LottoNumberFactory from '../domains/LottoNumberFactory.js'; +import LottoPrice from '../domains/LottoPrice.js'; class WinningResultService { #lottoRepository; @@ -14,21 +15,27 @@ class WinningResultService { saveWinningNumbers(requestDTO) { const { winningNumbers } = requestDTO; const winningLotto = new Lotto(winningNumbers); - this.#lottoRepository.save('admin', { winningLotto }); + this.#lottoRepository.save('admin', { winningLotto: winningLotto.getNumbers() }); } saveBonusNumber(requestDTO) { const { bonusNumber } = requestDTO; - const { winningLotto } = this.#lottoRepository.findAll('admin'); + const db = this.#lottoRepository.findAll('admin'); + const winningLotto = new Lotto(db.winningLotto); + // 객체끼리의 비교를 위해 먼저 생성 const bonusLotto = LottoNumberFactory.getLottoNumber(bonusNumber); BonusNumberValidator.validate(winningLotto, bonusLotto); - this.#lottoRepository.save('admin', { bonusLotto }); + this.#lottoRepository.save('admin', { bonusLotto: bonusLotto.getNumber() }); } getWinningResult() { const db = this.#lottoRepository.findAll('admin'); - const { lottoPrice, lottos, winningLotto, bonusLotto } = db; + const lottoPrice = new LottoPrice(db.purchaseAmount); + const lottos = db.lottos.map((lotto) => new Lotto(lotto)); + const winningLotto = new Lotto(db.winningLotto); + const bonusLotto = LottoNumberFactory.getLottoNumber(db.bonusLotto); + const { winningStats, totalWinningAmount } = LottoWinningResult.getWinningStats( lottos, winningLotto, From 00c3cae0a5cced93d77654315c08e725245ef82e Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 19:32:34 +0900 Subject: [PATCH 108/109] =?UTF-8?q?chore(LottoRepository):=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/errorMessages.js | 1 + src/repositories/LottoRepository.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index 1416dc4f4..46e0dc7fe 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -2,6 +2,7 @@ const PREFIX = '[ERROR]'; const format = (unit) => new Intl.NumberFormat().format(unit); const ERROR_MESSAGES = Object.freeze({ + NOT_DATA: `저장된 데이터가 없습니다`, INTEGER: `${PREFIX}입력 값이 정수여야 합니다`, BLANK: `${PREFIX}입력 값이 비었습니다`, diff --git a/src/repositories/LottoRepository.js b/src/repositories/LottoRepository.js index 3fa87ed74..4d708f398 100644 --- a/src/repositories/LottoRepository.js +++ b/src/repositories/LottoRepository.js @@ -1,3 +1,5 @@ +import ERROR_MESSAGES from '../constants/errorMessages.js'; + class LottoRepository { #lottoDB; @@ -13,7 +15,7 @@ class LottoRepository { update(id, data) { if (!this.#lottoDB.has(id)) { - throw new Error('저장된 데이터가 없습니다'); + throw new Error(ERROR_MESSAGES.NOT_DATA); } const repoData = this.#lottoDB.get(id); const insertData = { ...repoData, ...data }; @@ -22,7 +24,7 @@ class LottoRepository { findAll(id) { if (!this.#lottoDB.has(id)) { - throw new Error('저장된 데이터가 없습니다'); + throw new Error(ERROR_MESSAGES.NOT_DATA); } return this.#lottoDB.get(id); } From 30d12c3d1c1110d835813c26074ccc79a8c10069 Mon Sep 17 00:00:00 2001 From: iftype Date: Mon, 3 Nov 2025 19:59:31 +0900 Subject: [PATCH 109/109] =?UTF-8?q?refactor(controller):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메서드를 다섯개로 나눔 --- src/App.js | 2 +- src/controllers/LottoController.js | 54 ++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/App.js b/src/App.js index 40dcdd9db..b00e7ffa3 100644 --- a/src/App.js +++ b/src/App.js @@ -33,7 +33,7 @@ class App { } async run() { - await this.#lottoController.processLottoPurchase(); + await this.#lottoController.runLotto(); } } diff --git a/src/controllers/LottoController.js b/src/controllers/LottoController.js index 1fc94cdaf..408ed2850 100644 --- a/src/controllers/LottoController.js +++ b/src/controllers/LottoController.js @@ -15,42 +15,68 @@ class LottoController { this.#lottoOutputView = lottoOutputView; } - async processLottoPurchase() { + async runLotto() { + try { + await this.#processPurchaseAmount(); + this.#processLottoPurchase(); + + await this.#processWinningNumbers(); + await this.#processBonusNumbers(); + this.#processWinningResult(); + return true; + } catch (err) { + this.#lottoOutputView.printError(err); + return false; + } + } + + async #processPurchaseAmount() { try { const purchaseAmount = await this.#lottoInputView.readPurchaseAmount(); const purchaseAmountDto = new PurchaseAmountDto(purchaseAmount); this.#lottoPurchaseService.savePurchaseAmount(purchaseAmountDto); - - const purchasedLottos = this.#lottoPurchaseService.getPurchasedLottos(); - const purchasedLottosDto = purchasedLottos.toJSON(); - this.#lottoOutputView.printPurchaseLottos(purchasedLottosDto); - return this.#processWinningResult(); + return true; } catch (err) { this.#lottoOutputView.printError(err); - return this.processLottoPurchase(); + return this.#processPurchaseAmount(); } } - async #processWinningResult() { + async #processWinningNumbers() { try { const winningNumbers = await this.#lottoInputView.readWinningNumbers(); const purchaseAmountDto = new WinningNumbersDto(winningNumbers); this.#winningResultService.saveWinningNumbers(purchaseAmountDto); + return true; + } catch (err) { + this.#lottoOutputView.printError(err); + return this.#processWinningNumbers(); + } + } + async #processBonusNumbers() { + try { const bonusNumber = await this.#lottoInputView.readBonusNumber(); const bonusNumberDto = new BonusNumberDto(bonusNumber); this.#winningResultService.saveBonusNumber(bonusNumberDto); - - const winningResult = this.#winningResultService.getWinningResult(); - const winningResultDto = winningResult.toJSON(); - this.#lottoOutputView.printWinningResult(winningResultDto); - return true; } catch (err) { this.#lottoOutputView.printError(err); - return this.#processWinningResult(); + return this.#processBonusNumbers(); } } + + #processLottoPurchase() { + const purchasedLottos = this.#lottoPurchaseService.getPurchasedLottos(); + const purchasedLottosDto = purchasedLottos.toJSON(); + this.#lottoOutputView.printPurchaseLottos(purchasedLottosDto); + } + + #processWinningResult() { + const winningResult = this.#winningResultService.getWinningResult(); + const winningResultDto = winningResult.toJSON(); + this.#lottoOutputView.printWinningResult(winningResultDto); + } } export default LottoController;