From 0b0211590be072e863a26db931623fb9980f789b Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 00:27:14 +0900 Subject: [PATCH 01/65] =?UTF-8?q?docs=20:=20=EA=B8=B0=EB=8A=A5=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=EC=9D=84=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13420b29..38d196a6 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# javascript-calculator-precourse \ No newline at end of file +# javascript-calculator-precourse + +# 문자열 덧셈 계산기 개발 + +## 1. 기능요구 사항 + +-[] 커스텀 구분자를 추출하는 기능 +-[] 구분자를 통해 입력받은 숫자를 파싱하는 기능 +-[] 파싱된 입력이 올바른지 확인하는 기능 +-[] 파싱된 입력을 더하는 기능 + +- Parser : 구분자 +- Number : 입력받은 숫자 +- Extraction : 추출자 + +### - 입력 요구사항 + +-[] 구분자와 야수로 구성된 문자열 +-[] 구분자 : `,` `:` `//커스텀구분자\n` + +### - 출력 요구사항 + +-[] "결과 : {int}" +-[] 잘못된 입력시 [ERROR]로 시작하는 메시지와 함께 종료 + +## 2. 프로그래밍 요구 사항 + +-[] 프로그래밍의 시작지점은 App.js의 run() 인가? +-[] package.json 파일을 변경하지 않았는가? +-[] 프로그램 종료시 process.exit()를 호출하지 않았는가? +-[] 자바스크립트 코드 컨벤션에 맞게 작성했는가? +-[] @woowacourse/mission-utils에서 제공하는 Console API를 사용해서 구현했는가? + +## 3. 도전 사항 + +-[] TDD 설계원칙 적용 +-[] 테스트 코드 랜덤 문자열 생성기 구현 +-[] MVC 패턴 적용하기 +-[] 객체지향 원칙 준수하기 + +## 4. 예상 실행 결과 + +실행 결과 예시 +덧셈할 문자열을 입력해 주세요. +1,2:3 +결과 : 6 From 100e4f2d6b8505cc1ed2a8a46d7267b4ec4375fe Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 12:20:32 +0900 Subject: [PATCH 02/65] =?UTF-8?q?docs:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EB=AC=B8=EC=84=9C=ED=99=94=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++++++ src/App.js | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38d196a6..1e8bf0d2 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,18 @@ -[] 파싱된 입력이 올바른지 확인하는 기능 -[] 파싱된 입력을 더하는 기능 +- Model - Parser : 구분자 - Number : 입력받은 숫자 - Extraction : 추출자 +- RandomMaker : 랜덤 제작기 + +- View +- InputView : 입력 +- OutputView : 출력 + +- Controller +- Calculator : 계산기 ### - 입력 요구사항 @@ -37,6 +46,7 @@ -[] 테스트 코드 랜덤 문자열 생성기 구현 -[] MVC 패턴 적용하기 -[] 객체지향 원칙 준수하기 +-[] AI 사용 없이 구현하기 ## 4. 예상 실행 결과 diff --git a/src/App.js b/src/App.js index 091aa0a5..3ee873fc 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,7 @@ class App { - async run() {} + async run() { + console.log('App is running'); + } } export default App; From 0811d15bb9c404ea7086607e16410c5cf6a87b0f Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 21:22:06 +0900 Subject: [PATCH 03/65] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 0 src/service/model/Extraction.js | 0 src/service/model/Number.js | 0 src/service/model/Parser.js | 0 src/service/model/RandomMaker.js | 0 src/view/InputView.js | 0 src/view/OutputView.js | 0 test/Extraction.test.js | 0 test/Number.test.js | 0 test/Parser.test.js | 0 test/RandomMaker.test.js | 0 11 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/controller/Controller.js create mode 100644 src/service/model/Extraction.js create mode 100644 src/service/model/Number.js create mode 100644 src/service/model/Parser.js create mode 100644 src/service/model/RandomMaker.js create mode 100644 src/view/InputView.js create mode 100644 src/view/OutputView.js create mode 100644 test/Extraction.test.js create mode 100644 test/Number.test.js create mode 100644 test/Parser.test.js create mode 100644 test/RandomMaker.test.js diff --git a/src/controller/Controller.js b/src/controller/Controller.js new file mode 100644 index 00000000..e69de29b diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js new file mode 100644 index 00000000..e69de29b diff --git a/src/service/model/Number.js b/src/service/model/Number.js new file mode 100644 index 00000000..e69de29b diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js new file mode 100644 index 00000000..e69de29b diff --git a/src/service/model/RandomMaker.js b/src/service/model/RandomMaker.js new file mode 100644 index 00000000..e69de29b diff --git a/src/view/InputView.js b/src/view/InputView.js new file mode 100644 index 00000000..e69de29b diff --git a/src/view/OutputView.js b/src/view/OutputView.js new file mode 100644 index 00000000..e69de29b diff --git a/test/Extraction.test.js b/test/Extraction.test.js new file mode 100644 index 00000000..e69de29b diff --git a/test/Number.test.js b/test/Number.test.js new file mode 100644 index 00000000..e69de29b diff --git a/test/Parser.test.js b/test/Parser.test.js new file mode 100644 index 00000000..e69de29b diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js new file mode 100644 index 00000000..e69de29b From affdc1393d9cbcf5d1027407048dc601f96b5a2f Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 21:32:44 +0900 Subject: [PATCH 04/65] =?UTF-8?q?test:=20=EB=84=98=EB=B2=84=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Number.js | 3 +++ test/Number.test.js | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/service/model/Number.js b/src/service/model/Number.js index e69de29b..531fbd44 100644 --- a/src/service/model/Number.js +++ b/src/service/model/Number.js @@ -0,0 +1,3 @@ +export default class Number { + #numbers = []; +} diff --git a/test/Number.test.js b/test/Number.test.js index e69de29b..753a0458 100644 --- a/test/Number.test.js +++ b/test/Number.test.js @@ -0,0 +1,8 @@ +import { Number } from '../src/service/model/Number'; + +describe('넘버 클래스를 테스트 하다', () => { + it('숫자를 더한다.', () => { + const numbers = Number([1, 2, 3]); + expect(numbers.sum()).toBe(6); + }); +}); From 7e295b1f5c95ef63a4d2e4bbd207c002f29bf252 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 21:38:30 +0900 Subject: [PATCH 05/65] =?UTF-8?q?feat:=20number=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20add=ED=95=A8=EC=88=98=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Number.js | 10 +++++++++- test/Number.test.js | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/service/model/Number.js b/src/service/model/Number.js index 531fbd44..1223e1e8 100644 --- a/src/service/model/Number.js +++ b/src/service/model/Number.js @@ -1,3 +1,11 @@ export default class Number { - #numbers = []; + #numbers; + + constructor(numbers) { + this.#numbers = numbers; + } + + getAddedNumbers() { + return this.#numbers.reduce((acc, cur) => acc + cur); + } } diff --git a/test/Number.test.js b/test/Number.test.js index 753a0458..a91a595c 100644 --- a/test/Number.test.js +++ b/test/Number.test.js @@ -1,8 +1,8 @@ -import { Number } from '../src/service/model/Number'; +import Number from '../src/service/model/Number.js'; describe('넘버 클래스를 테스트 하다', () => { it('숫자를 더한다.', () => { - const numbers = Number([1, 2, 3]); - expect(numbers.sum()).toBe(6); + const numbers = new Number([1, 2, 3]); + expect(numbers.getAddedNumbers()).toBe(6); }); }); From 66407c5b8bce743ff05ed02c0698182fe2e03569 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 21:43:38 +0900 Subject: [PATCH 06/65] =?UTF-8?q?test:=20parse=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 1 + test/Parser.test.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index e69de29b..318b4198 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -0,0 +1 @@ +export default class Parser {} diff --git a/test/Parser.test.js b/test/Parser.test.js index e69de29b..1e736838 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -0,0 +1,10 @@ +import Parser from '../src/service/model/Parser'; + +describe('Parser 클래스를 테스트 하다', () => { + const parser = new Parser(); + it('구분자와 입력을 받아 파싱을 하다', () => { + const regx = ['/', '|']; + const exInput = '/123/2|31'; + expect(parser.parseData(regx, exInput)).toBe([123, 2, 31]); + }); +}); From bafbdb0899ea72eb74937f3662110e36820ec1b9 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 22:10:40 +0900 Subject: [PATCH 07/65] =?UTF-8?q?feat:=20parse=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 10 +++++++++- test/Parser.test.js | 6 ++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 318b4198..a9ed0321 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1 +1,9 @@ -export default class Parser {} +export default class Parser { + parseData(regxs, inputText) { + const regex = new RegExp(`[${regxs.join('')}]`); + return inputText + .split(regex) + .filter((item) => item !== '') // 빈문자열 제거 + .map((item) => Number(item)); + } +} diff --git a/test/Parser.test.js b/test/Parser.test.js index 1e736838..fcdfb0ff 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -1,10 +1,12 @@ -import Parser from '../src/service/model/Parser'; +import Parser from '../src/service/model/Parser.js'; describe('Parser 클래스를 테스트 하다', () => { const parser = new Parser(); it('구분자와 입력을 받아 파싱을 하다', () => { const regx = ['/', '|']; const exInput = '/123/2|31'; - expect(parser.parseData(regx, exInput)).toBe([123, 2, 31]); + expect(parser.parseData(regx, exInput)).toEqual([123, 2, 31]); }); }); + +// issue 객체나 배열은 toEqual을 사용해야함... From dfa88c588b23c46afff6de14f420154c5f68602a Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 23:15:43 +0900 Subject: [PATCH 08/65] =?UTF-8?q?test:=20parser=20usecase=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 3 ++- test/Parser.test.js | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index a9ed0321..ea0f0665 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,6 +1,7 @@ export default class Parser { parseData(regxs, inputText) { - const regex = new RegExp(`[${regxs.join('')}]`); + const totalRegexs = [...regxs, ',', ':']; + const regex = new RegExp(`[${totalRegexs.join('')}]`); return inputText .split(regex) .filter((item) => item !== '') // 빈문자열 제거 diff --git a/test/Parser.test.js b/test/Parser.test.js index fcdfb0ff..dc164a20 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -3,10 +3,39 @@ import Parser from '../src/service/model/Parser.js'; describe('Parser 클래스를 테스트 하다', () => { const parser = new Parser(); it('구분자와 입력을 받아 파싱을 하다', () => { - const regx = ['/', '|']; - const exInput = '/123/2|31'; - expect(parser.parseData(regx, exInput)).toEqual([123, 2, 31]); + const regxs = ['/', '|']; + const inputText = '/123/2|31'; + expect(parser.parseData(regxs, inputText)).toEqual([123, 2, 31]); }); }); // issue 객체나 배열은 toEqual을 사용해야함... + +describe('Parser 클래스의 useCase를 추가하다', () => { + const parser = new Parser(); + + it('구분자는 기본적으로 , : 를 포함합니다', () => { + const regxs = []; + const inputText = '11:6,1'; + expect(parser.parseData(regxs, inputText)).toEqual([11, 6, 1]); + }); + + it('구분자가 숫자인경우', () => { + const regxs = ['1', '2', '3', '#']; + const inputText = '1142232#61'; + expect(parser.parseData(regxs, inputText)).toEqual([4, 6]); + }); + it('구분자에 \\ 이스케이프가 포함되는경우', () => { + const regxs = ['//\\n', '\\']; + const inputText = '//\\n1//\\n2//\\n3'; + expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); + }); + it('구분자가 여러글자 인경우', () => { + const regxs = ['ab', 'bc', 'cd']; + const inputText = 'ab1bc2cd3'; + expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); + }); +}); + +// issue \ 처리가 매우 까다로움 +// \\ 의 경우에는 From 48a1ab867289bb86fc801c465cbf1aa18922c192 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 23:27:47 +0900 Subject: [PATCH 09/65] =?UTF-8?q?test:=20=EB=B0=B1=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=ED=94=84=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Parser.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Parser.test.js b/test/Parser.test.js index dc164a20..e5b85625 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -26,7 +26,7 @@ describe('Parser 클래스의 useCase를 추가하다', () => { expect(parser.parseData(regxs, inputText)).toEqual([4, 6]); }); it('구분자에 \\ 이스케이프가 포함되는경우', () => { - const regxs = ['//\\n', '\\']; + const regxs = ['//\\\\n', '\\\\']; const inputText = '//\\n1//\\n2//\\n3'; expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); }); @@ -38,4 +38,6 @@ describe('Parser 클래스의 useCase를 추가하다', () => { }); // issue \ 처리가 매우 까다로움 -// \\ 의 경우에는 +// \\ 의 경우에는 \\\\ 로 처리해야한다 +// js에서 \는 \\로 작성해야하므로 +// 따라서 inputText의 \\는 \\\\로 처리해야하는것 From 9894e9ab7fe8d06f3f251a5f46b5c40631d2560a Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 23:38:39 +0900 Subject: [PATCH 10/65] =?UTF-8?q?test:=20Extraction=20test=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Extraction.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/Extraction.test.js b/test/Extraction.test.js index e69de29b..f81a463b 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -0,0 +1,14 @@ +// 추출기 테스트 + +import Extraction from '../src/service/model/Extraction'; + +describe('Extraction 클래스 테스트', () => { + it('//ㅌ\\n 사이의 커스텀 구분자 ㅌ을 추출한다', () => { + const extraction = new Extraction(); + expect(extraction.extractCustom()).toEqual(['ㅌ']); + }); + it('커스텀 구분자가 없다면 빈값으로 반환된다', () => { + const extraction = new Extraction(); + expect(extraction.extractCustom()).toEqual([]); + }); +}); From f1d73e6683f65e70bd3af0d4732d487e2d577a36 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 15 Oct 2025 23:46:45 +0900 Subject: [PATCH 11/65] =?UTF-8?q?test:=20test=EC=BD=94=EB=93=9C=EC=9D=98?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=EC=97=90=20input=20=EB=A7=A4=EA=B0=9C?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Extraction.js | 3 +++ test/Extraction.test.js | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index e69de29b..73641dc6 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -0,0 +1,3 @@ +export default class Extraction { + extractCustom(inputText) {} +} diff --git a/test/Extraction.test.js b/test/Extraction.test.js index f81a463b..411ac601 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -4,11 +4,13 @@ import Extraction from '../src/service/model/Extraction'; describe('Extraction 클래스 테스트', () => { it('//ㅌ\\n 사이의 커스텀 구분자 ㅌ을 추출한다', () => { + const inputText = 'e//ㅌ\\neasd123'; const extraction = new Extraction(); - expect(extraction.extractCustom()).toEqual(['ㅌ']); + expect(extraction.extractCustom(inputText)).toEqual(['ㅌ']); }); it('커스텀 구분자가 없다면 빈값으로 반환된다', () => { + const inputText = 'eneasd123'; const extraction = new Extraction(); - expect(extraction.extractCustom()).toEqual([]); + expect(extraction.extractCustom(inputText)).toEqual([]); }); }); From 0d7919d32c4d9503129c28c5036a919cf3a05ff5 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 08:59:34 +0900 Subject: [PATCH 12/65] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=20=EC=B6=94=EC=B6=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Extraction.js | 10 +++++++++- test/Extraction.test.js | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index 73641dc6..f492aa5c 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -1,3 +1,11 @@ +const regex = /\/\/(.*?)\\n/; + export default class Extraction { - extractCustom(inputText) {} + extractCustom(inputText) { + const match = inputText.match(regex); + if (match) { + return [match[1]]; + } + return []; + } } diff --git a/test/Extraction.test.js b/test/Extraction.test.js index 411ac601..b5f3d0ed 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -1,6 +1,6 @@ // 추출기 테스트 -import Extraction from '../src/service/model/Extraction'; +import Extraction from '../src/service/model/Extraction.js'; describe('Extraction 클래스 테스트', () => { it('//ㅌ\\n 사이의 커스텀 구분자 ㅌ을 추출한다', () => { From b3aba354be5a15e0764429535961d3eecb4cd615 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 12:34:44 +0900 Subject: [PATCH 13/65] =?UTF-8?q?feat:=20input,output=20View=EB=A5=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 6 +++++- src/view/InputView.js | 9 +++++++++ src/view/OutputView.js | 8 ++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 3ee873fc..7f2a8361 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,10 @@ +import inputView from './view/InputView.js'; +import outputView from './view/OutputView.js'; + class App { async run() { - console.log('App is running'); + const input = inputView.readLineMesage('Hello World'); + outputView.printMessage(input); } } diff --git a/src/view/InputView.js b/src/view/InputView.js index e69de29b..191be50f 100644 --- a/src/view/InputView.js +++ b/src/view/InputView.js @@ -0,0 +1,9 @@ +import { Console } from '@woowacourse/mission-utils'; + +const inputView = { + async readLineMesage(message) { + const input = await Console.readLineAsync(message); + return input; + }, +}; +export default inputView; diff --git a/src/view/OutputView.js b/src/view/OutputView.js index e69de29b..8bdb5043 100644 --- a/src/view/OutputView.js +++ b/src/view/OutputView.js @@ -0,0 +1,8 @@ +import { Console } from '@woowacourse/mission-utils'; + +const outputView = { + printMessage(message) { + Console.print(message); + }, +}; +export default outputView; From edeb9540ac9540be5525a893be7f3c1252fa3904 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 20:26:20 +0900 Subject: [PATCH 14/65] =?UTF-8?q?feat:=20contooler=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B4=88=EA=B8=B0=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 1 + src/controller/Controller.js | 9 +++++++++ src/service/validate/validate.js | 6 ++++++ test/Contoller.test.js | 0 4 files changed, 16 insertions(+) create mode 100644 src/service/validate/validate.js create mode 100644 test/Contoller.test.js diff --git a/src/App.js b/src/App.js index 7f2a8361..f5e85b4d 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import outputView from './view/OutputView.js'; class App { async run() { const input = inputView.readLineMesage('Hello World'); + outputView.printMessage(input); } } diff --git a/src/controller/Controller.js b/src/controller/Controller.js index e69de29b..34a62307 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -0,0 +1,9 @@ +import Extraction from '../service/model/Extraction.js'; +import Parser from '../service/model/Parser.js'; + +export default class Controller { + constructor() { + const extraction = new Extraction(); + const parser = new Parser(); + } +} diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js new file mode 100644 index 00000000..a48be018 --- /dev/null +++ b/src/service/validate/validate.js @@ -0,0 +1,6 @@ +const validate = { + isNumber(input) { + return !Number.isNaN(input); + }, +}; +export default validate; diff --git a/test/Contoller.test.js b/test/Contoller.test.js new file mode 100644 index 00000000..e69de29b From 8675344c7674bd0d7304356f407917c4b692f155 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 20:35:47 +0900 Subject: [PATCH 15/65] =?UTF-8?q?test:=20controller=EC=9D=98=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Contoller.test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Contoller.test.js b/test/Contoller.test.js index e69de29b..a11ebb26 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -0,0 +1,17 @@ +import { Console } from '@woowacourse/mission-utils'; +import Controller from '../src/controller/Controller.js'; + +jest.mock('../src/view/InputView', () => ({ + default: { + readLineMessage: jest.fn(() => ''), + }, +})); + +describe('Controller 클래스 E2E 테스트', () => { + it('입력 : 1,2:3 | 출력 : 결과 : 6', () => { + const controller = new Controller(); + controller.run(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + expect(logSpy).toBe('결과 : 6'); + }); +}); From 953a6b1ab78424c57fe97e0f15771eda8cfb270c Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 20:44:53 +0900 Subject: [PATCH 16/65] =?UTF-8?q?feat:=20contooler=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20run=ED=95=A8=EC=88=98=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 16 ++++++++++++++-- src/view/InputView.js | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 34a62307..91992e58 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -1,9 +1,21 @@ import Extraction from '../service/model/Extraction.js'; import Parser from '../service/model/Parser.js'; +import inputView from '../view/InputView.js'; +import outputView from '../view/OutputView.js'; +import Number from '../service/model/Number.js'; export default class Controller { constructor() { - const extraction = new Extraction(); - const parser = new Parser(); + this.extraction = new Extraction(); + this.parser = new Parser(); + } + + async run() { + const input = + await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); + const customRegexs = this.extraction.extractCustom(input); + const parsedNumber = this.parser.parseData(customRegexs, input); + const numbers = new Number(parsedNumber); + outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } } diff --git a/src/view/InputView.js b/src/view/InputView.js index 191be50f..2b047c96 100644 --- a/src/view/InputView.js +++ b/src/view/InputView.js @@ -1,7 +1,7 @@ import { Console } from '@woowacourse/mission-utils'; const inputView = { - async readLineMesage(message) { + async readLineMessage(message) { const input = await Console.readLineAsync(message); return input; }, From 13d5e5f84fb38520f1669469498fd975a98fafa7 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 21:59:32 +0900 Subject: [PATCH 17/65] =?UTF-8?q?test:=20controller=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=AA=A8=ED=82=B9=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 1 - src/view/OutputView.js | 4 ++-- test/Contoller.test.js | 14 +++++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/App.js b/src/App.js index f5e85b4d..7f2a8361 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,6 @@ import outputView from './view/OutputView.js'; class App { async run() { const input = inputView.readLineMesage('Hello World'); - outputView.printMessage(input); } } diff --git a/src/view/OutputView.js b/src/view/OutputView.js index 8bdb5043..3a6e9ab4 100644 --- a/src/view/OutputView.js +++ b/src/view/OutputView.js @@ -1,8 +1,8 @@ import { Console } from '@woowacourse/mission-utils'; const outputView = { - printMessage(message) { - Console.print(message); + async printMessage(message) { + await Console.print(message); }, }; export default outputView; diff --git a/test/Contoller.test.js b/test/Contoller.test.js index a11ebb26..eec0a76e 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -1,17 +1,21 @@ import { Console } from '@woowacourse/mission-utils'; import Controller from '../src/controller/Controller.js'; -jest.mock('../src/view/InputView', () => ({ +// __esModule을 추가해야지 Babel 트랜스파일된 코드에서 ESM의 default를 붙여준 거라, mock 구조가 ESM 형식에 맞게 설정 +jest.mock('../src/view/InputView.js', () => ({ + __esModule: true, default: { - readLineMessage: jest.fn(() => ''), + readLineMessage: jest.fn(() => '1,2:3'), }, })); +// Received: [Function mockConstructor] +// -> logSpy는 문자열이 아니라 jest mock 함수이므로 toHaveBeenCalledWith를 describe('Controller 클래스 E2E 테스트', () => { - it('입력 : 1,2:3 | 출력 : 결과 : 6', () => { + it('입력 : 1,2:3 | 출력 : 결과 : 6', async () => { const controller = new Controller(); - controller.run(); const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); - expect(logSpy).toBe('결과 : 6'); + await controller.run(); + expect(logSpy).toHaveBeenCalledWith('결과 : 6'); }); }); From 3c0be912229ee0a4c97268cbed163848fad68113 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 22:02:30 +0900 Subject: [PATCH 18/65] =?UTF-8?q?feat:=20contooler=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20run=20=ED=95=A8=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 7f2a8361..4c829d47 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,9 @@ -import inputView from './view/InputView.js'; -import outputView from './view/OutputView.js'; +import Controller from './controller/Controller.js'; class App { async run() { - const input = inputView.readLineMesage('Hello World'); - outputView.printMessage(input); + const controller = new Controller(); + controller.run(); } } From d7c9eda599df4400c73194f35f978311080d189c Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 22:20:11 +0900 Subject: [PATCH 19/65] =?UTF-8?q?test:=20validate=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=95=EC=9D=98=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/validate.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/validate.test.js diff --git a/test/validate.test.js b/test/validate.test.js new file mode 100644 index 00000000..70e1c5a2 --- /dev/null +++ b/test/validate.test.js @@ -0,0 +1,9 @@ +// 잘못된 입력에 대한 검증 + +describe('잘못된 입력에 대한 검증', () => { + it('연속된 구분자가 입력된 경우', () => {}); + it('너무 긴 메시지기가 입력된 경우', () => {}); + it('숫자가 아닌값이 포함된 경우'); + it('빈 입력인경우 ""', () => {}); + it('공백 입력인경우 " "', () => {}); +}); From c69a58b5ad2f3d31bf3165d25d6772ef8b2004d6 Mon Sep 17 00:00:00 2001 From: manNomi Date: Thu, 16 Oct 2025 22:30:57 +0900 Subject: [PATCH 20/65] =?UTF-8?q?test:=20validate=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=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/validate/validate.js | 4 ++-- test/validate.test.js | 30 +++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index a48be018..67584ae7 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,6 +1,6 @@ const validate = { - isNumber(input) { - return !Number.isNaN(input); + isNumbers(numbers) { + return !Number.isNaN(numbers); }, }; export default validate; diff --git a/test/validate.test.js b/test/validate.test.js index 70e1c5a2..060f2dd2 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -1,9 +1,29 @@ // 잘못된 입력에 대한 검증 +import validate from '../src/service/validate/validate.js'; describe('잘못된 입력에 대한 검증', () => { - it('연속된 구분자가 입력된 경우', () => {}); - it('너무 긴 메시지기가 입력된 경우', () => {}); - it('숫자가 아닌값이 포함된 경우'); - it('빈 입력인경우 ""', () => {}); - it('공백 입력인경우 " "', () => {}); + // 파싱 전 - 예외 + it('연속된 구분자가 입력된 경우 ERROR', () => { + const input = ',,123,123'; + expect(validate.isRegexError(input)).toThrow('[ERROR]'); + }); + it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { + const input = + '1234567891011122312312312312312312312312321312312312312312312313'; + expect(validate.isLong(input)).toThrow('[ERROR]'); + }); + + // 파싱 후 - 예외 + it('숫자가 아닌값이 포함된 경우 ERROR', () => { + const parseInput = ['우', '테', '코', '1']; + expect(validate.isNumbers(parseInput)).toThrow('[ERROR]'); + }); + it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { + const parseInput = ['', '1', '2', '1']; + expect(validate.isNumbers(parseInput)).not.toThrow('[ERROR]'); + }); + it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { + const parseInput = [' ', '1', ' ', '1']; + expect(validate.isNumbers(parseInput)).not.toThrow('[ERROR]'); + }); }); From 5e7598b2403cccb9b3aa09dca8f8d5689e7d055a Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:41:34 +0900 Subject: [PATCH 21/65] =?UTF-8?q?docs:=20=EC=9D=8C=EC=88=98=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EC=98=88=EC=99=B8=EC=82=AC=ED=95=AD=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1e8bf0d2..7c6caeea 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ -[] 구분자와 야수로 구성된 문자열 -[] 구분자 : `,` `:` `//커스텀구분자\n` +#### - 추가사항 + +-[] 음수 입력시 ERROR 처리 + ### - 출력 요구사항 -[] "결과 : {int}" From 979cd6504a059befdc5e6bb819b7dee5e2412909 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:48:55 +0900 Subject: [PATCH 22/65] =?UTF-8?q?test:=20=EC=9D=8C=EC=88=98=20=EB=AF=B8?= =?UTF-8?q?=ED=8F=AC=ED=95=A8=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/validate.test.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/validate.test.js b/test/validate.test.js index 060f2dd2..741dd231 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -16,14 +16,19 @@ describe('잘못된 입력에 대한 검증', () => { // 파싱 후 - 예외 it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', '1']; - expect(validate.isNumbers(parseInput)).toThrow('[ERROR]'); + expect(validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { const parseInput = ['', '1', '2', '1']; - expect(validate.isNumbers(parseInput)).not.toThrow('[ERROR]'); + expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { const parseInput = [' ', '1', ' ', '1']; - expect(validate.isNumbers(parseInput)).not.toThrow('[ERROR]'); + expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + }); + + it('음수가 입력되는 경우 ERROR', () => { + const parseInput = [' ', '1', ' ', '1']; + expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); }); From 3e7b889233ed6396553fa8a483237e6594570755 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:49:09 +0900 Subject: [PATCH 23/65] =?UTF-8?q?fix:=20=EC=9D=8C=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/validate/validate.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 67584ae7..5b97a8ac 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,6 +1,9 @@ const validate = { - isNumbers(numbers) { - return !Number.isNaN(numbers); + isIntegers(numbers) { + numbers.forEach((number) => { + if (Number.isNaN(number)) throw new Error('[ERROR]'); + if (number <= 0) throw new Error('[ERROR]'); + }); }, }; export default validate; From ce20c3fd3e55fbf53f3448ab6b42bb4706416aa1 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:50:32 +0900 Subject: [PATCH 24/65] =?UTF-8?q?feat:=20isLong=ED=95=A8=EC=88=98=EB=A5=BC?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/validate/validate.js | 5 +++++ test/validate.test.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 5b97a8ac..89841ffa 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -5,5 +5,10 @@ const validate = { if (number <= 0) throw new Error('[ERROR]'); }); }, + isLong(numbers) { + if (numbers.length >= 50) { + throw new Error('[ERROR]'); + } + }, }; export default validate; diff --git a/test/validate.test.js b/test/validate.test.js index 741dd231..02235916 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -29,6 +29,6 @@ describe('잘못된 입력에 대한 검증', () => { it('음수가 입력되는 경우 ERROR', () => { const parseInput = [' ', '1', ' ', '1']; - expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + expect(validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); }); From 8286f1ef5e40bf16a1a8a1f135ad1b28db8c2c0e Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:54:53 +0900 Subject: [PATCH 25/65] =?UTF-8?q?fix:=20=ED=95=A8=EC=88=98=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/validate.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/validate.test.js b/test/validate.test.js index 02235916..f9e5876c 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -5,30 +5,30 @@ describe('잘못된 입력에 대한 검증', () => { // 파싱 전 - 예외 it('연속된 구분자가 입력된 경우 ERROR', () => { const input = ',,123,123'; - expect(validate.isRegexError(input)).toThrow('[ERROR]'); + expect(() => validate.isRegexError(input)).toThrow('[ERROR]'); }); it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { const input = '1234567891011122312312312312312312312312321312312312312312312313'; - expect(validate.isLong(input)).toThrow('[ERROR]'); + expect(() => validate.isLong(input)).toThrow('[ERROR]'); }); // 파싱 후 - 예외 it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', '1']; - expect(validate.isIntegers(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { const parseInput = ['', '1', '2', '1']; - expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { const parseInput = [' ', '1', ' ', '1']; - expect(validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); it('음수가 입력되는 경우 ERROR', () => { const parseInput = [' ', '1', ' ', '1']; - expect(validate.isIntegers(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); }); From a23f88bb3399de69e6e0e0a2d53520efbaf8cc4e Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 08:57:17 +0900 Subject: [PATCH 26/65] =?UTF-8?q?fix:=20isIntegers=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/validate/validate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 89841ffa..d5ced27a 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,7 +1,8 @@ const validate = { isIntegers(numbers) { numbers.forEach((number) => { - if (Number.isNaN(number)) throw new Error('[ERROR]'); + if (number.trim() === '') return; + if (Number.isNaN(Number(number))) throw new Error('[ERROR]'); if (number <= 0) throw new Error('[ERROR]'); }); }, From 123ea27259130844f4a0be4eea65a960d79d88ae Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 12:20:42 +0900 Subject: [PATCH 27/65] =?UTF-8?q?feat:=20validate=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 5 +++++ test/validate.test.js | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 91992e58..cd01f6d7 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -3,6 +3,7 @@ import Parser from '../service/model/Parser.js'; import inputView from '../view/InputView.js'; import outputView from '../view/OutputView.js'; import Number from '../service/model/Number.js'; +import validate from '../service/validate/validate.js'; export default class Controller { constructor() { @@ -13,8 +14,12 @@ export default class Controller { async run() { const input = await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); + validate.isLong(input); + const customRegexs = this.extraction.extractCustom(input); const parsedNumber = this.parser.parseData(customRegexs, input); + validate.isIntegers(parsedNumber); + const numbers = new Number(parsedNumber); outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } diff --git a/test/validate.test.js b/test/validate.test.js index f9e5876c..d1f39001 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -3,17 +3,13 @@ import validate from '../src/service/validate/validate.js'; describe('잘못된 입력에 대한 검증', () => { // 파싱 전 - 예외 - it('연속된 구분자가 입력된 경우 ERROR', () => { - const input = ',,123,123'; - expect(() => validate.isRegexError(input)).toThrow('[ERROR]'); - }); + it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { const input = '1234567891011122312312312312312312312312321312312312312312312313'; expect(() => validate.isLong(input)).toThrow('[ERROR]'); }); - // 파싱 후 - 예외 it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', '1']; expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); @@ -28,7 +24,7 @@ describe('잘못된 입력에 대한 검증', () => { }); it('음수가 입력되는 경우 ERROR', () => { - const parseInput = [' ', '1', ' ', '1']; + const parseInput = ['-1', '1', ' ', '1']; expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); }); From 5691a8a9400f0d632b9a170841654d8e73ced5bc Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 12:25:28 +0900 Subject: [PATCH 28/65] =?UTF-8?q?test:=20validate=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=EC=97=90=EC=84=9C=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20string=EC=9C=BC=EB=A1=9C=20=EA=B0=90?= =?UTF-8?q?=EC=8B=B8=EC=A7=84=20=EC=98=A4=ED=83=80=EB=A5=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Parser.test.js | 5 +++++ test/validate.test.js | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/Parser.test.js b/test/Parser.test.js index e5b85625..afa93a54 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -35,6 +35,11 @@ describe('Parser 클래스의 useCase를 추가하다', () => { const inputText = 'ab1bc2cd3'; expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); }); + + it('음수가 포함되는 경우', () => { + const inputText = '-1,2,3'; + expect(parser.parseData([], inputText)).toEqual([-1, 2, 3]); + }); }); // issue \ 처리가 매우 까다로움 diff --git a/test/validate.test.js b/test/validate.test.js index d1f39001..980eb4e9 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -11,20 +11,20 @@ describe('잘못된 입력에 대한 검증', () => { }); it('숫자가 아닌값이 포함된 경우 ERROR', () => { - const parseInput = ['우', '테', '코', '1']; + const parseInput = ['우', '테', '코', 1]; expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { - const parseInput = ['', '1', '2', '1']; + const parseInput = ['', 1, 2, 1]; expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { - const parseInput = [' ', '1', ' ', '1']; + const parseInput = [' ', 1, ' ', 1]; expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); }); it('음수가 입력되는 경우 ERROR', () => { - const parseInput = ['-1', '1', ' ', '1']; + const parseInput = [-1, 2, 3]; expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); }); }); From f4c7145f44a06aaea433dfea9f868e4bb6955a1b Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 12:28:29 +0900 Subject: [PATCH 29/65] =?UTF-8?q?fix:=20=EC=9D=8C=EC=88=98=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/validate/validate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index d5ced27a..37e3307a 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,7 +1,7 @@ const validate = { isIntegers(numbers) { numbers.forEach((number) => { - if (number.trim() === '') return; + if (typeof number === 'string' && number.trim() === '') return; if (Number.isNaN(Number(number))) throw new Error('[ERROR]'); if (number <= 0) throw new Error('[ERROR]'); }); From 0434003e001e672f58b4adfb6afc8502548b2267 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 12:59:37 +0900 Subject: [PATCH 30/65] =?UTF-8?q?test:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EC=A0=95=EC=9D=98=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 20 ++++++++++---------- src/controller/Controller.js | 2 +- test/Contoller.test.js | 29 +++++++++++++++++++++++++++-- test/Parser.test.js | 12 ++++++++++++ 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 7c6962dd..6cbe8579 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 { MissionUtils } from '@woowacourse/mission-utils'; +import App from '../src/App.js'; const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -11,18 +11,18 @@ const mockQuestions = (inputs) => { }; const getLogSpy = () => { - const logSpy = jest.spyOn(MissionUtils.Console, "print"); + const logSpy = jest.spyOn(MissionUtils.Console, 'print'); logSpy.mockClear(); return logSpy; }; -describe("문자열 계산기", () => { - test("커스텀 구분자 사용", async () => { - const inputs = ["//;\\n1"]; +describe('문자열 계산기', () => { + test('커스텀 구분자 사용', async () => { + const inputs = ['//;\\n1']; mockQuestions(inputs); const logSpy = getLogSpy(); - const outputs = ["결과 : 1"]; + const outputs = ['결과 : 1']; const app = new App(); await app.run(); @@ -32,12 +32,12 @@ describe("문자열 계산기", () => { }); }); - test("예외 테스트", async () => { - const inputs = ["-1,2,3"]; + test('예외 테스트', async () => { + const inputs = ['-1,2,3']; mockQuestions(inputs); const app = new App(); - await expect(app.run()).rejects.toThrow("[ERROR]"); + await expect(app.run()).rejects.toThrow('[ERROR]'); }); }); diff --git a/src/controller/Controller.js b/src/controller/Controller.js index cd01f6d7..02f5dc71 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -21,6 +21,6 @@ export default class Controller { validate.isIntegers(parsedNumber); const numbers = new Number(parsedNumber); - outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); + await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } } diff --git a/test/Contoller.test.js b/test/Contoller.test.js index eec0a76e..00b5e64b 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -1,21 +1,46 @@ import { Console } from '@woowacourse/mission-utils'; import Controller from '../src/controller/Controller.js'; +import inputView from '../src/view/InputView.js'; -// __esModule을 추가해야지 Babel 트랜스파일된 코드에서 ESM의 default를 붙여준 거라, mock 구조가 ESM 형식에 맞게 설정 jest.mock('../src/view/InputView.js', () => ({ __esModule: true, default: { - readLineMessage: jest.fn(() => '1,2:3'), + readLineMessage: jest.fn(), }, })); +// __esModule을 추가해야지 Babel 트랜스파일된 코드에서 ESM의 default를 붙여준 거라, mock 구조가 ESM 형식에 맞게 설정 + // Received: [Function mockConstructor] // -> logSpy는 문자열이 아니라 jest mock 함수이므로 toHaveBeenCalledWith를 describe('Controller 클래스 E2E 테스트', () => { it('입력 : 1,2:3 | 출력 : 결과 : 6', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '1,2:3'); const controller = new Controller(); const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); await controller.run(); expect(logSpy).toHaveBeenCalledWith('결과 : 6'); }); + + it('음수 입력시 ERROR 반환', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '-1,2:3'); + const controller = new Controller(); + await expect(controller.run()).rejects.toThrow('[ERROR]'); + }); + + it('입력이 한개인 경우 -1,', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '1,'); + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + await controller.run(); + expect(logSpy).toHaveBeenCalledWith('결과 : 1'); + }); + + it('//;\\n1 커스텀 구분자 사용', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '//;\\n1'); + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + await controller.run(); + expect(logSpy).toHaveBeenCalledWith('결과 : 1'); + }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index afa93a54..54bff80a 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -40,6 +40,18 @@ describe('Parser 클래스의 useCase를 추가하다', () => { const inputText = '-1,2,3'; expect(parser.parseData([], inputText)).toEqual([-1, 2, 3]); }); + + it('커스텀 구분자가 포함이 되는경우 //;\\n1', () => { + const regxs = [';']; + const inputText = '//;\\n1'; + expect(parser.parseData(regxs, inputText)).toEqual([1]); + }); + + it('커스텀 구분자가 처음 적히지 않은경우 ㅅ//;\\n1', () => { + const regxs = [';']; + const inputText = ',//;\\n1'; + expect(parser.parseData(regxs, inputText)).toEqual([1]); + }); }); // issue \ 처리가 매우 까다로움 From 70bd6d4320a5766a0c376b1a774a5616d761049f Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 23:29:01 +0900 Subject: [PATCH 31/65] =?UTF-8?q?test:=20=EB=94=94=EB=B2=84=EA=B9=85?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EC=B6=94=EC=B6=9C=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=A7=84=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 2 +- src/controller/Controller.js | 21 +++++++++++++-------- test/Extraction.test.js | 5 +++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/App.js b/src/App.js index 4c829d47..b99509d6 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,7 @@ import Controller from './controller/Controller.js'; class App { async run() { const controller = new Controller(); - controller.run(); + await controller.run(); } } diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 02f5dc71..3c53f7db 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -12,15 +12,20 @@ export default class Controller { } async run() { - const input = - await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); - validate.isLong(input); + try { + const input = + await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); + validate.isLong(input); - const customRegexs = this.extraction.extractCustom(input); - const parsedNumber = this.parser.parseData(customRegexs, input); - validate.isIntegers(parsedNumber); + const customRegexs = this.extraction.extractCustom(input); + const parsedNumber = this.parser.parseData(customRegexs, input); + validate.isIntegers(parsedNumber); - const numbers = new Number(parsedNumber); - await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); + const numbers = new Number(parsedNumber); + await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); + } catch (error) { + await outputView.printMessage(`${error.message}`); + throw new Error(error.message); + } } } diff --git a/test/Extraction.test.js b/test/Extraction.test.js index b5f3d0ed..e5daf42c 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -13,4 +13,9 @@ describe('Extraction 클래스 테스트', () => { const extraction = new Extraction(); expect(extraction.extractCustom(inputText)).toEqual([]); }); + it('커스텀 구분자 한개를 테스트하다 //;\\n1', () => { + const inputText = '//;\\n1'; + const extraction = new Extraction(); + expect(extraction.extractCustom(inputText)).toEqual([';']); + }); }); From cd1b854ecb766d42904f0270642186286058437f Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 23:31:50 +0900 Subject: [PATCH 32/65] =?UTF-8?q?test:=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Parser.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Parser.test.js b/test/Parser.test.js index 54bff80a..f525645a 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -41,6 +41,11 @@ describe('Parser 클래스의 useCase를 추가하다', () => { expect(parser.parseData([], inputText)).toEqual([-1, 2, 3]); }); + it('공백이 포함되는 경우', () => { + const inputText = '-1,,3'; + expect(parser.parseData([], inputText)).toEqual([-1, 0, 3]); + }); + it('커스텀 구분자가 포함이 되는경우 //;\\n1', () => { const regxs = [';']; const inputText = '//;\\n1'; From 24d25579f29b558be9337b092f2090ebf88012a5 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 17 Oct 2025 23:32:10 +0900 Subject: [PATCH 33/65] =?UTF-8?q?fix:=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index ea0f0665..173bd25d 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -2,9 +2,9 @@ export default class Parser { parseData(regxs, inputText) { const totalRegexs = [...regxs, ',', ':']; const regex = new RegExp(`[${totalRegexs.join('')}]`); - return inputText - .split(regex) - .filter((item) => item !== '') // 빈문자열 제거 - .map((item) => Number(item)); + return inputText.split(regex).map((item) => { + if (item === '') return 0; + return Number(item); + }); } } From cfa41e44adb4bafd5666d0d2abf0f3a0e4ff7a33 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 00:27:08 +0900 Subject: [PATCH 34/65] =?UTF-8?q?fix:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=EB=A5=BC=20=EC=97=AC=EB=9F=AC?= =?UTF-8?q?=EA=B0=9C=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Extraction.js | 9 +++------ src/service/model/Parser.js | 9 +++++---- test/Extraction.test.js | 6 ++++++ test/Parser.test.js | 13 ++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index f492aa5c..b771c42a 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -1,11 +1,8 @@ -const regex = /\/\/(.*?)\\n/; +const regex = /\/\/(.*?)\\n/g; export default class Extraction { extractCustom(inputText) { - const match = inputText.match(regex); - if (match) { - return [match[1]]; - } - return []; + const matches = Array.from(inputText.matchAll(regex), (match) => match[1]); + return matches; } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 173bd25d..8eeba6ea 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -2,9 +2,10 @@ export default class Parser { parseData(regxs, inputText) { const totalRegexs = [...regxs, ',', ':']; const regex = new RegExp(`[${totalRegexs.join('')}]`); - return inputText.split(regex).map((item) => { - if (item === '') return 0; - return Number(item); - }); + + return inputText + .split(regex) + .filter((item) => item !== '') // 빈문자열 제거 + .map((item) => Number(item)); } } diff --git a/test/Extraction.test.js b/test/Extraction.test.js index e5daf42c..0d92075c 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -18,4 +18,10 @@ describe('Extraction 클래스 테스트', () => { const extraction = new Extraction(); expect(extraction.extractCustom(inputText)).toEqual([';']); }); + + it('커스텀 구분자 두개를 테스트하다 //;\\n1', () => { + const inputText = '//;\\n1,//ㅁ\\n1'; + const extraction = new Extraction(); + expect(extraction.extractCustom(inputText)).toEqual([';', 'ㅁ']); + }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index f525645a..baa64952 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -22,12 +22,12 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('구분자가 숫자인경우', () => { const regxs = ['1', '2', '3', '#']; - const inputText = '1142232#61'; - expect(parser.parseData(regxs, inputText)).toEqual([4, 6]); + const inputText = '66166266366#66'; + expect(parser.parseData(regxs, inputText)).toEqual([66, 66, 66, 66, 66]); }); it('구분자에 \\ 이스케이프가 포함되는경우', () => { - const regxs = ['//\\\\n', '\\\\']; - const inputText = '//\\n1//\\n2//\\n3'; + const regxs = ['', '\\\\']; + const inputText = '\\1\\2\\3'; expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); }); it('구분자가 여러글자 인경우', () => { @@ -43,13 +43,12 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('공백이 포함되는 경우', () => { const inputText = '-1,,3'; - expect(parser.parseData([], inputText)).toEqual([-1, 0, 3]); + expect(parser.parseData([], inputText)).toEqual([-1, 3]); }); it('커스텀 구분자가 포함이 되는경우 //;\\n1', () => { - const regxs = [';']; const inputText = '//;\\n1'; - expect(parser.parseData(regxs, inputText)).toEqual([1]); + expect(parser.parseData([';'], inputText)).toEqual([1]); }); it('커스텀 구분자가 처음 적히지 않은경우 ㅅ//;\\n1', () => { From 2f8b73d5d3f794a05670475168a92e9591f637d6 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 00:31:27 +0900 Subject: [PATCH 35/65] =?UTF-8?q?fix:=20parser=EA=B0=80=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=EA=B5=AC=EB=B6=84=EC=9E=90=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 8eeba6ea..d52866ce 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,9 +1,17 @@ export default class Parser { - parseData(regxs, inputText) { - const totalRegexs = [...regxs, ',', ':']; + parseData(regexs, inputText) { + let inputResult = inputText; + + // 커스텀 구분자가 포함되는경우 양식대로 파싱 + regexs.forEach((regex) => { + const splitType = `//${regex}\\n`; + inputResult = inputText.split(splitType).join(''); + }); + + const totalRegexs = [...regexs, ',', ':']; const regex = new RegExp(`[${totalRegexs.join('')}]`); - return inputText + return inputResult .split(regex) .filter((item) => item !== '') // 빈문자열 제거 .map((item) => Number(item)); From 7725021437a94a4e90b75512ce5228ba8d33a5f9 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 16:46:39 +0900 Subject: [PATCH 36/65] =?UTF-8?q?fix:=20isLong=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 3 +- src/service/model/RandomMaker.js | 0 src/service/validate/validate.js | 11 +++-- test/RandomMaker.test.js | 80 ++++++++++++++++++++++++++++++++ test/validate.test.js | 10 ++-- 5 files changed, 93 insertions(+), 11 deletions(-) delete mode 100644 src/service/model/RandomMaker.js diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 3c53f7db..bdef980c 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -15,7 +15,8 @@ export default class Controller { try { const input = await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); - validate.isLong(input); + + // validate.isLong(input); const customRegexs = this.extraction.extractCustom(input); const parsedNumber = this.parser.parseData(customRegexs, input); diff --git a/src/service/model/RandomMaker.js b/src/service/model/RandomMaker.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 37e3307a..337cfa8d 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -6,10 +6,11 @@ const validate = { if (number <= 0) throw new Error('[ERROR]'); }); }, - isLong(numbers) { - if (numbers.length >= 50) { - throw new Error('[ERROR]'); - } - }, + // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 + // isLong(numbers) { + // if (numbers.length >= 50) { + // throw new Error('[ERROR]'); + // } + // }, }; export default validate; diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index e69de29b..cdba052b 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -0,0 +1,80 @@ +// 랜덤 문자열 생성기 + +import { Console, Random } from '@woowacourse/mission-utils'; +import Controller from '../src/controller/Controller.js'; +import inputView from '../src/view/InputView.js'; + +jest.mock('../src/view/InputView.js', () => ({ + __esModule: true, + default: { + readLineMessage: jest.fn(), + }, +})); +// 1. 커스텀 구분자를 포함하는 경우 +// 2. 커스텀 구분자를 포함하지 않는경우 + +// 32 ~ 126 텍스트 +// 48 ~ 57 : 숫자 + +// 숫자 추가하기 함수 getRandomNumber +// 커스텀 구분자 추가하기 함수 getCustomRegexWithText +// 구분자 추가하기 함수 + +const getCustomRegexWithText = (text) => `//${text}\\n`; +const getRandomNumber = (start = 1, end = 9) => + Random.pickNumberInRange(start, end); + +const makeCustomRegex = (customRegexLength) => { + const customRegexs = []; + for (let i = 0; i < customRegexLength; i++) { + const textLength = Random.pickNumberInRange(32, 126); + customRegexs.push(String.fromCharCode(textLength)); + } + return customRegexs; +}; + +const getRandomValueInArray = (values) => { + const index = getRandomNumber(0, values.length); + return values[index]; +}; + +const getRandomInput = () => { + let inputResult = ''; + let outputResult = 0; + + // 커스텀 구분자 개수를 정하다 + const customRegexLength = getRandomNumber(1, 1000); + const madeCustomRegex = makeCustomRegex(customRegexLength); + + // 숫자 개수를 정하다 + const numberLength = getRandomNumber(1, 100000); + for (let i = 0; i < numberLength; i++) { + const testNumber = getRandomNumber(0, 2); + if (testNumber === 0) { + const randNumber = getRandomNumber(0, 100000); + inputResult += randNumber; // 추가해야 함 + outputResult += randNumber; + } else if (testNumber === 1) { + const randDefaultRegex = getRandomValueInArray([':', ',']); + inputResult += randDefaultRegex; // 추가해야 함 + } else { + const randCustomRegex = getRandomValueInArray(madeCustomRegex); + inputResult += getCustomRegexWithText(randCustomRegex); // 추가해야 함 + } + } + return [inputResult, outputResult]; +}; + +describe('랜덤문자열 생성기', () => { + it('커스텀 구분자를 통해 테스트 합니다', async () => { + const [input, output] = getRandomInput(); + inputView.readLineMessage.mockImplementationOnce(() => input); + + console.log('input', input); + console.log('output', output); + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + await controller.run(); + expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); + }); +}); diff --git a/test/validate.test.js b/test/validate.test.js index 980eb4e9..c0249ee2 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -4,11 +4,11 @@ import validate from '../src/service/validate/validate.js'; describe('잘못된 입력에 대한 검증', () => { // 파싱 전 - 예외 - it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { - const input = - '1234567891011122312312312312312312312312321312312312312312312313'; - expect(() => validate.isLong(input)).toThrow('[ERROR]'); - }); + // it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { + // const input = + // '1234567891011122312312312312312312312312321312312312312312312313'; + // expect(() => validate.isLong(input)).toThrow('[ERROR]'); + // }); it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', 1]; From 3ebf241e2730adb8ebed2904fb01418cef1cdd40 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 16:48:52 +0900 Subject: [PATCH 37/65] =?UTF-8?q?test:=20=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=EB=A5=BC=20=EB=84=98=EC=96=B4=EC=84=A0=20?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=EB=A5=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/RandomMaker.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index cdba052b..830c2016 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -34,7 +34,7 @@ const makeCustomRegex = (customRegexLength) => { }; const getRandomValueInArray = (values) => { - const index = getRandomNumber(0, values.length); + const index = getRandomNumber(0, values.length - 1); return values[index]; }; From a40d61874aead6d13d4f101027f30ea915b9c653 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 16:53:37 +0900 Subject: [PATCH 38/65] =?UTF-8?q?docs:=20=EA=B5=AC=ED=98=84=EB=90=9C=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7c6caeea..5e04e8a8 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,7 @@ ## 1. 기능요구 사항 --[] 커스텀 구분자를 추출하는 기능 --[] 구분자를 통해 입력받은 숫자를 파싱하는 기능 --[] 파싱된 입력이 올바른지 확인하는 기능 --[] 파싱된 입력을 더하는 기능 +-[X] 커스텀 구분자를 추출하는 기능 -[X] 구분자를 통해 입력받은 숫자를 파싱하는 기능 -[X] 파싱된 입력이 올바른지 확인하는 기능 -[X] 파싱된 입력을 더하는 기능 - Model - Parser : 구분자 @@ -24,33 +21,23 @@ ### - 입력 요구사항 --[] 구분자와 야수로 구성된 문자열 --[] 구분자 : `,` `:` `//커스텀구분자\n` +-[X] 구분자와 야수로 구성된 문자열 -[X] 구분자 : `,` `:` `//커스텀구분자\n` #### - 추가사항 --[] 음수 입력시 ERROR 처리 +-[X] 음수 입력시 ERROR 처리 ### - 출력 요구사항 --[] "결과 : {int}" --[] 잘못된 입력시 [ERROR]로 시작하는 메시지와 함께 종료 +-[X] "결과 : {int}" -[X] 잘못된 입력시 [ERROR]로 시작하는 메시지와 함께 종료 ## 2. 프로그래밍 요구 사항 --[] 프로그래밍의 시작지점은 App.js의 run() 인가? --[] package.json 파일을 변경하지 않았는가? --[] 프로그램 종료시 process.exit()를 호출하지 않았는가? --[] 자바스크립트 코드 컨벤션에 맞게 작성했는가? --[] @woowacourse/mission-utils에서 제공하는 Console API를 사용해서 구현했는가? +-[X] 프로그래밍의 시작지점은 App.js의 run() 인가? -[X] package.json 파일을 변경하지 않았는가? -[X] 프로그램 종료시 process.exit()를 호출하지 않았는가? -[X] 자바스크립트 코드 컨벤션에 맞게 작성했는가? -[X] @woowacourse/mission-utils에서 제공하는 Console API를 사용해서 구현했는가? ## 3. 도전 사항 --[] TDD 설계원칙 적용 --[] 테스트 코드 랜덤 문자열 생성기 구현 --[] MVC 패턴 적용하기 --[] 객체지향 원칙 준수하기 --[] AI 사용 없이 구현하기 +-[X] TDD 설계원칙 적용 -[X] 테스트 코드 랜덤 문자열 생성기 구현 -[X] MVC 패턴 적용하기 -[X] 객체지향 원칙 준수하기 -[X] AI 사용 없이 구현하기 ## 4. 예상 실행 결과 From 796265e2540fe309c2885cb9dcf4c28d36143c05 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 17:14:23 +0900 Subject: [PATCH 39/65] =?UTF-8?q?test:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=EA=B0=80=20=EC=97=AC=EB=9F=AC?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 1 + src/service/model/Parser.js | 5 +++++ test/Parser.test.js | 6 ++++++ test/RandomMaker.test.js | 14 ++++++++++---- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/controller/Controller.js b/src/controller/Controller.js index bdef980c..0f138e6f 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -20,6 +20,7 @@ export default class Controller { const customRegexs = this.extraction.extractCustom(input); const parsedNumber = this.parser.parseData(customRegexs, input); + console.log('parsedNumber', parsedNumber); validate.isIntegers(parsedNumber); const numbers = new Number(parsedNumber); diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index d52866ce..63ca4b1f 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -11,6 +11,11 @@ export default class Parser { const totalRegexs = [...regexs, ',', ':']; const regex = new RegExp(`[${totalRegexs.join('')}]`); + console.log( + 'inputResult', + inputResult, + inputResult.split(regex).filter((item) => item !== ''), + ); return inputResult .split(regex) .filter((item) => item !== '') // 빈문자열 제거 diff --git a/test/Parser.test.js b/test/Parser.test.js index baa64952..c779b610 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -56,6 +56,12 @@ describe('Parser 클래스의 useCase를 추가하다', () => { const inputText = ',//;\\n1'; expect(parser.parseData(regxs, inputText)).toEqual([1]); }); + + it('커스텀 구분자가 여러개 적히는 경우', () => { + const regxs = [';', '|']; + const inputText = ',//;\\n1//|\\n'; + expect(parser.parseData(regxs, inputText)).toEqual([1]); + }); }); // issue \ 처리가 매우 까다로움 diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 830c2016..a0b5b025 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -43,15 +43,16 @@ const getRandomInput = () => { let outputResult = 0; // 커스텀 구분자 개수를 정하다 - const customRegexLength = getRandomNumber(1, 1000); + const customRegexLength = getRandomNumber(1, 2); const madeCustomRegex = makeCustomRegex(customRegexLength); + const addedCustomRegex = []; // 숫자 개수를 정하다 - const numberLength = getRandomNumber(1, 100000); + const numberLength = getRandomNumber(1, 10); for (let i = 0; i < numberLength; i++) { const testNumber = getRandomNumber(0, 2); if (testNumber === 0) { - const randNumber = getRandomNumber(0, 100000); + const randNumber = getRandomNumber(0, 100); inputResult += randNumber; // 추가해야 함 outputResult += randNumber; } else if (testNumber === 1) { @@ -59,7 +60,12 @@ const getRandomInput = () => { inputResult += randDefaultRegex; // 추가해야 함 } else { const randCustomRegex = getRandomValueInArray(madeCustomRegex); - inputResult += getCustomRegexWithText(randCustomRegex); // 추가해야 함 + if (!addedCustomRegex.includes(randCustomRegex)) { + inputResult += getCustomRegexWithText(randCustomRegex); // 추가해야 함 + addedCustomRegex.push(randCustomRegex); + } else { + inputResult += randCustomRegex; + } } } return [inputResult, outputResult]; From c23a799b5cde017b2cfc518aa08dcd866d5464d4 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 17:18:13 +0900 Subject: [PATCH 40/65] =?UTF-8?q?fix:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=EA=B0=80=20=EC=97=AC=EB=9F=AC?= =?UTF-8?q?=EA=B0=9C=EC=9D=BC=EB=95=8C=20=ED=8C=8C=EC=8B=B1=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=EB=A5=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 63ca4b1f..1cb2d5dd 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -5,17 +5,12 @@ export default class Parser { // 커스텀 구분자가 포함되는경우 양식대로 파싱 regexs.forEach((regex) => { const splitType = `//${regex}\\n`; - inputResult = inputText.split(splitType).join(''); + inputResult = inputResult.split(splitType).join(''); }); const totalRegexs = [...regexs, ',', ':']; const regex = new RegExp(`[${totalRegexs.join('')}]`); - console.log( - 'inputResult', - inputResult, - inputResult.split(regex).filter((item) => item !== ''), - ); return inputResult .split(regex) .filter((item) => item !== '') // 빈문자열 제거 From adfa885e9fcb5800bd0b2d0a813604a2482c4cf6 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 17:21:49 +0900 Subject: [PATCH 41/65] =?UTF-8?q?fix:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=20=EC=97=AC=EB=9F=AC=EA=B0=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EC=97=90=20=EC=88=AB=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1=EC=95=88=EB=90=98=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=EB=A5=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 3 ++- test/Parser.test.js | 6 ++++++ test/RandomMaker.test.js | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 1cb2d5dd..00d770fb 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -5,7 +5,8 @@ export default class Parser { // 커스텀 구분자가 포함되는경우 양식대로 파싱 regexs.forEach((regex) => { const splitType = `//${regex}\\n`; - inputResult = inputResult.split(splitType).join(''); + const splitedText = inputResult.split(splitType); + inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; }); const totalRegexs = [...regexs, ',', ':']; diff --git a/test/Parser.test.js b/test/Parser.test.js index c779b610..c5409717 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -62,6 +62,12 @@ describe('Parser 클래스의 useCase를 추가하다', () => { const inputText = ',//;\\n1//|\\n'; expect(parser.parseData(regxs, inputText)).toEqual([1]); }); + + it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { + const regxs = [';', '|']; + const inputText = ',//;\\n1//|\\n1|2'; + expect(parser.parseData(regxs, inputText)).toEqual([1, 1, 2]); + }); }); // issue \ 처리가 매우 까다로움 diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index a0b5b025..f74bb231 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -43,16 +43,16 @@ const getRandomInput = () => { let outputResult = 0; // 커스텀 구분자 개수를 정하다 - const customRegexLength = getRandomNumber(1, 2); + const customRegexLength = getRandomNumber(1, 1000); const madeCustomRegex = makeCustomRegex(customRegexLength); const addedCustomRegex = []; // 숫자 개수를 정하다 - const numberLength = getRandomNumber(1, 10); + const numberLength = getRandomNumber(1, 100000); for (let i = 0; i < numberLength; i++) { const testNumber = getRandomNumber(0, 2); if (testNumber === 0) { - const randNumber = getRandomNumber(0, 100); + const randNumber = getRandomNumber(0, 100000); inputResult += randNumber; // 추가해야 함 outputResult += randNumber; } else if (testNumber === 1) { From d25d18eec7916f582cca6e6e4f0b1436d2beed42 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 17:53:42 +0900 Subject: [PATCH 42/65] =?UTF-8?q?test:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=EC=97=90=20=EC=95=A0=EB=9F=AC=20?= =?UTF-8?q?=EC=B6=94=EC=A0=81=ED=88=B4=EC=9D=84=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 100 ++++++++++++++++++++++++++++++++++++ src/service/model/Parser.js | 2 +- test/Parser.test.js | 2 +- test/RandomMaker.test.js | 45 +++++++++++++--- 4 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 error_cases/last_error.json diff --git a/error_cases/last_error.json b/error_cases/last_error.json new file mode 100644 index 00000000..8a58600a --- /dev/null +++ b/error_cases/last_error.json @@ -0,0 +1,100 @@ +{ + "input": ":93218,,80668:641749610753117,,95567//\"\\n\"\"\"::\"\"\"\"7315\"26483\"\"75614\"::\"\"\"\"\",:,:10621:,\",25989\":,\"\"\",52424\"\"\"351564875286948,\"\"63500:10765:,\",,\",7606320708\"48332\"33243\"\"\"5992765672,\",36813\"28804,80413:\"\":,\"::93139:,,,\"\":48219::23100\"68778,:2854994751\"2629599179\",44800\"\"19000,\":2049978::\"\"\"54019\"\"77083,91574\":\"\",,\"\"\"\"\"\"54724:64857\"\",,61564::12697,\",\"72041,\":,:\",\"42550\"\"\"\"\"\",\"\"\"\",31711,1395553451\"\",55690\"73254231\"\",14860\"878589994414884\",:\"\"\"\"\"16618\",97669978958826:39539\"\"::::10914770,\"43267,,33779\"\",43249:\"\"\"35028\"\":\"3119350273,\",\"87334309678796,\"::321341932358789\":\"74429\"68734,9838576050\"7534\"26588,71293,606090450,\"74410\"\"\":5854\"49519:\"\"\"\"70087\"\":\"1492522086\",\"\"82285,95264,\"53381\"\"\"\":\"33941,:\"38026\"30694\"92083\"\"33516,\"17209:\",,\"95385\"\"96367,196292583826422\"\"\"\",71111\"58266\"\"\"\"52679,6532131634\"\":\"95267\"\"\"3959840250963602070722339:\"3479,\"2513535830:\":,,\"\"38681,::79840906228710\"::\":\",\",\",\":,33638\":\"71086\"::\"63611,1854885688\":\"89736:\",", + "output": 6417077, + "parseMumber": [ + 93218, + 80668, + 641749610753117, + 95567, + 7315, + 26483, + 75614, + 10621, + 25989, + 52424, + 351564875286948, + 63500, + 10765, + 7606320708, + 48332, + 33243, + 5992765672, + 36813, + 28804, + 80413, + 93139, + 48219, + 23100, + 68778, + 2854994751, + 2629599179, + 44800, + 19000, + 2049978, + 54019, + 77083, + 91574, + 54724, + 64857, + 61564, + 12697, + 72041, + 42550, + 31711, + 1395553451, + 55690, + 73254231, + 14860, + 878589994414884, + 16618, + 97669978958826, + 39539, + 10914770, + 43267, + 33779, + 43249, + 35028, + 3119350273, + 87334309678796, + 321341932358789, + 74429, + 68734, + 9838576050, + 7534, + 26588, + 71293, + 606090450, + 74410, + 5854, + 49519, + 70087, + 1492522086, + 82285, + 95264, + 53381, + 33941, + 38026, + 30694, + 92083, + 33516, + 17209, + 95385, + 96367, + 196292583826422, + 71111, + 58266, + 52679, + 6532131634, + 95267, + 3.959840250963602e+24, + 3479, + 2513535830, + 38681, + 79840906228710, + 33638, + 71086, + 63611, + 1854885688, + 89736 + ] +} \ No newline at end of file diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 00d770fb..51d42bf7 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -6,7 +6,7 @@ export default class Parser { regexs.forEach((regex) => { const splitType = `//${regex}\\n`; const splitedText = inputResult.split(splitType); - inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; + inputResult = splitedText.join(''); }); const totalRegexs = [...regexs, ',', ':']; diff --git a/test/Parser.test.js b/test/Parser.test.js index c5409717..1d502b7e 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -66,7 +66,7 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { const regxs = [';', '|']; const inputText = ',//;\\n1//|\\n1|2'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 1, 2]); + expect(parser.parseData(regxs, inputText)).toEqual([11, 2]); }); }); diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index f74bb231..2648e1e5 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -1,8 +1,11 @@ // 랜덤 문자열 생성기 import { Console, Random } from '@woowacourse/mission-utils'; +import fs from 'fs'; +import path from 'path'; import Controller from '../src/controller/Controller.js'; import inputView from '../src/view/InputView.js'; +import Parser from '../src/service/model/Parser.js'; jest.mock('../src/view/InputView.js', () => ({ __esModule: true, @@ -20,6 +23,16 @@ jest.mock('../src/view/InputView.js', () => ({ // 커스텀 구분자 추가하기 함수 getCustomRegexWithText // 구분자 추가하기 함수 +const ERROR_DIR = path.join(process.cwd(), 'error_cases'); +if (!fs.existsSync(ERROR_DIR)) fs.mkdirSync(ERROR_DIR); +const errorFilePath = path.join(ERROR_DIR, 'last_error.json'); + +export const saveErrorCase = (input, output, parseMumber) => { + fs.writeFileSync( + errorFilePath, + JSON.stringify({ input, output, parseMumber }, null, 3), + ); +}; const getCustomRegexWithText = (text) => `//${text}\\n`; const getRandomNumber = (start = 1, end = 9) => Random.pickNumberInRange(start, end); @@ -39,22 +52,28 @@ const getRandomValueInArray = (values) => { }; const getRandomInput = () => { + if (fs.existsSync(errorFilePath)) { + const data = JSON.parse(fs.readFileSync(errorFilePath, 'utf-8')); + return [data.input, data.output, data.parseMumber]; + } let inputResult = ''; let outputResult = 0; // 커스텀 구분자 개수를 정하다 - const customRegexLength = getRandomNumber(1, 1000); + const customRegexLength = getRandomNumber(1, 100); const madeCustomRegex = makeCustomRegex(customRegexLength); const addedCustomRegex = []; + const parseMumber = []; // 숫자 개수를 정하다 - const numberLength = getRandomNumber(1, 100000); + const numberLength = getRandomNumber(1, 1000); for (let i = 0; i < numberLength; i++) { const testNumber = getRandomNumber(0, 2); if (testNumber === 0) { const randNumber = getRandomNumber(0, 100000); inputResult += randNumber; // 추가해야 함 outputResult += randNumber; + parseMumber.push(randNumber); } else if (testNumber === 1) { const randDefaultRegex = getRandomValueInArray([':', ',']); inputResult += randDefaultRegex; // 추가해야 함 @@ -68,19 +87,29 @@ const getRandomInput = () => { } } } - return [inputResult, outputResult]; + return [inputResult, outputResult, parseMumber]; }; describe('랜덤문자열 생성기', () => { it('커스텀 구분자를 통해 테스트 합니다', async () => { - const [input, output] = getRandomInput(); + const [input, output, parseMumber] = getRandomInput(); + inputView.readLineMessage.mockImplementationOnce(() => input); - console.log('input', input); - console.log('output', output); const controller = new Controller(); const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); - await controller.run(); - expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); + const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); + + let parsedValue; + try { + await controller.run(); + parsedValue = parsedValueSpy.mock.results[0].value; + expect(parseMumber).toEqual(parsedValue); + expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); + } catch (err) { + // 실패하면 에러 케이스 저장 + saveErrorCase(input, output, parsedValue); + throw err; + } }); }); From 636a1a0bf8724d6ff1f17a5eb2d411b877661e9c Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 18:18:55 +0900 Subject: [PATCH 43/65] =?UTF-8?q?fix:=20Parser=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 12 ++++++++++-- test/Extraction.test.js | 6 ++++++ test/Parser.test.js | 12 ++++++------ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 51d42bf7..190476bc 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -6,15 +6,23 @@ export default class Parser { regexs.forEach((regex) => { const splitType = `//${regex}\\n`; const splitedText = inputResult.split(splitType); - inputResult = splitedText.join(''); + inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; }); const totalRegexs = [...regexs, ',', ':']; - const regex = new RegExp(`[${totalRegexs.join('')}]`); + const escapedRegexs = totalRegexs.map((regex) => + this.#escapeForCharClass(regex), + ); + + const regex = new RegExp(`[${escapedRegexs.join('')}]`); return inputResult .split(regex) .filter((item) => item !== '') // 빈문자열 제거 .map((item) => Number(item)); } + + #escapeForCharClass(char) { + return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + } } diff --git a/test/Extraction.test.js b/test/Extraction.test.js index 0d92075c..793f88ea 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -24,4 +24,10 @@ describe('Extraction 클래스 테스트', () => { const extraction = new Extraction(); expect(extraction.extractCustom(inputText)).toEqual([';', 'ㅁ']); }); + + it('커스텀 구분자가 \\인경우', () => { + const inputText = '//\\\\n'; + const extraction = new Extraction(); + expect(extraction.extractCustom(inputText)).toEqual(['\\']); + }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index 1d502b7e..7b882d47 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -22,18 +22,18 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('구분자가 숫자인경우', () => { const regxs = ['1', '2', '3', '#']; - const inputText = '66166266366#66'; + const inputText = '66//1\\n66//2\\n66//3\\n66//#\\n66'; expect(parser.parseData(regxs, inputText)).toEqual([66, 66, 66, 66, 66]); }); it('구분자에 \\ 이스케이프가 포함되는경우', () => { - const regxs = ['', '\\\\']; - const inputText = '\\1\\2\\3'; + const regxs = ['\\']; // 커스텀 구분자: 백슬래시 + const inputText = '//\\\\n1\\2\\3'; expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); }); it('구분자가 여러글자 인경우', () => { const regxs = ['ab', 'bc', 'cd']; - const inputText = 'ab1bc2cd3'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); + const inputText = '//ab\\n1ab2//bc\\n2//cd\\n3'; + expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 2, 3]); }); it('음수가 포함되는 경우', () => { @@ -66,7 +66,7 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { const regxs = [';', '|']; const inputText = ',//;\\n1//|\\n1|2'; - expect(parser.parseData(regxs, inputText)).toEqual([11, 2]); + expect(parser.parseData(regxs, inputText)).toEqual([1, 1, 2]); }); }); From 42b88ca378df26b62ef7d9308c90e5be81d17507 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 18 Oct 2025 18:51:00 +0900 Subject: [PATCH 44/65] =?UTF-8?q?test:=20=EB=9E=9C=EB=8D=A4=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EC=83=9D=EC=84=B1=EA=B8=B0=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=88=AB=EC=9E=90=EB=A5=BC=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=EA=B5=AC=EB=B6=84=EC=9E=90=EB=A1=9C=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 100 ------------------------------- src/controller/Controller.js | 4 +- src/service/validate/validate.js | 7 ++- test/RandomMaker.test.js | 39 ++++++++++-- test/validate.test.js | 8 +-- 5 files changed, 44 insertions(+), 114 deletions(-) delete mode 100644 error_cases/last_error.json diff --git a/error_cases/last_error.json b/error_cases/last_error.json deleted file mode 100644 index 8a58600a..00000000 --- a/error_cases/last_error.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "input": ":93218,,80668:641749610753117,,95567//\"\\n\"\"\"::\"\"\"\"7315\"26483\"\"75614\"::\"\"\"\"\",:,:10621:,\",25989\":,\"\"\",52424\"\"\"351564875286948,\"\"63500:10765:,\",,\",7606320708\"48332\"33243\"\"\"5992765672,\",36813\"28804,80413:\"\":,\"::93139:,,,\"\":48219::23100\"68778,:2854994751\"2629599179\",44800\"\"19000,\":2049978::\"\"\"54019\"\"77083,91574\":\"\",,\"\"\"\"\"\"54724:64857\"\",,61564::12697,\",\"72041,\":,:\",\"42550\"\"\"\"\"\",\"\"\"\",31711,1395553451\"\",55690\"73254231\"\",14860\"878589994414884\",:\"\"\"\"\"16618\",97669978958826:39539\"\"::::10914770,\"43267,,33779\"\",43249:\"\"\"35028\"\":\"3119350273,\",\"87334309678796,\"::321341932358789\":\"74429\"68734,9838576050\"7534\"26588,71293,606090450,\"74410\"\"\":5854\"49519:\"\"\"\"70087\"\":\"1492522086\",\"\"82285,95264,\"53381\"\"\"\":\"33941,:\"38026\"30694\"92083\"\"33516,\"17209:\",,\"95385\"\"96367,196292583826422\"\"\"\",71111\"58266\"\"\"\"52679,6532131634\"\":\"95267\"\"\"3959840250963602070722339:\"3479,\"2513535830:\":,,\"\"38681,::79840906228710\"::\":\",\",\",\":,33638\":\"71086\"::\"63611,1854885688\":\"89736:\",", - "output": 6417077, - "parseMumber": [ - 93218, - 80668, - 641749610753117, - 95567, - 7315, - 26483, - 75614, - 10621, - 25989, - 52424, - 351564875286948, - 63500, - 10765, - 7606320708, - 48332, - 33243, - 5992765672, - 36813, - 28804, - 80413, - 93139, - 48219, - 23100, - 68778, - 2854994751, - 2629599179, - 44800, - 19000, - 2049978, - 54019, - 77083, - 91574, - 54724, - 64857, - 61564, - 12697, - 72041, - 42550, - 31711, - 1395553451, - 55690, - 73254231, - 14860, - 878589994414884, - 16618, - 97669978958826, - 39539, - 10914770, - 43267, - 33779, - 43249, - 35028, - 3119350273, - 87334309678796, - 321341932358789, - 74429, - 68734, - 9838576050, - 7534, - 26588, - 71293, - 606090450, - 74410, - 5854, - 49519, - 70087, - 1492522086, - 82285, - 95264, - 53381, - 33941, - 38026, - 30694, - 92083, - 33516, - 17209, - 95385, - 96367, - 196292583826422, - 71111, - 58266, - 52679, - 6532131634, - 95267, - 3.959840250963602e+24, - 3479, - 2513535830, - 38681, - 79840906228710, - 33638, - 71086, - 63611, - 1854885688, - 89736 - ] -} \ No newline at end of file diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 0f138e6f..834f0ecf 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -20,9 +20,7 @@ export default class Controller { const customRegexs = this.extraction.extractCustom(input); const parsedNumber = this.parser.parseData(customRegexs, input); - console.log('parsedNumber', parsedNumber); - validate.isIntegers(parsedNumber); - + validate.isNotMinus(parsedNumber); const numbers = new Number(parsedNumber); await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } catch (error) { diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 337cfa8d..15b45eb1 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,9 +1,10 @@ const validate = { - isIntegers(numbers) { + isNotMinus(numbers) { numbers.forEach((number) => { if (typeof number === 'string' && number.trim() === '') return; - if (Number.isNaN(Number(number))) throw new Error('[ERROR]'); - if (number <= 0) throw new Error('[ERROR]'); + if (Number.isNaN(Number(number))) + throw new Error('[ERROR] 숫자가 아닙니다'); + if (number < 0) throw new Error('[ERROR] 음수가 포함되어있습니다'); }); }, // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 2648e1e5..f081ba64 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -23,6 +23,24 @@ jest.mock('../src/view/InputView.js', () => ({ // 커스텀 구분자 추가하기 함수 getCustomRegexWithText // 구분자 추가하기 함수 +const compareArrays = (expected, actual) => { + const maxLength = Math.max(expected.length, actual.length); + let hasDifference = false; + + for (let i = 0; i < maxLength; i++) { + const exp = expected[i]; + const act = actual[i]; + if (exp !== act) { + console.log(`인덱스 ${i}: expected=${exp}, actual=${act}`); + hasDifference = true; + } + } + + if (!hasDifference) { + console.log('두 배열은 동일합니다.'); + } +}; + const ERROR_DIR = path.join(process.cwd(), 'error_cases'); if (!fs.existsSync(ERROR_DIR)) fs.mkdirSync(ERROR_DIR); const errorFilePath = path.join(ERROR_DIR, 'last_error.json'); @@ -40,8 +58,12 @@ const getRandomNumber = (start = 1, end = 9) => const makeCustomRegex = (customRegexLength) => { const customRegexs = []; for (let i = 0; i < customRegexLength; i++) { - const textLength = Random.pickNumberInRange(32, 126); - customRegexs.push(String.fromCharCode(textLength)); + let charCode; + // 48 ('0') ~ 57 ('9') 사이의 숫자가 나오면 다시 뽑습니다. + do { + charCode = Random.pickNumberInRange(32, 126); + } while (charCode >= 48 && charCode <= 57); + customRegexs.push(String.fromCharCode(charCode)); } return customRegexs; }; @@ -67,16 +89,23 @@ const getRandomInput = () => { // 숫자 개수를 정하다 const numberLength = getRandomNumber(1, 1000); + let isLastNumber = false; for (let i = 0; i < numberLength; i++) { const testNumber = getRandomNumber(0, 2); - if (testNumber === 0) { + if ( + testNumber === 0 && + !isLastNumber && + !madeCustomRegex.includes(testNumber) + ) { const randNumber = getRandomNumber(0, 100000); inputResult += randNumber; // 추가해야 함 outputResult += randNumber; parseMumber.push(randNumber); + isLastNumber = true; } else if (testNumber === 1) { const randDefaultRegex = getRandomValueInArray([':', ',']); inputResult += randDefaultRegex; // 추가해야 함 + isLastNumber = false; } else { const randCustomRegex = getRandomValueInArray(madeCustomRegex); if (!addedCustomRegex.includes(randCustomRegex)) { @@ -85,6 +114,7 @@ const getRandomInput = () => { } else { inputResult += randCustomRegex; } + isLastNumber = false; } } return [inputResult, outputResult, parseMumber]; @@ -104,11 +134,12 @@ describe('랜덤문자열 생성기', () => { try { await controller.run(); parsedValue = parsedValueSpy.mock.results[0].value; + compareArrays(parseMumber, parsedValue); expect(parseMumber).toEqual(parsedValue); expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); } catch (err) { // 실패하면 에러 케이스 저장 - saveErrorCase(input, output, parsedValue); + saveErrorCase(input, output, parseMumber); throw err; } }); diff --git a/test/validate.test.js b/test/validate.test.js index c0249ee2..e0f975e1 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -12,19 +12,19 @@ describe('잘못된 입력에 대한 검증', () => { it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', 1]; - expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); }); it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { const parseInput = ['', 1, 2, 1]; - expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); }); it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { const parseInput = [' ', 1, ' ', 1]; - expect(() => validate.isIntegers(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); }); it('음수가 입력되는 경우 ERROR', () => { const parseInput = [-1, 2, 3]; - expect(() => validate.isIntegers(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); }); }); From ed290658a3e2dd2f38c4950fd2ee913097f3342c Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 00:54:23 +0900 Subject: [PATCH 45/65] =?UTF-8?q?test:=20=EB=9E=9C=EB=8D=A4=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EC=83=9D=EC=84=B1=EA=B8=B0=EC=97=90=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=20=EA=B5=AC=EB=B6=84=EC=9E=90=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 101 ++++++++++++++++++++++++++++++++++++ test/RandomMaker.test.js | 33 ++++++++---- 2 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 error_cases/last_error.json diff --git a/error_cases/last_error.json b/error_cases/last_error.json new file mode 100644 index 00000000..7196e9fa --- /dev/null +++ b/error_cases/last_error.json @@ -0,0 +1,101 @@ +{ + "input": ",83006//f\\n//c\\n:19324//R\\nc8653,,,::41525:10921//W\\n67061//U\\n96846:,//(\\n31146(78963U//)\\n88393://\"\\n92048//H\\n98668//]\\n//_\\n//:\\nR://h\\n):98030]//,\\n:,:,//'\\n,15504::24250'::30969,,54761//[\\n_W31402:'9711,8336::4218//E\\n//M\\n16275//i\\n:,71328:::M'3748,:,,4384\":://X\\n,:26378[56799,,,61476[96079:,:M,E86471//<\\n,26654):89022W,,W,1629]//|\\n)H84782,:,,:R8671,://+\\n,85327f,17869_,:,,,HW83504(c,\"12752_63484:31096:,R//o\\n26122h56709i:[,o54794,H://;\\n51871':Uo'<89974,68571[:,26187//T\\n33288:19456hcW,10402f:51620i],59049:f,Ec64925:,,:2818X56763':_,U|:X65431::4350,,;98114c,,63821\":;90621:iUMR,18394h:,):,':,,(:::,89494,65003,:EWi,(),20106,16099T15945HW+\"", + "output": 3141390, + "extracionRegex": [ + "R", + "<", + "\"", + "i", + ",", + "X", + "_", + "T", + "[", + "h", + ")", + "f", + "o", + "|", + "(", + "W", + "E", + "M", + ":", + "c", + "U", + "]", + "+", + ";", + "H", + "'" + ], + "parseMumber": [ + 83006, + 19324, + 8653, + 41525, + 10921, + 67061, + 96846, + 31146, + 78963, + 88393, + 92048, + 98668, + 98030, + 15504, + 24250, + 30969, + 54761, + 31402, + 9711, + 8336, + 4218, + 16275, + 71328, + 3748, + 4384, + 26378, + 56799, + 61476, + 96079, + 86471, + 26654, + 89022, + 1629, + 84782, + 8671, + 85327, + 17869, + 83504, + 12752, + 63484, + 31096, + 26122, + 56709, + 54794, + 51871, + 89974, + 68571, + 26187, + 33288, + 19456, + 10402, + 51620, + 59049, + 64925, + 2818, + 56763, + 65431, + 4350, + 98114, + 63821, + 90621, + 18394, + 89494, + 65003, + 20106, + 16099, + 15945 + ] +} \ No newline at end of file diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index f081ba64..3aed5a7f 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -6,6 +6,7 @@ import path from 'path'; import Controller from '../src/controller/Controller.js'; import inputView from '../src/view/InputView.js'; import Parser from '../src/service/model/Parser.js'; +import Extraction from '../src/service/model/Extraction.js'; jest.mock('../src/view/InputView.js', () => ({ __esModule: true, @@ -45,10 +46,10 @@ const ERROR_DIR = path.join(process.cwd(), 'error_cases'); if (!fs.existsSync(ERROR_DIR)) fs.mkdirSync(ERROR_DIR); const errorFilePath = path.join(ERROR_DIR, 'last_error.json'); -export const saveErrorCase = (input, output, parseMumber) => { +export const saveErrorCase = (input, output, parseMumber, extracionRegex) => { fs.writeFileSync( errorFilePath, - JSON.stringify({ input, output, parseMumber }, null, 3), + JSON.stringify({ input, output, extracionRegex, parseMumber }, null, 4), ); }; const getCustomRegexWithText = (text) => `//${text}\\n`; @@ -65,7 +66,7 @@ const makeCustomRegex = (customRegexLength) => { } while (charCode >= 48 && charCode <= 57); customRegexs.push(String.fromCharCode(charCode)); } - return customRegexs; + return [...new Set(customRegexs)]; }; const getRandomValueInArray = (values) => { @@ -76,7 +77,7 @@ const getRandomValueInArray = (values) => { const getRandomInput = () => { if (fs.existsSync(errorFilePath)) { const data = JSON.parse(fs.readFileSync(errorFilePath, 'utf-8')); - return [data.input, data.output, data.parseMumber]; + return [data.input, data.output, data.parseMumber, data.extracionRegex]; } let inputResult = ''; let outputResult = 0; @@ -117,29 +118,39 @@ const getRandomInput = () => { isLastNumber = false; } } - return [inputResult, outputResult, parseMumber]; + return [inputResult, outputResult, parseMumber, madeCustomRegex]; }; describe('랜덤문자열 생성기', () => { it('커스텀 구분자를 통해 테스트 합니다', async () => { - const [input, output, parseMumber] = getRandomInput(); + const [input, output, parseMumber, extracionRegex] = getRandomInput(); inputView.readLineMessage.mockImplementationOnce(() => input); const controller = new Controller(); const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); + const extractionValueSpy = jest.spyOn( + Extraction.prototype, + 'extractCustom', + ); - let parsedValue; try { await controller.run(); - parsedValue = parsedValueSpy.mock.results[0].value; - compareArrays(parseMumber, parsedValue); - expect(parseMumber).toEqual(parsedValue); + + const extractionSpyValue = extractionValueSpy.mock.results[0].value; + const parsedSpyValue = parsedValueSpy.mock.results[0].value; + + compareArrays(parseMumber, parsedSpyValue); // 디버깅을 위한 콘솔 + + console.log('extracionRegex', extracionRegex); + console.log('extractionSpyValue', extractionSpyValue); + expect(new Set(extracionRegex)).toEqual(new Set(extractionSpyValue)); + expect(parseMumber).toEqual(parsedSpyValue); expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); } catch (err) { // 실패하면 에러 케이스 저장 - saveErrorCase(input, output, parseMumber); + saveErrorCase(input, output, parseMumber, extracionRegex); throw err; } }); From 15fc6992c0d449ea0820c13e0be0362e8857b789 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 12:56:43 +0900 Subject: [PATCH 46/65] =?UTF-8?q?docs:=20=EC=B6=94=EA=B0=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=9D=84=20=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ test/RandomMaker.test.js | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5e04e8a8..c8256812 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,9 @@ 덧셈할 문자열을 입력해 주세요. 1,2:3 결과 : 6 + +### 5. 추가사항 + +- 연속된 구분자의 입력을 허용할것인가 +- 공백 입력을 허용할것인가 +- 소수점을 허용할것인가 diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 3aed5a7f..7eaca342 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -142,9 +142,6 @@ describe('랜덤문자열 생성기', () => { const parsedSpyValue = parsedValueSpy.mock.results[0].value; compareArrays(parseMumber, parsedSpyValue); // 디버깅을 위한 콘솔 - - console.log('extracionRegex', extracionRegex); - console.log('extractionSpyValue', extractionSpyValue); expect(new Set(extracionRegex)).toEqual(new Set(extractionSpyValue)); expect(parseMumber).toEqual(parsedSpyValue); expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); From 1a9a93548961d6d0c2e81cd07d3c41c4f4e204ef Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 13:15:53 +0900 Subject: [PATCH 47/65] =?UTF-8?q?fix:=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20?= =?UTF-8?q?=EB=AF=B8=ED=97=88=EC=9A=A9=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + src/service/validate/validate.js | 4 +++- test/validate.test.js | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c8256812..c83bf34b 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,4 @@ - 연속된 구분자의 입력을 허용할것인가 - 공백 입력을 허용할것인가 - 소수점을 허용할것인가 + -> 소수점이 포함된경우 커스텀 구분자로 . 이 오면 허용할것인가 diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 15b45eb1..297767c8 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,7 +1,9 @@ const validate = { isNotMinus(numbers) { numbers.forEach((number) => { - if (typeof number === 'string' && number.trim() === '') return; + // 공백을 허용하지 않음 + if (typeof number === 'string' && number.trim() === '') + throw new Error('[ERROR] 공백이 포함되어 있습니다'); if (Number.isNaN(Number(number))) throw new Error('[ERROR] 숫자가 아닙니다'); if (number < 0) throw new Error('[ERROR] 음수가 포함되어있습니다'); diff --git a/test/validate.test.js b/test/validate.test.js index e0f975e1..4ca731af 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -14,13 +14,22 @@ describe('잘못된 입력에 대한 검증', () => { const parseInput = ['우', '테', '코', 1]; expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); }); - it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { + // it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { + // const parseInput = ['', 1, 2, 1]; + // expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + // }); + // it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { + // const parseInput = [' ', 1, ' ', 1]; + // expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + // }); + + it('빈 입력 이 포함된 경우 "" ERROR', () => { const parseInput = ['', 1, 2, 1]; - expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); }); - it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { + it('공백 입력인경우 " " ERROR', () => { const parseInput = [' ', 1, ' ', 1]; - expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); }); it('음수가 입력되는 경우 ERROR', () => { From 15179a63a2a7cd79b8e3b1656fa5b0df5c63e9fe Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 13:21:23 +0900 Subject: [PATCH 48/65] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=EB=A5=BC?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/error.js | 5 +++++ src/service/validate/validate.js | 8 +++++--- test/Contoller.test.js | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/constant/error.js diff --git a/src/constant/error.js b/src/constant/error.js new file mode 100644 index 00000000..b847435c --- /dev/null +++ b/src/constant/error.js @@ -0,0 +1,5 @@ +export const ERROR_MESSAGE = { + NOT_MINUS: '[ERROR] 음수가 포함되어 있습니다.', + NOT_EMPTY: '[ERROR] 공백이 포함되어 있습니다', + NOT_NUMBER: '[ERROR] 숫자가 포함되어 있습니다', +}; diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 297767c8..3608c806 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,12 +1,14 @@ +import { ERROR_MESSAGE } from '../../constant/error.js'; + const validate = { isNotMinus(numbers) { numbers.forEach((number) => { // 공백을 허용하지 않음 if (typeof number === 'string' && number.trim() === '') - throw new Error('[ERROR] 공백이 포함되어 있습니다'); + throw new Error(ERROR_MESSAGE.NOT_EMPTY); if (Number.isNaN(Number(number))) - throw new Error('[ERROR] 숫자가 아닙니다'); - if (number < 0) throw new Error('[ERROR] 음수가 포함되어있습니다'); + throw new Error(ERROR_MESSAGE.NOT_NUMBER); + if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); }); }, // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 diff --git a/test/Contoller.test.js b/test/Contoller.test.js index 00b5e64b..a12a1870 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -43,4 +43,12 @@ describe('Controller 클래스 E2E 테스트', () => { await controller.run(); expect(logSpy).toHaveBeenCalledWith('결과 : 1'); }); + + it('입력 : 1.2,2.3:3.4 | 출력 : 결과 : 6.9', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '1.2,2.3:3.4'); + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + await controller.run(); + expect(logSpy).toHaveBeenCalledWith('결과 : 6.9'); + }); }); From c1ee3567cd7da0004b475db03ee3471d0b803911 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 13:41:48 +0900 Subject: [PATCH 49/65] =?UTF-8?q?fix:=20isNumber=EB=A1=9C=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=AA=85=EC=9D=84=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/error.js | 2 +- src/controller/Controller.js | 2 +- src/service/validate/validate.js | 3 ++- test/Contoller.test.js | 14 ++++++++++++++ test/Parser.test.js | 5 ----- test/validate.test.js | 12 ++++++------ 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/constant/error.js b/src/constant/error.js index b847435c..c19f6414 100644 --- a/src/constant/error.js +++ b/src/constant/error.js @@ -1,5 +1,5 @@ export const ERROR_MESSAGE = { NOT_MINUS: '[ERROR] 음수가 포함되어 있습니다.', NOT_EMPTY: '[ERROR] 공백이 포함되어 있습니다', - NOT_NUMBER: '[ERROR] 숫자가 포함되어 있습니다', + NOT_NUMBER: '[ERROR] 숫자가 아닌것이 포함되어 있습니다', }; diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 834f0ecf..07184e0f 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -20,7 +20,7 @@ export default class Controller { const customRegexs = this.extraction.extractCustom(input); const parsedNumber = this.parser.parseData(customRegexs, input); - validate.isNotMinus(parsedNumber); + validate.isNumber(parsedNumber); const numbers = new Number(parsedNumber); await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } catch (error) { diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 3608c806..6020abbb 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,7 +1,7 @@ import { ERROR_MESSAGE } from '../../constant/error.js'; const validate = { - isNotMinus(numbers) { + isNumber(numbers) { numbers.forEach((number) => { // 공백을 허용하지 않음 if (typeof number === 'string' && number.trim() === '') @@ -11,6 +11,7 @@ const validate = { if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); }); }, + // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 // isLong(numbers) { // if (numbers.length >= 50) { diff --git a/test/Contoller.test.js b/test/Contoller.test.js index a12a1870..854f5016 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -51,4 +51,18 @@ describe('Controller 클래스 E2E 테스트', () => { await controller.run(); expect(logSpy).toHaveBeenCalledWith('결과 : 6.9'); }); + + it('//;\\n1;; 연속된 구분자 사용', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '//;\\n1;;'); + const controller = new Controller(); + await controller.run(); + await expect(controller.run()).rejects.toThrow('[ERROR]'); + }); + + it('1;1 미 언급 구분자 사용', async () => { + inputView.readLineMessage.mockImplementationOnce(() => '1;1'); + const controller = new Controller(); + await controller.run(); + await expect(controller.run()).rejects.toThrow('[ERROR]'); + }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index 7b882d47..8c452663 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -41,11 +41,6 @@ describe('Parser 클래스의 useCase를 추가하다', () => { expect(parser.parseData([], inputText)).toEqual([-1, 2, 3]); }); - it('공백이 포함되는 경우', () => { - const inputText = '-1,,3'; - expect(parser.parseData([], inputText)).toEqual([-1, 3]); - }); - it('커스텀 구분자가 포함이 되는경우 //;\\n1', () => { const inputText = '//;\\n1'; expect(parser.parseData([';'], inputText)).toEqual([1]); diff --git a/test/validate.test.js b/test/validate.test.js index 4ca731af..e0848c81 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -12,28 +12,28 @@ describe('잘못된 입력에 대한 검증', () => { it('숫자가 아닌값이 포함된 경우 ERROR', () => { const parseInput = ['우', '테', '코', 1]; - expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); }); // it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { // const parseInput = ['', 1, 2, 1]; - // expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + // expect(() => validate.isNumber(parseInput)).not.toThrow('[ERROR]'); // }); // it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { // const parseInput = [' ', 1, ' ', 1]; - // expect(() => validate.isNotMinus(parseInput)).not.toThrow('[ERROR]'); + // expect(() => validate.isNumber(parseInput)).not.toThrow('[ERROR]'); // }); it('빈 입력 이 포함된 경우 "" ERROR', () => { const parseInput = ['', 1, 2, 1]; - expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); }); it('공백 입력인경우 " " ERROR', () => { const parseInput = [' ', 1, ' ', 1]; - expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); }); it('음수가 입력되는 경우 ERROR', () => { const parseInput = [-1, 2, 3]; - expect(() => validate.isNotMinus(parseInput)).toThrow('[ERROR]'); + expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); }); }); From 81af4f4f5fd76579d68ddf9de4e689c5ede723a8 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 14:49:32 +0900 Subject: [PATCH 50/65] =?UTF-8?q?refactor:=20parse=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9C=84=ED=95=B4=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/regex.js | 0 src/controller/Controller.js | 16 ++++++++-- src/service/model/Extraction.js | 10 ++++++- src/service/model/Parser.js | 28 ++++++++---------- src/service/validate/validate.js | 7 +++++ test/Contoller.test.js | 2 -- test/Parser.test.js | 51 +++++++++++++------------------- test/validate.test.js | 44 +++++++++++++++++++++++++++ 8 files changed, 105 insertions(+), 53 deletions(-) create mode 100644 src/constant/regex.js diff --git a/src/constant/regex.js b/src/constant/regex.js new file mode 100644 index 00000000..e69de29b diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 07184e0f..42d1b8cf 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -19,13 +19,23 @@ export default class Controller { // validate.isLong(input); const customRegexs = this.extraction.extractCustom(input); - const parsedNumber = this.parser.parseData(customRegexs, input); + + validate.isRegexError(input, customRegexs); + + const parsingCustomText = this.parser.parseCustomRegex( + customRegexs, + input, + ); + const parsedNumber = this.parser.parseData( + customRegexs, + parsingCustomText, + ); validate.isNumber(parsedNumber); const numbers = new Number(parsedNumber); await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } catch (error) { - await outputView.printMessage(`${error.message}`); - throw new Error(error.message); + await outputView.printMessage(`[ERROR]`); + throw new Error(`[ERROR]`); } } } diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index b771c42a..366e408c 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -2,7 +2,15 @@ const regex = /\/\/(.*?)\\n/g; export default class Extraction { extractCustom(inputText) { - const matches = Array.from(inputText.matchAll(regex), (match) => match[1]); + const matches = Array.from( + inputText.matchAll(regex), + (match) => match[1], + ).map((item) => this.#escapeForCharClass(item)); return matches; } + + // 정규 문자열이 포함되는 경우 대비 + #escapeForCharClass(char) { + return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 190476bc..fec181ff 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,28 +1,24 @@ export default class Parser { parseData(regexs, inputText) { - let inputResult = inputText; - - // 커스텀 구분자가 포함되는경우 양식대로 파싱 - regexs.forEach((regex) => { - const splitType = `//${regex}\\n`; - const splitedText = inputResult.split(splitType); - inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; - }); - const totalRegexs = [...regexs, ',', ':']; - const escapedRegexs = totalRegexs.map((regex) => - this.#escapeForCharClass(regex), - ); - const regex = new RegExp(`[${escapedRegexs.join('')}]`); + const regex = new RegExp(`[${totalRegexs.join('')}]`); - return inputResult + return inputText .split(regex) .filter((item) => item !== '') // 빈문자열 제거 .map((item) => Number(item)); } - #escapeForCharClass(char) { - return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + parseCustomRegex(regexs, inputText) { + let inputResult = inputText; + + // 커스텀 구분자가 포함되는경우 양식대로 파싱 + regexs.forEach((regex) => { + const splitType = `//${regex}\\n`; + const splitedText = inputResult.split(splitType); + inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; + }); + return inputResult; } } diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 6020abbb..b17dfb10 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -11,6 +11,13 @@ const validate = { if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); }); }, + isRegexError(inputText, customRegexs) { + // 각 구분자마다 연속 검사 + [',', ':', ...customRegexs].forEach((delim) => { + const regex = new RegExp(`${delim}{2,}`); + if (regex.test(inputText)) throw new Error(ERROR_MESSAGE.NOT_MINUS); + }); + }, // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 // isLong(numbers) { diff --git a/test/Contoller.test.js b/test/Contoller.test.js index 854f5016..26e621a5 100644 --- a/test/Contoller.test.js +++ b/test/Contoller.test.js @@ -55,14 +55,12 @@ describe('Controller 클래스 E2E 테스트', () => { it('//;\\n1;; 연속된 구분자 사용', async () => { inputView.readLineMessage.mockImplementationOnce(() => '//;\\n1;;'); const controller = new Controller(); - await controller.run(); await expect(controller.run()).rejects.toThrow('[ERROR]'); }); it('1;1 미 언급 구분자 사용', async () => { inputView.readLineMessage.mockImplementationOnce(() => '1;1'); const controller = new Controller(); - await controller.run(); await expect(controller.run()).rejects.toThrow('[ERROR]'); }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index 8c452663..979fad75 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -4,8 +4,8 @@ describe('Parser 클래스를 테스트 하다', () => { const parser = new Parser(); it('구분자와 입력을 받아 파싱을 하다', () => { const regxs = ['/', '|']; - const inputText = '/123/2|31'; - expect(parser.parseData(regxs, inputText)).toEqual([123, 2, 31]); + const inputText = '2/123/2|31'; + expect([2, 123, 2, 31]).toEqual(parser.parseData(regxs, inputText)); }); }); @@ -17,51 +17,40 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('구분자는 기본적으로 , : 를 포함합니다', () => { const regxs = []; const inputText = '11:6,1'; - expect(parser.parseData(regxs, inputText)).toEqual([11, 6, 1]); + expect([11, 6, 1]).toEqual(parser.parseData(regxs, inputText)); }); - it('구분자가 숫자인경우', () => { - const regxs = ['1', '2', '3', '#']; - const inputText = '66//1\\n66//2\\n66//3\\n66//#\\n66'; - expect(parser.parseData(regxs, inputText)).toEqual([66, 66, 66, 66, 66]); - }); it('구분자에 \\ 이스케이프가 포함되는경우', () => { const regxs = ['\\']; // 커스텀 구분자: 백슬래시 - const inputText = '//\\\\n1\\2\\3'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); + const inputText = '1\\2\\3'; + expect([1, 2, 3]).toEqual(parser.parseData(regxs, inputText)); }); it('구분자가 여러글자 인경우', () => { const regxs = ['ab', 'bc', 'cd']; - const inputText = '//ab\\n1ab2//bc\\n2//cd\\n3'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 2, 3]); - }); - - it('음수가 포함되는 경우', () => { - const inputText = '-1,2,3'; - expect(parser.parseData([], inputText)).toEqual([-1, 2, 3]); - }); - - it('커스텀 구분자가 포함이 되는경우 //;\\n1', () => { - const inputText = '//;\\n1'; - expect(parser.parseData([';'], inputText)).toEqual([1]); + const inputText = '1ab2bc2cd3'; + expect([1, 2, 2, 3]).toEqual(parser.parseData(regxs, inputText)); }); +}); - it('커스텀 구분자가 처음 적히지 않은경우 ㅅ//;\\n1', () => { - const regxs = [';']; - const inputText = ',//;\\n1'; - expect(parser.parseData(regxs, inputText)).toEqual([1]); - }); +describe('파싱 - 커스텀 구분자', () => { + const parser = new Parser(); it('커스텀 구분자가 여러개 적히는 경우', () => { const regxs = [';', '|']; - const inputText = ',//;\\n1//|\\n'; - expect(parser.parseData(regxs, inputText)).toEqual([1]); + const inputText = '//;\\n1//|\\n'; + expect(';1|').toEqual(parser.parseCustomRegex(regxs, inputText)); }); it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { const regxs = [';', '|']; - const inputText = ',//;\\n1//|\\n1|2'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 1, 2]); + const inputText = '//;\\n1//|\\n1|2'; + expect(';1|1|2').toEqual(parser.parseCustomRegex(regxs, inputText)); + }); + + it('구분자가 연속으로 작성된 경우 //;\\n1;;', () => { + const regxs = [';']; + const inputText = '//;\\n1;;'; + expect(';1;;').toEqual(parser.parseCustomRegex(regxs, inputText)); }); }); diff --git a/test/validate.test.js b/test/validate.test.js index e0848c81..8716a6cd 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -37,3 +37,47 @@ describe('잘못된 입력에 대한 검증', () => { expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); }); }); + +describe('잘못된 구분자에 대한 ERROR', () => { + it('올바른 입력 1,2:3', () => { + const inputText = '1,2:3'; + const regexs = []; + expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( + '[ERROR]', + ); + }); + + it('연속된 구분자 ERROR', () => { + const inputText = '12,,1'; + const regexs = ['a']; + expect(() => validate.isRegexError(inputText, regexs)).toThrow('[ERROR]'); + }); + it('구분자가 선입력된 경우 허용', () => { + const inputText = ',12,1'; + const regexs = []; + expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( + '[ERROR]', + ); + }); + it('커스텀 구분자 양식을 포함한경우 //a\\n123a123', () => { + const inputText = '//a\\n123a123'; + const regexs = ['a']; + expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( + '[ERROR]', + ); + }); + + it('커스텀 구분자 양식이 여러번 포함한경우 //a\\n123a123//n\\123n123', () => { + const inputText = '//a\\n123a123'; + const regexs = ['a', 'n']; + expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( + '[ERROR]', + ); + }); + + it('커스텀 구분자 여러번 - 구분자 여러번 //a\\n123a123//n\\n123n123nn123', () => { + const inputText = '//a\\n123a123//n\\n123n123nn123'; + const regexs = ['a', 'n']; + expect(() => validate.isRegexError(inputText, regexs)).toThrow('[ERROR]'); + }); +}); From d4f6f13aba6188567437ab18cd35fe914fb24a2f Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 14:57:00 +0900 Subject: [PATCH 51/65] =?UTF-8?q?test:=20=EA=B8=B0=EB=8C=80=EA=B0=92?= =?UTF-8?q?=EA=B3=BC=20=EC=8B=A4=EC=A0=9C=EA=B0=92=EC=9D=84=20=EB=B0=98?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EC=9E=91=EC=84=B1=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Parser.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Parser.test.js b/test/Parser.test.js index 979fad75..fa1d97ad 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -5,7 +5,7 @@ describe('Parser 클래스를 테스트 하다', () => { it('구분자와 입력을 받아 파싱을 하다', () => { const regxs = ['/', '|']; const inputText = '2/123/2|31'; - expect([2, 123, 2, 31]).toEqual(parser.parseData(regxs, inputText)); + expect(parser.parseData(regxs, inputText)).toEqual([2, 123, 2, 31]); }); }); @@ -17,18 +17,18 @@ describe('Parser 클래스의 useCase를 추가하다', () => { it('구분자는 기본적으로 , : 를 포함합니다', () => { const regxs = []; const inputText = '11:6,1'; - expect([11, 6, 1]).toEqual(parser.parseData(regxs, inputText)); + expect(parser.parseData(regxs, inputText)).toEqual([11, 6, 1]); }); it('구분자에 \\ 이스케이프가 포함되는경우', () => { const regxs = ['\\']; // 커스텀 구분자: 백슬래시 const inputText = '1\\2\\3'; - expect([1, 2, 3]).toEqual(parser.parseData(regxs, inputText)); + expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); }); it('구분자가 여러글자 인경우', () => { const regxs = ['ab', 'bc', 'cd']; const inputText = '1ab2bc2cd3'; - expect([1, 2, 2, 3]).toEqual(parser.parseData(regxs, inputText)); + expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 2, 3]); }); }); @@ -38,19 +38,19 @@ describe('파싱 - 커스텀 구분자', () => { it('커스텀 구분자가 여러개 적히는 경우', () => { const regxs = [';', '|']; const inputText = '//;\\n1//|\\n'; - expect(';1|').toEqual(parser.parseCustomRegex(regxs, inputText)); + expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1|'); }); it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { const regxs = [';', '|']; const inputText = '//;\\n1//|\\n1|2'; - expect(';1|1|2').toEqual(parser.parseCustomRegex(regxs, inputText)); + expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1|1|2'); }); it('구분자가 연속으로 작성된 경우 //;\\n1;;', () => { const regxs = [';']; const inputText = '//;\\n1;;'; - expect(';1;;').toEqual(parser.parseCustomRegex(regxs, inputText)); + expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1;;'); }); }); From 94aa076fffcbfcc3ec1bc234f549f4e80dbb4d9d Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 15:01:44 +0900 Subject: [PATCH 52/65] =?UTF-8?q?docs:=20=EC=B6=94=EA=B0=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=9D=84=20=EC=9E=91=EC=84=B1=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ src/service/model/Parser.js | 2 -- test/Extraction.test.js | 2 +- test/Parser.test.js | 5 ----- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c83bf34b..bd43f77d 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,5 @@ - 공백 입력을 허용할것인가 - 소수점을 허용할것인가 -> 소수점이 포함된경우 커스텀 구분자로 . 이 오면 허용할것인가 +- 커스텀 구분자 여러개를 허용할것 인가 ? +- 커스텀 구분자가 중간에 나오는것을 허용할것인가? diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index fec181ff..2cf4be2d 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,9 +1,7 @@ export default class Parser { parseData(regexs, inputText) { const totalRegexs = [...regexs, ',', ':']; - const regex = new RegExp(`[${totalRegexs.join('')}]`); - return inputText .split(regex) .filter((item) => item !== '') // 빈문자열 제거 diff --git a/test/Extraction.test.js b/test/Extraction.test.js index 793f88ea..ab050fc1 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -28,6 +28,6 @@ describe('Extraction 클래스 테스트', () => { it('커스텀 구분자가 \\인경우', () => { const inputText = '//\\\\n'; const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual(['\\']); + expect(extraction.extractCustom(inputText)).toEqual(['\\\\']); }); }); diff --git a/test/Parser.test.js b/test/Parser.test.js index fa1d97ad..49a78763 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -20,11 +20,6 @@ describe('Parser 클래스의 useCase를 추가하다', () => { expect(parser.parseData(regxs, inputText)).toEqual([11, 6, 1]); }); - it('구분자에 \\ 이스케이프가 포함되는경우', () => { - const regxs = ['\\']; // 커스텀 구분자: 백슬래시 - const inputText = '1\\2\\3'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 3]); - }); it('구분자가 여러글자 인경우', () => { const regxs = ['ab', 'bc', 'cd']; const inputText = '1ab2bc2cd3'; From 77b99acb2b40d30f53c38207cfc97a2aeade8b5c Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 15:44:32 +0900 Subject: [PATCH 53/65] =?UTF-8?q?fix:=20parsing=EC=9D=B4=20=EC=A0=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EB=90=98=EC=A7=80=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + error_cases/last_error.json | 487 +++++++++++++++++++++++++------ src/controller/Controller.js | 11 +- src/service/model/Parser.js | 15 +- src/service/validate/validate.js | 19 +- test/RandomMaker.test.js | 66 ++--- 6 files changed, 460 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index bd43f77d..6eed678b 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,4 @@ -> 소수점이 포함된경우 커스텀 구분자로 . 이 오면 허용할것인가 - 커스텀 구분자 여러개를 허용할것 인가 ? - 커스텀 구분자가 중간에 나오는것을 허용할것인가? +- 커스텀 구분자가 . 일때 .. 처리를 어케할것인지 diff --git a/error_cases/last_error.json b/error_cases/last_error.json index 7196e9fa..cbdcbb86 100644 --- a/error_cases/last_error.json +++ b/error_cases/last_error.json @@ -1,101 +1,406 @@ { - "input": ",83006//f\\n//c\\n:19324//R\\nc8653,,,::41525:10921//W\\n67061//U\\n96846:,//(\\n31146(78963U//)\\n88393://\"\\n92048//H\\n98668//]\\n//_\\n//:\\nR://h\\n):98030]//,\\n:,:,//'\\n,15504::24250'::30969,,54761//[\\n_W31402:'9711,8336::4218//E\\n//M\\n16275//i\\n:,71328:::M'3748,:,,4384\":://X\\n,:26378[56799,,,61476[96079:,:M,E86471//<\\n,26654):89022W,,W,1629]//|\\n)H84782,:,,:R8671,://+\\n,85327f,17869_,:,,,HW83504(c,\"12752_63484:31096:,R//o\\n26122h56709i:[,o54794,H://;\\n51871':Uo'<89974,68571[:,26187//T\\n33288:19456hcW,10402f:51620i],59049:f,Ec64925:,,:2818X56763':_,U|:X65431::4350,,;98114c,,63821\":;90621:iUMR,18394h:,):,':,,(:::,89494,65003,:EWi,(),20106,16099T15945HW+\"", - "output": 3141390, + "input": "//Z\\n//:\\n//.\\n//]\\n//n\\n//E\\n//+\\n//^\\n//g\\n//}\\n//p\\n//K\\n//?\\n//l\\n//S\\n//I\\n//=\\n//D\\n//>\\n//#\\n//k\\n//|\\n//&\\n//*\\n//i\\n//a\\n//%\\n//s\\n//h\\n//e\\n//y\\n//q\\n//f\\n//N\\n//t\\n//\\\\n//,\\n//!\\n//b\\n//H\\n//r\\n15661\\38867]89744^27745i99806q96252h33258:46283S67666}68712*7708,40228s85279h47290s65855b76314S98442H62180r52352h14594h41072&50835k7640]52547l45283%20439I22642a72938K2941?93058:29321t74093^57572D74959*66598r35485>49690K40473y96174,34686#61054|85971t11874I97108D47196y60398H75870t42160,34139|74554?13098>34312S24969.48172H83061.76730S39147l78815I69783l87499k99968b74834Z39335h7925>80167#57349>5378q88688y57675>49721k9050s44070i18336s91174+45096\\98059Z97130:10014b98118b80604I37254]15824D6340?4514%347h16797i77831|32527K29621=75029k63539H96567h90395r1186?22631y69174D18191y81079S33031Z77072f28199*33088l86123+6614]35135K64160=94909]56227S24031r64991|26335:12478K56462Z16962|85822p52950?4282}66255\\21876?20394:41781>50932:78289e54560H19651Z6461q74209h35202,15036#65830=66656,76637S45178>25967:91359%73222>64485,68125.26390s65454t52614+15878k7426%12996|2171Z43837h69274a22707*16153.43286&55622,77402!32928N57240t50926&62464*23605D58407,11328s90586K93712e85018H25533y21312]69442N26779%42012:12421k68798s10073r1029D95887=24340n85375k81336k54621E6335Z55948b10623i73737i89837k79347h90075D37362D35632:28210f89314f52842>38785Z4197g34626n63366n32855N20097,15009\\13341|39161p50946K52449l15049I68575y86818E26166!96369p51709Z84982D19954n22187e27972s19981Z2819:55477b12418s48190k95502l16904b40345*62187k92015i35916b61272E94561f53759?41469I94875&4718,58205]85847]52134.45800S78167f68017E51992k89547E37793:22999=56682s83545g32326y78493!55236i23939*74031>55935^9498E5909%47459g27934f7579!66297f67683b5225*97952&35409Z56156#85073l95899l29426!54394n17620K91856k3618&42760h36003i13485!19368|67044g35700e41619?65143]26036a44505y57240,35798|15161g12940i91849S19463h95297n32758e66220r45942i72266b97159%46190?5247\\14255:1626\\80167!61777N25759\\85662a66707N48819!67178K27181N48898=64060s63447!87082:48873f16128D14455*71455E46788\\47025:63306p72130S76765q44803f97099^1278y22483r98357p89615H74671,72050e65597t69648f74331:16093|48088b19482,25795S94817p83455q87177\\64476&43980|54611q57445,46400E71705>50587.27690=89040*51838f81505t81975r64645*11652,76064I92214s93244K37836^36819:23425t47881I54477.11861r66974s51669+24911\\88305,65199a4342b52038", + "output": 17953828, "extracionRegex": [ - "R", - "<", - "\"", - "i", - ",", - "X", - "_", - "T", - "[", - "h", - ")", - "f", - "o", - "|", - "(", - "W", - "E", - "M", + "Z", ":", - "c", - "U", + ".", "]", + "n", + "E", "+", - ";", + "^", + "g", + "}", + "p", + "K", + "?", + "l", + "S", + "I", + "=", + "D", + ">", + "#", + "k", + "|", + "&", + "*", + "i", + "a", + "%", + "s", + "h", + "e", + "y", + "q", + "f", + "N", + "t", + "\\", + ",", + "!", + "b", "H", - "'" + "r" ], "parseMumber": [ - 83006, - 19324, - 8653, - 41525, - 10921, - 67061, - 96846, - 31146, - 78963, - 88393, - 92048, - 98668, - 98030, - 15504, - 24250, - 30969, - 54761, - 31402, - 9711, - 8336, - 4218, - 16275, - 71328, - 3748, - 4384, - 26378, - 56799, - 61476, - 96079, - 86471, - 26654, - 89022, - 1629, - 84782, - 8671, - 85327, - 17869, - 83504, - 12752, - 63484, - 31096, - 26122, - 56709, - 54794, - 51871, - 89974, - 68571, - 26187, - 33288, - 19456, - 10402, - 51620, - 59049, - 64925, - 2818, - 56763, - 65431, - 4350, - 98114, - 63821, - 90621, - 18394, - 89494, - 65003, - 20106, - 16099, - 15945 + 15661, + 38867, + 89744, + 27745, + 99806, + 96252, + 33258, + 46283, + 67666, + 68712, + 7708, + 40228, + 85279, + 47290, + 65855, + 76314, + 98442, + 62180, + 52352, + 14594, + 41072, + 50835, + 7640, + 52547, + 45283, + 20439, + 22642, + 72938, + 2941, + 93058, + 29321, + 74093, + 57572, + 74959, + 66598, + 35485, + 49690, + 40473, + 96174, + 34686, + 61054, + 85971, + 11874, + 97108, + 47196, + 60398, + 75870, + 42160, + 34139, + 74554, + 13098, + 34312, + 24969, + 48172, + 83061, + 76730, + 39147, + 78815, + 69783, + 87499, + 99968, + 74834, + 39335, + 7925, + 80167, + 57349, + 5378, + 88688, + 57675, + 49721, + 9050, + 44070, + 18336, + 91174, + 45096, + 98059, + 97130, + 10014, + 98118, + 80604, + 37254, + 15824, + 6340, + 4514, + 347, + 16797, + 77831, + 32527, + 29621, + 75029, + 63539, + 96567, + 90395, + 1186, + 22631, + 69174, + 18191, + 81079, + 33031, + 77072, + 28199, + 33088, + 86123, + 6614, + 35135, + 64160, + 94909, + 56227, + 24031, + 64991, + 26335, + 12478, + 56462, + 16962, + 85822, + 52950, + 4282, + 66255, + 21876, + 20394, + 41781, + 50932, + 78289, + 54560, + 19651, + 6461, + 74209, + 35202, + 15036, + 65830, + 66656, + 76637, + 45178, + 25967, + 91359, + 73222, + 64485, + 68125, + 26390, + 65454, + 52614, + 15878, + 7426, + 12996, + 2171, + 43837, + 69274, + 22707, + 16153, + 43286, + 55622, + 77402, + 32928, + 57240, + 50926, + 62464, + 23605, + 58407, + 11328, + 90586, + 93712, + 85018, + 25533, + 21312, + 69442, + 26779, + 42012, + 12421, + 68798, + 10073, + 1029, + 95887, + 24340, + 85375, + 81336, + 54621, + 6335, + 55948, + 10623, + 73737, + 89837, + 79347, + 90075, + 37362, + 35632, + 28210, + 89314, + 52842, + 38785, + 4197, + 34626, + 63366, + 32855, + 20097, + 15009, + 13341, + 39161, + 50946, + 52449, + 15049, + 68575, + 86818, + 26166, + 96369, + 51709, + 84982, + 19954, + 22187, + 27972, + 19981, + 2819, + 55477, + 12418, + 48190, + 95502, + 16904, + 40345, + 62187, + 92015, + 35916, + 61272, + 94561, + 53759, + 41469, + 94875, + 4718, + 58205, + 85847, + 52134, + 45800, + 78167, + 68017, + 51992, + 89547, + 37793, + 22999, + 56682, + 83545, + 32326, + 78493, + 55236, + 23939, + 74031, + 55935, + 9498, + 5909, + 47459, + 27934, + 7579, + 66297, + 67683, + 5225, + 97952, + 35409, + 56156, + 85073, + 95899, + 29426, + 54394, + 17620, + 91856, + 3618, + 42760, + 36003, + 13485, + 19368, + 67044, + 35700, + 41619, + 65143, + 26036, + 44505, + 57240, + 35798, + 15161, + 12940, + 91849, + 19463, + 95297, + 32758, + 66220, + 45942, + 72266, + 97159, + 46190, + 5247, + 14255, + 1626, + 80167, + 61777, + 25759, + 85662, + 66707, + 48819, + 67178, + 27181, + 48898, + 64060, + 63447, + 87082, + 48873, + 16128, + 14455, + 71455, + 46788, + 47025, + 63306, + 72130, + 76765, + 44803, + 97099, + 1278, + 22483, + 98357, + 89615, + 74671, + 72050, + 65597, + 69648, + 74331, + 16093, + 48088, + 19482, + 25795, + 94817, + 83455, + 87177, + 64476, + 43980, + 54611, + 57445, + 46400, + 71705, + 50587, + 27690, + 89040, + 51838, + 81505, + 81975, + 64645, + 11652, + 76064, + 92214, + 93244, + 37836, + 36819, + 23425, + 47881, + 54477, + 11861, + 66974, + 51669, + 24911, + 88305, + 65199, + 4342, + 52038 ] } \ No newline at end of file diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 42d1b8cf..4b3566ea 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -20,22 +20,27 @@ export default class Controller { const customRegexs = this.extraction.extractCustom(input); - validate.isRegexError(input, customRegexs); + validate.isRegexValidError(customRegexs); + validate.isRegexContinueError(input, customRegexs); + console.log(customRegexs); const parsingCustomText = this.parser.parseCustomRegex( customRegexs, input, ); + console.log(parsingCustomText); + const parsedNumber = this.parser.parseData( customRegexs, parsingCustomText, ); + console.log(parsedNumber); validate.isNumber(parsedNumber); const numbers = new Number(parsedNumber); await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } catch (error) { - await outputView.printMessage(`[ERROR]`); - throw new Error(`[ERROR]`); + await outputView.printMessage(error.message); + throw new Error(error.message); } } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 2cf4be2d..ece50fbe 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,7 +1,7 @@ export default class Parser { parseData(regexs, inputText) { const totalRegexs = [...regexs, ',', ':']; - const regex = new RegExp(`[${totalRegexs.join('')}]`); + const regex = new RegExp(totalRegexs.join('|')); return inputText .split(regex) .filter((item) => item !== '') // 빈문자열 제거 @@ -9,14 +9,9 @@ export default class Parser { } parseCustomRegex(regexs, inputText) { - let inputResult = inputText; - - // 커스텀 구분자가 포함되는경우 양식대로 파싱 - regexs.forEach((regex) => { - const splitType = `//${regex}\\n`; - const splitedText = inputResult.split(splitType); - inputResult = `${splitedText[0]}${regex}${splitedText[1]}`; - }); - return inputResult; + let result = inputText; + const pattern = new RegExp(`//${regexs}\\n`, 'g'); + result = result.replace(pattern, ''); + return result; } } diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index b17dfb10..9cb507f2 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -11,11 +11,26 @@ const validate = { if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); }); }, - isRegexError(inputText, customRegexs) { + isRegexContinueError(inputText, customRegexs) { // 각 구분자마다 연속 검사 [',', ':', ...customRegexs].forEach((delim) => { const regex = new RegExp(`${delim}{2,}`); - if (regex.test(inputText)) throw new Error(ERROR_MESSAGE.NOT_MINUS); + if (regex.test(inputText)) throw new Error('연속된 구분자가 존재합니다'); + }); + }, + isRegexValidError(customRegexs) { + customRegexs.forEach((regex) => { + if (!regex) { + throw new Error('구분자가 공백입니다'); + } + // 공백 포함 체크 + if (/\s/.test(regex)) { + throw new Error('구분자가 공백입니다'); + } + // 숫자 포함 체크 + if (/\d/.test(regex)) { + throw new Error('구분자가 숫자입니다'); + } }); }, diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 7eaca342..2c3de91d 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -60,10 +60,9 @@ const makeCustomRegex = (customRegexLength) => { const customRegexs = []; for (let i = 0; i < customRegexLength; i++) { let charCode; - // 48 ('0') ~ 57 ('9') 사이의 숫자가 나오면 다시 뽑습니다. do { - charCode = Random.pickNumberInRange(32, 126); - } while (charCode >= 48 && charCode <= 57); + charCode = Random.pickNumberInRange(33, 126); // 32번(공백) 제외 + } while (charCode >= 48 && charCode <= 57); // 숫자 제외 customRegexs.push(String.fromCharCode(charCode)); } return [...new Set(customRegexs)]; @@ -79,46 +78,47 @@ const getRandomInput = () => { const data = JSON.parse(fs.readFileSync(errorFilePath, 'utf-8')); return [data.input, data.output, data.parseMumber, data.extracionRegex]; } - let inputResult = ''; + + let definitionString = ''; // "//P\n//l\n" 등 정의가 담길 부분 + let numberString = ''; // "123P456l789..." 숫자가 담길 부분 let outputResult = 0; - // 커스텀 구분자 개수를 정하다 const customRegexLength = getRandomNumber(1, 100); const madeCustomRegex = makeCustomRegex(customRegexLength); - const addedCustomRegex = []; const parseMumber = []; - // 숫자 개수를 정하다 + const usedCustomRegex = []; + const numToUse = getRandomNumber(1, madeCustomRegex.length); // 최소 1개는 사용 + while (usedCustomRegex.length < numToUse) { + const regex = getRandomValueInArray(madeCustomRegex); + if (!usedCustomRegex.includes(regex)) { + usedCustomRegex.push(regex); + } + } + + usedCustomRegex.forEach((regex) => { + definitionString += getCustomRegexWithText(regex); // `//${regex}\n` + }); + + const allAvailableDelimiters = [...usedCustomRegex, ':', ',']; + const numberLength = getRandomNumber(1, 1000); - let isLastNumber = false; + for (let i = 0; i < numberLength; i++) { - const testNumber = getRandomNumber(0, 2); - if ( - testNumber === 0 && - !isLastNumber && - !madeCustomRegex.includes(testNumber) - ) { - const randNumber = getRandomNumber(0, 100000); - inputResult += randNumber; // 추가해야 함 - outputResult += randNumber; - parseMumber.push(randNumber); - isLastNumber = true; - } else if (testNumber === 1) { - const randDefaultRegex = getRandomValueInArray([':', ',']); - inputResult += randDefaultRegex; // 추가해야 함 - isLastNumber = false; - } else { - const randCustomRegex = getRandomValueInArray(madeCustomRegex); - if (!addedCustomRegex.includes(randCustomRegex)) { - inputResult += getCustomRegexWithText(randCustomRegex); // 추가해야 함 - addedCustomRegex.push(randCustomRegex); - } else { - inputResult += randCustomRegex; - } - isLastNumber = false; + const randNumber = getRandomNumber(0, 100000); + numberString += randNumber; // 숫자 문자열에 추가 + outputResult += randNumber; + parseMumber.push(randNumber); + + if (i < numberLength - 1) { + const delimiter = getRandomValueInArray(allAvailableDelimiters); + numberString += delimiter; // `//P\n`가 아닌 `P` 자체가 추가됨 } } - return [inputResult, outputResult, parseMumber, madeCustomRegex]; + + const inputResult = definitionString + numberString; + + return [inputResult, outputResult, parseMumber, usedCustomRegex]; }; describe('랜덤문자열 생성기', () => { From 9e674426d9dc635a34b2c67ab35c34360a73d58f Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 15:54:34 +0900 Subject: [PATCH 54/65] =?UTF-8?q?test:=20=EC=A0=84=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/model/Parser.js | 14 +- src/service/validate/validate.js | 9 +- test/Parser.test.js | 17 +-- test/validate.test.js | 220 ++++++++++++++++++++++--------- 4 files changed, 177 insertions(+), 83 deletions(-) diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index ece50fbe..e583a929 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -9,9 +9,15 @@ export default class Parser { } parseCustomRegex(regexs, inputText) { - let result = inputText; - const pattern = new RegExp(`//${regexs}\\n`, 'g'); - result = result.replace(pattern, ''); - return result; + let inputResult = inputText; + + regexs.forEach((regex) => { + const splitType = `//${regex}\\n`; + if (inputResult.startsWith(splitType)) { + inputResult = inputResult.slice(splitType.length); + } + }); + + return inputResult; } } diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 9cb507f2..befebb1a 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -15,21 +15,22 @@ const validate = { // 각 구분자마다 연속 검사 [',', ':', ...customRegexs].forEach((delim) => { const regex = new RegExp(`${delim}{2,}`); - if (regex.test(inputText)) throw new Error('연속된 구분자가 존재합니다'); + if (regex.test(inputText)) + throw new Error('[ERROR] 연속된 구분자가 존재합니다'); }); }, isRegexValidError(customRegexs) { customRegexs.forEach((regex) => { if (!regex) { - throw new Error('구분자가 공백입니다'); + throw new Error('[ERROR]구분자가 공백입니다'); } // 공백 포함 체크 if (/\s/.test(regex)) { - throw new Error('구분자가 공백입니다'); + throw new Error('[ERROR] 구분자가 공백입니다'); } // 숫자 포함 체크 if (/\d/.test(regex)) { - throw new Error('구분자가 숫자입니다'); + throw new Error('[ERROR] 구분자가 숫자입니다'); } }); }, diff --git a/test/Parser.test.js b/test/Parser.test.js index 49a78763..8c9ecb9a 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -4,6 +4,7 @@ describe('Parser 클래스를 테스트 하다', () => { const parser = new Parser(); it('구분자와 입력을 받아 파싱을 하다', () => { const regxs = ['/', '|']; + // regexs 테스트시 이스케이프 처리를 해주고 넘겨야함 const inputText = '2/123/2|31'; expect(parser.parseData(regxs, inputText)).toEqual([2, 123, 2, 31]); }); @@ -32,20 +33,8 @@ describe('파싱 - 커스텀 구분자', () => { it('커스텀 구분자가 여러개 적히는 경우', () => { const regxs = [';', '|']; - const inputText = '//;\\n1//|\\n'; - expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1|'); - }); - - it('커스텀 구분자가 여러개 적히고 숫자가 사이에 있는경우', () => { - const regxs = [';', '|']; - const inputText = '//;\\n1//|\\n1|2'; - expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1|1|2'); - }); - - it('구분자가 연속으로 작성된 경우 //;\\n1;;', () => { - const regxs = [';']; - const inputText = '//;\\n1;;'; - expect(parser.parseCustomRegex(regxs, inputText)).toEqual(';1;;'); + const inputText = '//;\\n//|\\n1'; + expect(parser.parseCustomRegex(regxs, inputText)).toEqual('1'); }); }); diff --git a/test/validate.test.js b/test/validate.test.js index 8716a6cd..7c16e955 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -1,83 +1,181 @@ // 잘못된 입력에 대한 검증 import validate from '../src/service/validate/validate.js'; +import { ERROR_MESSAGE } from '../src/constant/error.js'; -describe('잘못된 입력에 대한 검증', () => { - // 파싱 전 - 예외 +describe('validate.isNumber - 숫자 검증', () => { + describe('정상 케이스', () => { + it('정상적인 숫자 배열은 에러를 발생시키지 않는다', () => { + const numbers = [1, 2, 3]; + expect(() => validate.isNumber(numbers)).not.toThrow(); + }); - // it('너무 긴 메시지가(50자) 입력된 경우 ERROR', () => { - // const input = - // '1234567891011122312312312312312312312312321312312312312312312313'; - // expect(() => validate.isLong(input)).toThrow('[ERROR]'); - // }); + it('0을 포함한 숫자 배열은 정상 처리된다', () => { + const numbers = [0, 1, 2]; + expect(() => validate.isNumber(numbers)).not.toThrow(); + }); - it('숫자가 아닌값이 포함된 경우 ERROR', () => { - const parseInput = ['우', '테', '코', 1]; - expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); + it('문자열 형태의 숫자는 정상 처리된다', () => { + const numbers = ['1', '2', '3']; + expect(() => validate.isNumber(numbers)).not.toThrow(); + }); }); - // it('빈 입력 이 포함된 경우 "" 빈값으로 0으로 허용', () => { - // const parseInput = ['', 1, 2, 1]; - // expect(() => validate.isNumber(parseInput)).not.toThrow('[ERROR]'); - // }); - // it('공백 입력인경우 " " 빈값으로 0으로 허용', () => { - // const parseInput = [' ', 1, ' ', 1]; - // expect(() => validate.isNumber(parseInput)).not.toThrow('[ERROR]'); - // }); - - it('빈 입력 이 포함된 경우 "" ERROR', () => { - const parseInput = ['', 1, 2, 1]; - expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); + + describe('예외 케이스 - 숫자가 아닌 값', () => { + it('숫자가 아닌 문자가 포함된 경우 에러를 발생시킨다', () => { + const numbers = ['우', '테', '코', 1]; + expect(() => validate.isNumber(numbers)).toThrow( + ERROR_MESSAGE.NOT_NUMBER, + ); + }); + + it('알파벳이 포함된 경우 에러를 발생시킨다', () => { + const numbers = ['a', 'b', 1]; + expect(() => validate.isNumber(numbers)).toThrow( + ERROR_MESSAGE.NOT_NUMBER, + ); + }); }); - it('공백 입력인경우 " " ERROR', () => { - const parseInput = [' ', 1, ' ', 1]; - expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); + + describe('예외 케이스 - 공백/빈값', () => { + it('빈 문자열이 포함된 경우 에러를 발생시킨다', () => { + const numbers = ['', 1, 2, 1]; + expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + }); + + it('공백 문자열이 포함된 경우 에러를 발생시킨다', () => { + const numbers = [' ', 1, 2]; + expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + }); + + it('여러 개의 공백이 포함된 경우 에러를 발생시킨다', () => { + const numbers = [' ', 1, 2]; + expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + }); }); - it('음수가 입력되는 경우 ERROR', () => { - const parseInput = [-1, 2, 3]; - expect(() => validate.isNumber(parseInput)).toThrow('[ERROR]'); + describe('예외 케이스 - 음수', () => { + it('음수가 포함된 경우 에러를 발생시킨다', () => { + const numbers = [-1, 2, 3]; + expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_MINUS); + }); + + it('0보다 작은 음수가 포함된 경우 에러를 발생시킨다', () => { + const numbers = [1, -5, 3]; + expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_MINUS); + }); }); }); -describe('잘못된 구분자에 대한 ERROR', () => { - it('올바른 입력 1,2:3', () => { - const inputText = '1,2:3'; - const regexs = []; - expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( - '[ERROR]', - ); +describe('validate.isRegexContinueError - 연속된 구분자 검증', () => { + describe('정상 케이스', () => { + it('구분자가 연속되지 않은 경우 에러를 발생시키지 않는다', () => { + const inputText = '1,2:3'; + const customRegexs = []; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).not.toThrow(); + }); + + it('커스텀 구분자가 연속되지 않은 경우 에러를 발생시키지 않는다', () => { + const inputText = '1;2;3'; + const customRegexs = [';']; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).not.toThrow(); + }); }); - it('연속된 구분자 ERROR', () => { - const inputText = '12,,1'; - const regexs = ['a']; - expect(() => validate.isRegexError(inputText, regexs)).toThrow('[ERROR]'); + describe('예외 케이스 - 연속된 구분자', () => { + it('쉼표가 연속된 경우 에러를 발생시킨다', () => { + const inputText = '1,,2'; + const customRegexs = []; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).toThrow('[ERROR]'); + }); + + it('콜론이 연속된 경우 에러를 발생시킨다', () => { + const inputText = '1::2'; + const customRegexs = []; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).toThrow('[ERROR]'); + }); + + it('커스텀 구분자가 연속된 경우 에러를 발생시킨다', () => { + const inputText = '1;;2'; + const customRegexs = [';']; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).toThrow('[ERROR]'); + }); + + it('세 개 이상 연속된 구분자도 에러를 발생시킨다', () => { + const inputText = '1,,,2'; + const customRegexs = []; + expect(() => + validate.isRegexContinueError(inputText, customRegexs), + ).toThrow('[ERROR]'); + }); }); - it('구분자가 선입력된 경우 허용', () => { - const inputText = ',12,1'; - const regexs = []; - expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( - '[ERROR]', - ); +}); + +describe('validate.isRegexValidError - 구분자 유효성 검증', () => { + describe('정상 케이스', () => { + it('올바른 커스텀 구분자는 에러를 발생시키지 않는다', () => { + const customRegexs = [';', '|']; + expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + }); + + it('특수문자 구분자는 정상 처리된다', () => { + const customRegexs = ['!', '@', '#']; + expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + }); + + it('한글 구분자는 정상 처리된다', () => { + const customRegexs = ['가', 'ㄱ']; + expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + }); }); - it('커스텀 구분자 양식을 포함한경우 //a\\n123a123', () => { - const inputText = '//a\\n123a123'; - const regexs = ['a']; - expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( - '[ERROR]', - ); + + describe('예외 케이스 - 빈 구분자', () => { + it('빈 문자열 구분자는 에러를 발생시킨다', () => { + const customRegexs = ['']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); + + it('여러 구분자 중 빈 값이 포함된 경우 에러를 발생시킨다', () => { + const customRegexs = [';', '', '|']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); }); - it('커스텀 구분자 양식이 여러번 포함한경우 //a\\n123a123//n\\123n123', () => { - const inputText = '//a\\n123a123'; - const regexs = ['a', 'n']; - expect(() => validate.isRegexError(inputText, regexs)).not.toThrow( - '[ERROR]', - ); + describe('예외 케이스 - 공백 포함', () => { + it('공백이 포함된 구분자는 에러를 발생시킨다', () => { + const customRegexs = [' ']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); + + it('구분자에 공백이 섞여있는 경우 에러를 발생시킨다', () => { + const customRegexs = ['a b']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); }); - it('커스텀 구분자 여러번 - 구분자 여러번 //a\\n123a123//n\\n123n123nn123', () => { - const inputText = '//a\\n123a123//n\\n123n123nn123'; - const regexs = ['a', 'n']; - expect(() => validate.isRegexError(inputText, regexs)).toThrow('[ERROR]'); + describe('예외 케이스 - 숫자 포함', () => { + it('숫자가 포함된 구분자는 에러를 발생시킨다', () => { + const customRegexs = ['1']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); + + it('문자와 숫자가 섞인 구분자는 에러를 발생시킨다', () => { + const customRegexs = ['a1']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); + + it('여러 구분자 중 숫자가 포함된 경우 에러를 발생시킨다', () => { + const customRegexs = [';', '5', '|']; + expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + }); }); }); From 88f66bf3c4797bf3b16833528afb83fdaea4c396 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 21:02:00 +0900 Subject: [PATCH 55/65] =?UTF-8?q?refactor:=20parser=EA=B0=80=20regex?= =?UTF-8?q?=EB=A5=BC=20=EA=B4=80=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=9C=84=EC=9E=84=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 1030 +++++++++++++++++++------------ src/controller/Controller.js | 13 +- src/service/model/Extraction.js | 10 +- src/service/model/Parser.js | 20 +- 4 files changed, 654 insertions(+), 419 deletions(-) diff --git a/error_cases/last_error.json b/error_cases/last_error.json index cbdcbb86..bc885c76 100644 --- a/error_cases/last_error.json +++ b/error_cases/last_error.json @@ -1,406 +1,642 @@ { - "input": "//Z\\n//:\\n//.\\n//]\\n//n\\n//E\\n//+\\n//^\\n//g\\n//}\\n//p\\n//K\\n//?\\n//l\\n//S\\n//I\\n//=\\n//D\\n//>\\n//#\\n//k\\n//|\\n//&\\n//*\\n//i\\n//a\\n//%\\n//s\\n//h\\n//e\\n//y\\n//q\\n//f\\n//N\\n//t\\n//\\\\n//,\\n//!\\n//b\\n//H\\n//r\\n15661\\38867]89744^27745i99806q96252h33258:46283S67666}68712*7708,40228s85279h47290s65855b76314S98442H62180r52352h14594h41072&50835k7640]52547l45283%20439I22642a72938K2941?93058:29321t74093^57572D74959*66598r35485>49690K40473y96174,34686#61054|85971t11874I97108D47196y60398H75870t42160,34139|74554?13098>34312S24969.48172H83061.76730S39147l78815I69783l87499k99968b74834Z39335h7925>80167#57349>5378q88688y57675>49721k9050s44070i18336s91174+45096\\98059Z97130:10014b98118b80604I37254]15824D6340?4514%347h16797i77831|32527K29621=75029k63539H96567h90395r1186?22631y69174D18191y81079S33031Z77072f28199*33088l86123+6614]35135K64160=94909]56227S24031r64991|26335:12478K56462Z16962|85822p52950?4282}66255\\21876?20394:41781>50932:78289e54560H19651Z6461q74209h35202,15036#65830=66656,76637S45178>25967:91359%73222>64485,68125.26390s65454t52614+15878k7426%12996|2171Z43837h69274a22707*16153.43286&55622,77402!32928N57240t50926&62464*23605D58407,11328s90586K93712e85018H25533y21312]69442N26779%42012:12421k68798s10073r1029D95887=24340n85375k81336k54621E6335Z55948b10623i73737i89837k79347h90075D37362D35632:28210f89314f52842>38785Z4197g34626n63366n32855N20097,15009\\13341|39161p50946K52449l15049I68575y86818E26166!96369p51709Z84982D19954n22187e27972s19981Z2819:55477b12418s48190k95502l16904b40345*62187k92015i35916b61272E94561f53759?41469I94875&4718,58205]85847]52134.45800S78167f68017E51992k89547E37793:22999=56682s83545g32326y78493!55236i23939*74031>55935^9498E5909%47459g27934f7579!66297f67683b5225*97952&35409Z56156#85073l95899l29426!54394n17620K91856k3618&42760h36003i13485!19368|67044g35700e41619?65143]26036a44505y57240,35798|15161g12940i91849S19463h95297n32758e66220r45942i72266b97159%46190?5247\\14255:1626\\80167!61777N25759\\85662a66707N48819!67178K27181N48898=64060s63447!87082:48873f16128D14455*71455E46788\\47025:63306p72130S76765q44803f97099^1278y22483r98357p89615H74671,72050e65597t69648f74331:16093|48088b19482,25795S94817p83455q87177\\64476&43980|54611q57445,46400E71705>50587.27690=89040*51838f81505t81975r64645*11652,76064I92214s93244K37836^36819:23425t47881I54477.11861r66974s51669+24911\\88305,65199a4342b52038", - "output": 17953828, + "input": "//'\\n//o\\n//q\\n//|\\n//,\\n//>\\n//[\\n//}\\n//k\\n//Y\\n//c\\n83829Y98538>43955[70659|49574|87527,39113Y20104}25174|28221[50047c46129'53860|21615c4574|98868Y45088,95221'27952[33522o48463c39633,43893k52473:6039>75201,49185|94330>81443o67449o87554,38084>9385:57094o10056[42452[7059'44285,63660c78250q45891o9356q57012q50969}75002:93929:53791q252>76532Y91713>30475>57365o98108'47772:7038>36232,71688Y64117[44865c58567,28251>88621'25669q33250[27380o3520>53018o90189k28526c92870k36546o65796c68526|13050k36554|26642>20858,65379[52841|61852'25019[90559c19371:41593|73056>14953>6063q97098,2907[45332}80802,90124:40095}58565k33229|61423'43654'42493}19136Y26202|24420Y12250k2860q29891:66457k7810,35999,20287}28556c56632q50486Y13949>85916[74374:43362q62328Y63069>15314:43452|78724q30813Y12727c17045Y23139k36782o81822[41845[3727,37144[99901[81249>71447:25710,2273c93729>6024|58474,78617,82426o48028,64990k16180q80929k5245Y17735c48297k26144Y88427,15054c34112,38928c46897Y40058|98576k74194,38892>75889c37779[91170Y35895,48988>66459:48126q42100k86840'22563:75310[77297}60755>2938c70381:74803q24731Y73891q30583|23801}65370:15444Y53289k91865Y89320c66192q81950'51578}99539[31350}96627Y91653:32036c79464,16931'52258}25565c13969[87970:23839}76843c4040k59782,20623c32224q82527c92499q92745o6711,56497'74819c54853[18048o75147c47982o50225'16930o71614,11333|90943Y60389,32072>37556:70650>477Y4318q71936}58287,85772[27702|92504,6351c46162Y58833c27261>41846Y71913Y49842}90437:18674c95239'32262>32297c90836k17705q70376}14388o63641,24046,56461,20223k6899[80495k19966}38678'12722o84571:69386o7387,8947>22266:35253'52026}51318,97404k90739:24981|80410,82560|46484,79748c57401>72620'17205:4601k11991Y74422q63611k92577,39201|17331'82435o21736k57985c94713k7542,72470q30780}95165|23406q22855Y88085o1677,66109}75207'33493k96917,11004Y76966Y6002,74799c34235k72097o61956[81381k53015Y46435>72808>49607>80314:5476[85468q13234c47497,32430Y11982,37988>76379>75007k1856c8577,84135>35376,50601}73879c37005c35454o61762c55981o87034>13257Y82036}38872o54587Y59078q72626[50731[34007'2902o45720Y1307,60431[23593k31923|52309,37125o25314|50318,86171k84981:72364>21138c5270,71289c56211Y12718}347:20994c32399c42294c93o98343c32182|94365q7080|82017o9988:84941>62733q10214o11028>85300q5591|96003>78785c50079c16077q6425|50669[89795c55175c82064,17905}61320Y38551c15930Y83117c56858,39382Y69488Y57068[83255:24495>37146[79259|3403[17000[43850,52526'56448,7188>85598c73547[59826k39499c21093>45426Y65244,7848[62951|16514>31439'92477c79574c56926|34114'71849o21630}39804q1120}6688>94241c92716k37741>77292k47075'37085>21803}91184c13173>56014Y97712c59986,64784[43797,77639'1802Y59069c77591:13220[78450,17897'77751,9866,31908,91074'32409[79652o2681[66586k92306:50402q17298[54993Y15144'11888>28585:6219,61058[31066o33492o54121,88684>26591:45489o11399Y97736|62181|4043'410k56571}76079q82584}8807,7999Y23345:3222:63578q23018|58673Y96508'77287>22007>40820c99319,55749Y60711c26981,49633:67744q94913>25625>79523}68759:8239>97981>11231>51456,96621>1556'54781q55682[66237[56554[42380c73041,52689>87143[70097,74719>61232q92573q46963q71165>80874o66308'22714,55132,98520[99582'75752'86468k48180'59043>25732c89484}67202,88434[60323[31201o22375|68004Y17033}90763:83695q57995,80909'85012Y10155}83271:85105,89071q44894Y20984[92865'94522c98578}85524,81050|82458c5517|37779[58636|3407}30942[40892:34238,33868q38760Y10991k1259[48424|62861,17582:71918:40456,6883}8393,31406>10181:58812Y77008[61260'45414>83722}35828q65864o73241}51316,36920'93335}96535k41715[79990o90275,77140:63893'59147q90654Y96010k43302|22465,65267}41721Y74355,8131'40148[99637q5307'98832|92077q29936k74541|71404'56578c80160q12862:62788'18345,2212[87692Y68926q58846Y18195Y33692'91902k70264}56332o29956c58855,59286'22482c3053c18321q47917[92819>46643", + "output": 31262470, "extracionRegex": [ - "Z", - ":", - ".", - "]", - "n", - "E", - "+", - "^", - "g", - "}", - "p", - "K", - "?", - "l", - "S", - "I", - "=", - "D", - ">", - "#", - "k", - "|", - "&", - "*", - "i", - "a", - "%", - "s", - "h", - "e", - "y", + "'", + "o", "q", - "f", - "N", - "t", - "\\", + "|", ",", - "!", - "b", - "H", - "r" + ">", + "[", + "}", + "k", + "Y", + "c" ], "parseMumber": [ - 15661, - 38867, - 89744, - 27745, - 99806, - 96252, - 33258, - 46283, - 67666, - 68712, - 7708, - 40228, - 85279, - 47290, - 65855, - 76314, - 98442, - 62180, - 52352, - 14594, - 41072, - 50835, - 7640, - 52547, - 45283, - 20439, - 22642, - 72938, - 2941, - 93058, - 29321, - 74093, - 57572, - 74959, - 66598, - 35485, - 49690, - 40473, - 96174, - 34686, - 61054, - 85971, - 11874, - 97108, - 47196, - 60398, - 75870, - 42160, - 34139, - 74554, - 13098, - 34312, - 24969, - 48172, - 83061, - 76730, - 39147, - 78815, - 69783, - 87499, - 99968, - 74834, - 39335, - 7925, - 80167, - 57349, - 5378, - 88688, - 57675, - 49721, - 9050, - 44070, - 18336, - 91174, - 45096, - 98059, - 97130, - 10014, - 98118, - 80604, - 37254, - 15824, - 6340, - 4514, + 83829, + 98538, + 43955, + 70659, + 49574, + 87527, + 39113, + 20104, + 25174, + 28221, + 50047, + 46129, + 53860, + 21615, + 4574, + 98868, + 45088, + 95221, + 27952, + 33522, + 48463, + 39633, + 43893, + 52473, + 6039, + 75201, + 49185, + 94330, + 81443, + 67449, + 87554, + 38084, + 9385, + 57094, + 10056, + 42452, + 7059, + 44285, + 63660, + 78250, + 45891, + 9356, + 57012, + 50969, + 75002, + 93929, + 53791, + 252, + 76532, + 91713, + 30475, + 57365, + 98108, + 47772, + 7038, + 36232, + 71688, + 64117, + 44865, + 58567, + 28251, + 88621, + 25669, + 33250, + 27380, + 3520, + 53018, + 90189, + 28526, + 92870, + 36546, + 65796, + 68526, + 13050, + 36554, + 26642, + 20858, + 65379, + 52841, + 61852, + 25019, + 90559, + 19371, + 41593, + 73056, + 14953, + 6063, + 97098, + 2907, + 45332, + 80802, + 90124, + 40095, + 58565, + 33229, + 61423, + 43654, + 42493, + 19136, + 26202, + 24420, + 12250, + 2860, + 29891, + 66457, + 7810, + 35999, + 20287, + 28556, + 56632, + 50486, + 13949, + 85916, + 74374, + 43362, + 62328, + 63069, + 15314, + 43452, + 78724, + 30813, + 12727, + 17045, + 23139, + 36782, + 81822, + 41845, + 3727, + 37144, + 99901, + 81249, + 71447, + 25710, + 2273, + 93729, + 6024, + 58474, + 78617, + 82426, + 48028, + 64990, + 16180, + 80929, + 5245, + 17735, + 48297, + 26144, + 88427, + 15054, + 34112, + 38928, + 46897, + 40058, + 98576, + 74194, + 38892, + 75889, + 37779, + 91170, + 35895, + 48988, + 66459, + 48126, + 42100, + 86840, + 22563, + 75310, + 77297, + 60755, + 2938, + 70381, + 74803, + 24731, + 73891, + 30583, + 23801, + 65370, + 15444, + 53289, + 91865, + 89320, + 66192, + 81950, + 51578, + 99539, + 31350, + 96627, + 91653, + 32036, + 79464, + 16931, + 52258, + 25565, + 13969, + 87970, + 23839, + 76843, + 4040, + 59782, + 20623, + 32224, + 82527, + 92499, + 92745, + 6711, + 56497, + 74819, + 54853, + 18048, + 75147, + 47982, + 50225, + 16930, + 71614, + 11333, + 90943, + 60389, + 32072, + 37556, + 70650, + 477, + 4318, + 71936, + 58287, + 85772, + 27702, + 92504, + 6351, + 46162, + 58833, + 27261, + 41846, + 71913, + 49842, + 90437, + 18674, + 95239, + 32262, + 32297, + 90836, + 17705, + 70376, + 14388, + 63641, + 24046, + 56461, + 20223, + 6899, + 80495, + 19966, + 38678, + 12722, + 84571, + 69386, + 7387, + 8947, + 22266, + 35253, + 52026, + 51318, + 97404, + 90739, + 24981, + 80410, + 82560, + 46484, + 79748, + 57401, + 72620, + 17205, + 4601, + 11991, + 74422, + 63611, + 92577, + 39201, + 17331, + 82435, + 21736, + 57985, + 94713, + 7542, + 72470, + 30780, + 95165, + 23406, + 22855, + 88085, + 1677, + 66109, + 75207, + 33493, + 96917, + 11004, + 76966, + 6002, + 74799, + 34235, + 72097, + 61956, + 81381, + 53015, + 46435, + 72808, + 49607, + 80314, + 5476, + 85468, + 13234, + 47497, + 32430, + 11982, + 37988, + 76379, + 75007, + 1856, + 8577, + 84135, + 35376, + 50601, + 73879, + 37005, + 35454, + 61762, + 55981, + 87034, + 13257, + 82036, + 38872, + 54587, + 59078, + 72626, + 50731, + 34007, + 2902, + 45720, + 1307, + 60431, + 23593, + 31923, + 52309, + 37125, + 25314, + 50318, + 86171, + 84981, + 72364, + 21138, + 5270, + 71289, + 56211, + 12718, 347, - 16797, - 77831, - 32527, - 29621, - 75029, - 63539, - 96567, - 90395, - 1186, - 22631, - 69174, - 18191, - 81079, - 33031, - 77072, - 28199, - 33088, - 86123, - 6614, - 35135, - 64160, - 94909, - 56227, - 24031, - 64991, - 26335, - 12478, - 56462, - 16962, - 85822, - 52950, - 4282, - 66255, - 21876, - 20394, - 41781, - 50932, - 78289, - 54560, - 19651, - 6461, - 74209, - 35202, - 15036, - 65830, - 66656, - 76637, - 45178, - 25967, - 91359, - 73222, - 64485, - 68125, - 26390, - 65454, - 52614, - 15878, - 7426, - 12996, - 2171, - 43837, - 69274, - 22707, - 16153, - 43286, - 55622, - 77402, - 32928, - 57240, - 50926, - 62464, - 23605, - 58407, - 11328, - 90586, - 93712, - 85018, - 25533, - 21312, - 69442, - 26779, - 42012, - 12421, - 68798, - 10073, - 1029, - 95887, - 24340, - 85375, - 81336, - 54621, - 6335, - 55948, - 10623, - 73737, - 89837, - 79347, - 90075, - 37362, - 35632, - 28210, - 89314, - 52842, - 38785, - 4197, - 34626, - 63366, - 32855, - 20097, - 15009, - 13341, - 39161, - 50946, - 52449, - 15049, - 68575, - 86818, - 26166, - 96369, - 51709, - 84982, - 19954, - 22187, - 27972, - 19981, - 2819, - 55477, - 12418, - 48190, - 95502, - 16904, - 40345, - 62187, - 92015, - 35916, - 61272, - 94561, - 53759, - 41469, - 94875, - 4718, - 58205, - 85847, - 52134, - 45800, - 78167, - 68017, - 51992, - 89547, - 37793, - 22999, - 56682, - 83545, - 32326, - 78493, - 55236, - 23939, - 74031, - 55935, - 9498, - 5909, - 47459, - 27934, - 7579, - 66297, - 67683, - 5225, - 97952, - 35409, - 56156, - 85073, - 95899, - 29426, - 54394, - 17620, - 91856, - 3618, - 42760, - 36003, - 13485, - 19368, - 67044, - 35700, - 41619, - 65143, - 26036, - 44505, - 57240, - 35798, - 15161, - 12940, - 91849, - 19463, - 95297, - 32758, - 66220, - 45942, - 72266, - 97159, - 46190, - 5247, - 14255, - 1626, - 80167, - 61777, - 25759, - 85662, - 66707, - 48819, - 67178, - 27181, - 48898, - 64060, - 63447, - 87082, - 48873, - 16128, - 14455, - 71455, - 46788, - 47025, - 63306, - 72130, - 76765, - 44803, - 97099, - 1278, - 22483, - 98357, - 89615, - 74671, - 72050, - 65597, - 69648, - 74331, - 16093, - 48088, - 19482, - 25795, - 94817, - 83455, - 87177, - 64476, - 43980, - 54611, - 57445, - 46400, - 71705, - 50587, - 27690, - 89040, - 51838, - 81505, - 81975, - 64645, - 11652, - 76064, - 92214, - 93244, - 37836, - 36819, - 23425, - 47881, - 54477, - 11861, - 66974, - 51669, - 24911, - 88305, - 65199, - 4342, - 52038 + 20994, + 32399, + 42294, + 93, + 98343, + 32182, + 94365, + 7080, + 82017, + 9988, + 84941, + 62733, + 10214, + 11028, + 85300, + 5591, + 96003, + 78785, + 50079, + 16077, + 6425, + 50669, + 89795, + 55175, + 82064, + 17905, + 61320, + 38551, + 15930, + 83117, + 56858, + 39382, + 69488, + 57068, + 83255, + 24495, + 37146, + 79259, + 3403, + 17000, + 43850, + 52526, + 56448, + 7188, + 85598, + 73547, + 59826, + 39499, + 21093, + 45426, + 65244, + 7848, + 62951, + 16514, + 31439, + 92477, + 79574, + 56926, + 34114, + 71849, + 21630, + 39804, + 1120, + 6688, + 94241, + 92716, + 37741, + 77292, + 47075, + 37085, + 21803, + 91184, + 13173, + 56014, + 97712, + 59986, + 64784, + 43797, + 77639, + 1802, + 59069, + 77591, + 13220, + 78450, + 17897, + 77751, + 9866, + 31908, + 91074, + 32409, + 79652, + 2681, + 66586, + 92306, + 50402, + 17298, + 54993, + 15144, + 11888, + 28585, + 6219, + 61058, + 31066, + 33492, + 54121, + 88684, + 26591, + 45489, + 11399, + 97736, + 62181, + 4043, + 410, + 56571, + 76079, + 82584, + 8807, + 7999, + 23345, + 3222, + 63578, + 23018, + 58673, + 96508, + 77287, + 22007, + 40820, + 99319, + 55749, + 60711, + 26981, + 49633, + 67744, + 94913, + 25625, + 79523, + 68759, + 8239, + 97981, + 11231, + 51456, + 96621, + 1556, + 54781, + 55682, + 66237, + 56554, + 42380, + 73041, + 52689, + 87143, + 70097, + 74719, + 61232, + 92573, + 46963, + 71165, + 80874, + 66308, + 22714, + 55132, + 98520, + 99582, + 75752, + 86468, + 48180, + 59043, + 25732, + 89484, + 67202, + 88434, + 60323, + 31201, + 22375, + 68004, + 17033, + 90763, + 83695, + 57995, + 80909, + 85012, + 10155, + 83271, + 85105, + 89071, + 44894, + 20984, + 92865, + 94522, + 98578, + 85524, + 81050, + 82458, + 5517, + 37779, + 58636, + 3407, + 30942, + 40892, + 34238, + 33868, + 38760, + 10991, + 1259, + 48424, + 62861, + 17582, + 71918, + 40456, + 6883, + 8393, + 31406, + 10181, + 58812, + 77008, + 61260, + 45414, + 83722, + 35828, + 65864, + 73241, + 51316, + 36920, + 93335, + 96535, + 41715, + 79990, + 90275, + 77140, + 63893, + 59147, + 90654, + 96010, + 43302, + 22465, + 65267, + 41721, + 74355, + 8131, + 40148, + 99637, + 5307, + 98832, + 92077, + 29936, + 74541, + 71404, + 56578, + 80160, + 12862, + 62788, + 18345, + 2212, + 87692, + 68926, + 58846, + 18195, + 33692, + 91902, + 70264, + 56332, + 29956, + 58855, + 59286, + 22482, + 3053, + 18321, + 47917, + 92819, + 46643 ] } \ No newline at end of file diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 4b3566ea..98b52cb1 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -8,7 +8,6 @@ import validate from '../service/validate/validate.js'; export default class Controller { constructor() { this.extraction = new Extraction(); - this.parser = new Parser(); } async run() { @@ -23,17 +22,13 @@ export default class Controller { validate.isRegexValidError(customRegexs); validate.isRegexContinueError(input, customRegexs); + const parser = new Parser(customRegexs); + console.log(customRegexs); - const parsingCustomText = this.parser.parseCustomRegex( - customRegexs, - input, - ); + const parsingCustomText = parser.parseCustomRegex(input); console.log(parsingCustomText); - const parsedNumber = this.parser.parseData( - customRegexs, - parsingCustomText, - ); + const parsedNumber = parser.parseData(parsingCustomText); console.log(parsedNumber); validate.isNumber(parsedNumber); const numbers = new Number(parsedNumber); diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index 366e408c..b771c42a 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -2,15 +2,7 @@ const regex = /\/\/(.*?)\\n/g; export default class Extraction { extractCustom(inputText) { - const matches = Array.from( - inputText.matchAll(regex), - (match) => match[1], - ).map((item) => this.#escapeForCharClass(item)); + const matches = Array.from(inputText.matchAll(regex), (match) => match[1]); return matches; } - - // 정규 문자열이 포함되는 경우 대비 - #escapeForCharClass(char) { - return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index e583a929..331873bb 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,6 +1,13 @@ export default class Parser { - parseData(regexs, inputText) { - const totalRegexs = [...regexs, ',', ':']; + constructor(customRegexs) { + this.customRegexs = customRegexs; + this.customEsacpedRegexs = customRegexs.map((item) => + this.#escapeForCharClass(item), + ); + } + + parseData(inputText) { + const totalRegexs = [...this.customEsacpedRegexs, ',', ':']; const regex = new RegExp(totalRegexs.join('|')); return inputText .split(regex) @@ -8,10 +15,10 @@ export default class Parser { .map((item) => Number(item)); } - parseCustomRegex(regexs, inputText) { + parseCustomRegex(inputText) { let inputResult = inputText; - regexs.forEach((regex) => { + this.customRegexs.forEach((regex) => { const splitType = `//${regex}\\n`; if (inputResult.startsWith(splitType)) { inputResult = inputResult.slice(splitType.length); @@ -20,4 +27,9 @@ export default class Parser { return inputResult; } + + // 정규 문자열이 포함되는 경우 대비 + #escapeForCharClass(char) { + return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + } } From 91dbfac8ce463174a55610aea53f1c7e3acf90d2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 21:24:55 +0900 Subject: [PATCH 56/65] =?UTF-8?q?refactor:=20extractor=EC=9D=98=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B0=92=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 1022 ++++++++++++------------------ src/controller/Controller.js | 18 +- src/service/model/Extraction.js | 9 +- src/service/model/Parser.js | 20 +- src/service/validate/validate.js | 4 +- test/RandomMaker.test.js | 55 +- 6 files changed, 439 insertions(+), 689 deletions(-) diff --git a/error_cases/last_error.json b/error_cases/last_error.json index bc885c76..ddc7f0d5 100644 --- a/error_cases/last_error.json +++ b/error_cases/last_error.json @@ -1,642 +1,400 @@ { - "input": "//'\\n//o\\n//q\\n//|\\n//,\\n//>\\n//[\\n//}\\n//k\\n//Y\\n//c\\n83829Y98538>43955[70659|49574|87527,39113Y20104}25174|28221[50047c46129'53860|21615c4574|98868Y45088,95221'27952[33522o48463c39633,43893k52473:6039>75201,49185|94330>81443o67449o87554,38084>9385:57094o10056[42452[7059'44285,63660c78250q45891o9356q57012q50969}75002:93929:53791q252>76532Y91713>30475>57365o98108'47772:7038>36232,71688Y64117[44865c58567,28251>88621'25669q33250[27380o3520>53018o90189k28526c92870k36546o65796c68526|13050k36554|26642>20858,65379[52841|61852'25019[90559c19371:41593|73056>14953>6063q97098,2907[45332}80802,90124:40095}58565k33229|61423'43654'42493}19136Y26202|24420Y12250k2860q29891:66457k7810,35999,20287}28556c56632q50486Y13949>85916[74374:43362q62328Y63069>15314:43452|78724q30813Y12727c17045Y23139k36782o81822[41845[3727,37144[99901[81249>71447:25710,2273c93729>6024|58474,78617,82426o48028,64990k16180q80929k5245Y17735c48297k26144Y88427,15054c34112,38928c46897Y40058|98576k74194,38892>75889c37779[91170Y35895,48988>66459:48126q42100k86840'22563:75310[77297}60755>2938c70381:74803q24731Y73891q30583|23801}65370:15444Y53289k91865Y89320c66192q81950'51578}99539[31350}96627Y91653:32036c79464,16931'52258}25565c13969[87970:23839}76843c4040k59782,20623c32224q82527c92499q92745o6711,56497'74819c54853[18048o75147c47982o50225'16930o71614,11333|90943Y60389,32072>37556:70650>477Y4318q71936}58287,85772[27702|92504,6351c46162Y58833c27261>41846Y71913Y49842}90437:18674c95239'32262>32297c90836k17705q70376}14388o63641,24046,56461,20223k6899[80495k19966}38678'12722o84571:69386o7387,8947>22266:35253'52026}51318,97404k90739:24981|80410,82560|46484,79748c57401>72620'17205:4601k11991Y74422q63611k92577,39201|17331'82435o21736k57985c94713k7542,72470q30780}95165|23406q22855Y88085o1677,66109}75207'33493k96917,11004Y76966Y6002,74799c34235k72097o61956[81381k53015Y46435>72808>49607>80314:5476[85468q13234c47497,32430Y11982,37988>76379>75007k1856c8577,84135>35376,50601}73879c37005c35454o61762c55981o87034>13257Y82036}38872o54587Y59078q72626[50731[34007'2902o45720Y1307,60431[23593k31923|52309,37125o25314|50318,86171k84981:72364>21138c5270,71289c56211Y12718}347:20994c32399c42294c93o98343c32182|94365q7080|82017o9988:84941>62733q10214o11028>85300q5591|96003>78785c50079c16077q6425|50669[89795c55175c82064,17905}61320Y38551c15930Y83117c56858,39382Y69488Y57068[83255:24495>37146[79259|3403[17000[43850,52526'56448,7188>85598c73547[59826k39499c21093>45426Y65244,7848[62951|16514>31439'92477c79574c56926|34114'71849o21630}39804q1120}6688>94241c92716k37741>77292k47075'37085>21803}91184c13173>56014Y97712c59986,64784[43797,77639'1802Y59069c77591:13220[78450,17897'77751,9866,31908,91074'32409[79652o2681[66586k92306:50402q17298[54993Y15144'11888>28585:6219,61058[31066o33492o54121,88684>26591:45489o11399Y97736|62181|4043'410k56571}76079q82584}8807,7999Y23345:3222:63578q23018|58673Y96508'77287>22007>40820c99319,55749Y60711c26981,49633:67744q94913>25625>79523}68759:8239>97981>11231>51456,96621>1556'54781q55682[66237[56554[42380c73041,52689>87143[70097,74719>61232q92573q46963q71165>80874o66308'22714,55132,98520[99582'75752'86468k48180'59043>25732c89484}67202,88434[60323[31201o22375|68004Y17033}90763:83695q57995,80909'85012Y10155}83271:85105,89071q44894Y20984[92865'94522c98578}85524,81050|82458c5517|37779[58636|3407}30942[40892:34238,33868q38760Y10991k1259[48424|62861,17582:71918:40456,6883}8393,31406>10181:58812Y77008[61260'45414>83722}35828q65864o73241}51316,36920'93335}96535k41715[79990o90275,77140:63893'59147q90654Y96010k43302|22465,65267}41721Y74355,8131'40148[99637q5307'98832|92077q29936k74541|71404'56578c80160q12862:62788'18345,2212[87692Y68926q58846Y18195Y33692'91902k70264}56332o29956c58855,59286'22482c3053c18321q47917[92819>46643", - "output": 31262470, + "input": "//t\\n//n\\n//C\\n//\"\\n//K\\n//q\\n//L\\n///\\n//N\\n//g\\n//o\\n70282n88694\"73231g86588C57196n5651n27155t34673,35763,81836n77011C88823,8266t6838n2127N54395o22954:40274L17414,57977C99661/52607K55579,86119/49513n50076C20086C68652/47521t26586,93051C3553C99647o86512o63008L79293:63709,58323C23606g67859:1822\"63402K23216o30151\"38775g89323K67461L66252K73291N42808o4715K66659K87095t33744o82453/12792t69135K71687\"69857K95424g51972o79604\"35993N51221q39546:98679K29891g68766g50539t64037L97678L33733,64347K71679/4595/46831,65372/14485\"36887\"49393o28803C79322K31376,34671\"14388L53622/73578K87217q47026o72282g37515,39619q87848K32545K93417:75060o17211N6493/24250n92466n6317:61490N36568o71859L72546/32812,34778g99158K54096L8817g40289/21461\"43564o46653:71609q52191N55694q21555o47102o27245K6430t26154g15873n2923n16743/89993K58275o14739\"24152o62389N3559,11736o2127o56537,96334\"42958n37729q66791\"55582\"97971N11676N81604/49001,72257g21630g69847K17842o59572\"36800,60078o51686N61809t14667\"2493n17025t37522t80499:65393,88716g46689L6191,83905n50756\"97329o49648:6059,85380\"58916q95243L2144C16664C83656C25114C75272n56142g37862t2237C98609C48205o87455K56402K16757L57392:3987g13400\"44410t97302K56079:42565\"70816g77611C30563\"25495N85747q57579L1224C55880q94003K87294:54470\"71335L62592N28010g16354:8329:85358t30025L44365K22852\"93031:63318/39107C80163n67826q10391C14852g37880K53309q22302N84895t403o43770n88643K3138\"47341/64831/81442L93356N14728t59183K1946q56861C43705/29554o3018N58133N43115C11136q63400K58633:25816,10901g94844n93881g98790K4329\"44890n19439,45965o38798g24656/41694N46965C56949,88886t41901K82119:1212n82723o46780L92238/10939C41909g47171N3523C41081o50795N15293N5849,72823C44880o49369K25987o98221/36623t37281,5042\"19048C79624K83437N78237K23533o87971q9961g27562:75175n24948K13806L6582N71846t74622L22980\"50348g88296,26857t79257g11600:70094o44893o7232\"57427:59540C60167L14784q82597q84100:46398o57608:23135g90291,83665K48972n70802L82146t79681o19154,37684n28684C85383n98589C43226q17583C75992g4084L92212o17420n73329q93070L24968N31137L18968\"94382n97665q87498g44785o4665/45738o37833g83709:8100C31169:88812,33797\"87654\"59084L92357C16934L91817n63236n80118\"28662K16664t86775n88517/31377o75611,59723\"91176\"7818q80105,29935N26362:29458C73748\"71665K90876g92682t44865t78435t45n21147\"11468N84629/87506o89676N4311n36710,98259t38258,38784K36732", + "output": 19098725, "extracionRegex": [ - "'", - "o", + "t", + "n", + "C", + "\"", + "K", "q", - "|", - ",", - ">", - "[", - "}", - "k", - "Y", - "c" + "L", + "/", + "N", + "g", + "o" ], "parseMumber": [ - 83829, - 98538, - 43955, - 70659, - 49574, - 87527, - 39113, - 20104, - 25174, - 28221, - 50047, - 46129, - 53860, - 21615, - 4574, - 98868, - 45088, - 95221, - 27952, - 33522, - 48463, - 39633, - 43893, - 52473, - 6039, - 75201, - 49185, - 94330, - 81443, - 67449, - 87554, - 38084, - 9385, - 57094, - 10056, - 42452, - 7059, - 44285, - 63660, - 78250, - 45891, - 9356, - 57012, - 50969, - 75002, - 93929, - 53791, - 252, - 76532, - 91713, - 30475, - 57365, - 98108, - 47772, - 7038, - 36232, - 71688, - 64117, - 44865, - 58567, - 28251, - 88621, - 25669, - 33250, - 27380, - 3520, - 53018, - 90189, - 28526, - 92870, - 36546, - 65796, - 68526, - 13050, - 36554, - 26642, - 20858, - 65379, - 52841, - 61852, - 25019, - 90559, - 19371, - 41593, - 73056, - 14953, - 6063, - 97098, - 2907, - 45332, - 80802, - 90124, - 40095, - 58565, - 33229, - 61423, - 43654, - 42493, - 19136, - 26202, - 24420, - 12250, - 2860, + 70282, + 88694, + 73231, + 86588, + 57196, + 5651, + 27155, + 34673, + 35763, + 81836, + 77011, + 88823, + 8266, + 6838, + 2127, + 54395, + 22954, + 40274, + 17414, + 57977, + 99661, + 52607, + 55579, + 86119, + 49513, + 50076, + 20086, + 68652, + 47521, + 26586, + 93051, + 3553, + 99647, + 86512, + 63008, + 79293, + 63709, + 58323, + 23606, + 67859, + 1822, + 63402, + 23216, + 30151, + 38775, + 89323, + 67461, + 66252, + 73291, + 42808, + 4715, + 66659, + 87095, + 33744, + 82453, + 12792, + 69135, + 71687, + 69857, + 95424, + 51972, + 79604, + 35993, + 51221, + 39546, + 98679, 29891, - 66457, - 7810, - 35999, - 20287, - 28556, - 56632, - 50486, - 13949, - 85916, - 74374, - 43362, - 62328, - 63069, - 15314, - 43452, - 78724, - 30813, - 12727, - 17045, - 23139, - 36782, - 81822, - 41845, - 3727, - 37144, - 99901, - 81249, - 71447, - 25710, - 2273, - 93729, - 6024, - 58474, - 78617, - 82426, - 48028, - 64990, - 16180, - 80929, - 5245, - 17735, - 48297, - 26144, - 88427, - 15054, - 34112, - 38928, - 46897, - 40058, - 98576, - 74194, - 38892, - 75889, - 37779, - 91170, - 35895, - 48988, - 66459, - 48126, - 42100, - 86840, - 22563, - 75310, - 77297, - 60755, - 2938, - 70381, - 74803, - 24731, - 73891, - 30583, - 23801, - 65370, - 15444, - 53289, - 91865, - 89320, - 66192, - 81950, - 51578, - 99539, - 31350, - 96627, - 91653, - 32036, - 79464, - 16931, - 52258, - 25565, - 13969, - 87970, - 23839, - 76843, - 4040, - 59782, - 20623, - 32224, - 82527, - 92499, - 92745, - 6711, - 56497, - 74819, - 54853, - 18048, - 75147, - 47982, - 50225, - 16930, - 71614, - 11333, - 90943, - 60389, - 32072, - 37556, - 70650, - 477, - 4318, - 71936, - 58287, - 85772, - 27702, - 92504, - 6351, - 46162, - 58833, - 27261, - 41846, - 71913, - 49842, - 90437, - 18674, - 95239, - 32262, - 32297, - 90836, - 17705, - 70376, + 68766, + 50539, + 64037, + 97678, + 33733, + 64347, + 71679, + 4595, + 46831, + 65372, + 14485, + 36887, + 49393, + 28803, + 79322, + 31376, + 34671, 14388, - 63641, - 24046, - 56461, - 20223, - 6899, - 80495, - 19966, - 38678, - 12722, - 84571, - 69386, - 7387, - 8947, - 22266, - 35253, - 52026, - 51318, - 97404, - 90739, - 24981, - 80410, - 82560, - 46484, - 79748, - 57401, - 72620, - 17205, - 4601, - 11991, - 74422, - 63611, - 92577, - 39201, - 17331, - 82435, - 21736, - 57985, - 94713, - 7542, - 72470, - 30780, - 95165, - 23406, - 22855, - 88085, - 1677, - 66109, - 75207, - 33493, - 96917, - 11004, - 76966, - 6002, - 74799, - 34235, - 72097, - 61956, - 81381, - 53015, - 46435, - 72808, - 49607, - 80314, - 5476, - 85468, - 13234, - 47497, - 32430, - 11982, - 37988, - 76379, - 75007, - 1856, - 8577, - 84135, - 35376, - 50601, - 73879, - 37005, - 35454, - 61762, - 55981, - 87034, - 13257, - 82036, - 38872, - 54587, - 59078, - 72626, - 50731, - 34007, - 2902, - 45720, - 1307, - 60431, - 23593, - 31923, - 52309, - 37125, - 25314, - 50318, - 86171, - 84981, - 72364, - 21138, - 5270, - 71289, - 56211, - 12718, - 347, - 20994, - 32399, - 42294, - 93, - 98343, - 32182, - 94365, - 7080, - 82017, - 9988, - 84941, - 62733, - 10214, - 11028, - 85300, - 5591, - 96003, - 78785, - 50079, - 16077, - 6425, - 50669, - 89795, - 55175, - 82064, - 17905, - 61320, - 38551, - 15930, - 83117, - 56858, - 39382, - 69488, - 57068, - 83255, - 24495, - 37146, - 79259, - 3403, - 17000, - 43850, - 52526, - 56448, - 7188, - 85598, - 73547, - 59826, - 39499, - 21093, - 45426, - 65244, - 7848, - 62951, - 16514, - 31439, - 92477, - 79574, - 56926, - 34114, - 71849, + 53622, + 73578, + 87217, + 47026, + 72282, + 37515, + 39619, + 87848, + 32545, + 93417, + 75060, + 17211, + 6493, + 24250, + 92466, + 6317, + 61490, + 36568, + 71859, + 72546, + 32812, + 34778, + 99158, + 54096, + 8817, + 40289, + 21461, + 43564, + 46653, + 71609, + 52191, + 55694, + 21555, + 47102, + 27245, + 6430, + 26154, + 15873, + 2923, + 16743, + 89993, + 58275, + 14739, + 24152, + 62389, + 3559, + 11736, + 2127, + 56537, + 96334, + 42958, + 37729, + 66791, + 55582, + 97971, + 11676, + 81604, + 49001, + 72257, 21630, - 39804, - 1120, - 6688, - 94241, - 92716, - 37741, - 77292, - 47075, - 37085, - 21803, - 91184, - 13173, - 56014, - 97712, - 59986, - 64784, - 43797, - 77639, - 1802, - 59069, - 77591, - 13220, - 78450, - 17897, - 77751, - 9866, - 31908, - 91074, - 32409, - 79652, - 2681, - 66586, - 92306, - 50402, - 17298, - 54993, - 15144, - 11888, - 28585, - 6219, - 61058, - 31066, - 33492, - 54121, - 88684, - 26591, - 45489, - 11399, - 97736, - 62181, - 4043, - 410, - 56571, - 76079, - 82584, - 8807, - 7999, - 23345, - 3222, - 63578, - 23018, - 58673, - 96508, - 77287, - 22007, - 40820, - 99319, - 55749, - 60711, - 26981, - 49633, - 67744, - 94913, - 25625, - 79523, - 68759, - 8239, - 97981, - 11231, - 51456, - 96621, - 1556, - 54781, - 55682, - 66237, - 56554, - 42380, - 73041, - 52689, - 87143, - 70097, - 74719, - 61232, - 92573, - 46963, - 71165, - 80874, - 66308, - 22714, - 55132, - 98520, - 99582, - 75752, - 86468, - 48180, - 59043, - 25732, - 89484, - 67202, - 88434, - 60323, - 31201, - 22375, - 68004, - 17033, - 90763, - 83695, - 57995, - 80909, - 85012, - 10155, - 83271, - 85105, - 89071, - 44894, - 20984, - 92865, - 94522, - 98578, - 85524, - 81050, - 82458, - 5517, - 37779, - 58636, - 3407, - 30942, - 40892, - 34238, - 33868, - 38760, - 10991, - 1259, - 48424, - 62861, - 17582, - 71918, - 40456, - 6883, - 8393, - 31406, - 10181, - 58812, - 77008, - 61260, - 45414, - 83722, - 35828, - 65864, - 73241, - 51316, - 36920, - 93335, - 96535, - 41715, - 79990, - 90275, - 77140, - 63893, - 59147, - 90654, - 96010, - 43302, - 22465, - 65267, - 41721, - 74355, - 8131, - 40148, - 99637, - 5307, - 98832, - 92077, - 29936, - 74541, - 71404, - 56578, - 80160, - 12862, - 62788, - 18345, - 2212, - 87692, - 68926, - 58846, - 18195, - 33692, - 91902, - 70264, - 56332, - 29956, - 58855, - 59286, - 22482, - 3053, - 18321, - 47917, - 92819, - 46643 + 69847, + 17842, + 59572, + 36800, + 60078, + 51686, + 61809, + 14667, + 2493, + 17025, + 37522, + 80499, + 65393, + 88716, + 46689, + 6191, + 83905, + 50756, + 97329, + 49648, + 6059, + 85380, + 58916, + 95243, + 2144, + 16664, + 83656, + 25114, + 75272, + 56142, + 37862, + 2237, + 98609, + 48205, + 87455, + 56402, + 16757, + 57392, + 3987, + 13400, + 44410, + 97302, + 56079, + 42565, + 70816, + 77611, + 30563, + 25495, + 85747, + 57579, + 1224, + 55880, + 94003, + 87294, + 54470, + 71335, + 62592, + 28010, + 16354, + 8329, + 85358, + 30025, + 44365, + 22852, + 93031, + 63318, + 39107, + 80163, + 67826, + 10391, + 14852, + 37880, + 53309, + 22302, + 84895, + 403, + 43770, + 88643, + 3138, + 47341, + 64831, + 81442, + 93356, + 14728, + 59183, + 1946, + 56861, + 43705, + 29554, + 3018, + 58133, + 43115, + 11136, + 63400, + 58633, + 25816, + 10901, + 94844, + 93881, + 98790, + 4329, + 44890, + 19439, + 45965, + 38798, + 24656, + 41694, + 46965, + 56949, + 88886, + 41901, + 82119, + 1212, + 82723, + 46780, + 92238, + 10939, + 41909, + 47171, + 3523, + 41081, + 50795, + 15293, + 5849, + 72823, + 44880, + 49369, + 25987, + 98221, + 36623, + 37281, + 5042, + 19048, + 79624, + 83437, + 78237, + 23533, + 87971, + 9961, + 27562, + 75175, + 24948, + 13806, + 6582, + 71846, + 74622, + 22980, + 50348, + 88296, + 26857, + 79257, + 11600, + 70094, + 44893, + 7232, + 57427, + 59540, + 60167, + 14784, + 82597, + 84100, + 46398, + 57608, + 23135, + 90291, + 83665, + 48972, + 70802, + 82146, + 79681, + 19154, + 37684, + 28684, + 85383, + 98589, + 43226, + 17583, + 75992, + 4084, + 92212, + 17420, + 73329, + 93070, + 24968, + 31137, + 18968, + 94382, + 97665, + 87498, + 44785, + 4665, + 45738, + 37833, + 83709, + 8100, + 31169, + 88812, + 33797, + 87654, + 59084, + 92357, + 16934, + 91817, + 63236, + 80118, + 28662, + 16664, + 86775, + 88517, + 31377, + 75611, + 59723, + 91176, + 7818, + 80105, + 29935, + 26362, + 29458, + 73748, + 71665, + 90876, + 92682, + 44865, + 78435, + 45, + 21147, + 11468, + 84629, + 87506, + 89676, + 4311, + 36710, + 98259, + 38258, + 38784, + 36732 ] } \ No newline at end of file diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 98b52cb1..68b04b71 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -8,6 +8,7 @@ import validate from '../service/validate/validate.js'; export default class Controller { constructor() { this.extraction = new Extraction(); + this.parser = new Parser(); } async run() { @@ -17,18 +18,19 @@ export default class Controller { // validate.isLong(input); - const customRegexs = this.extraction.extractCustom(input); + const { regexs, esacpedRegexs } = this.extraction.extractCustom(input); - validate.isRegexValidError(customRegexs); - validate.isRegexContinueError(input, customRegexs); + validate.isRegexValidError(regexs); + validate.isRegexContinueError(input, esacpedRegexs); - const parser = new Parser(customRegexs); - - console.log(customRegexs); - const parsingCustomText = parser.parseCustomRegex(input); + console.log(regexs); + const parsingCustomText = this.parser.parseCustomRegex(input, regexs); console.log(parsingCustomText); - const parsedNumber = parser.parseData(parsingCustomText); + const parsedNumber = this.parser.parseData( + parsingCustomText, + esacpedRegexs, + ); console.log(parsedNumber); validate.isNumber(parsedNumber); const numbers = new Number(parsedNumber); diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index b771c42a..37906545 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -3,6 +3,13 @@ const regex = /\/\/(.*?)\\n/g; export default class Extraction { extractCustom(inputText) { const matches = Array.from(inputText.matchAll(regex), (match) => match[1]); - return matches; + return { + regexs: matches, // 원본 + esacpedRegexs: this.#escapeRegex(matches), // 이스케이프 (사용용) + }; + } + + #escapeRegex(regexs) { + return regexs.map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 331873bb..6ee94f12 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,13 +1,6 @@ export default class Parser { - constructor(customRegexs) { - this.customRegexs = customRegexs; - this.customEsacpedRegexs = customRegexs.map((item) => - this.#escapeForCharClass(item), - ); - } - - parseData(inputText) { - const totalRegexs = [...this.customEsacpedRegexs, ',', ':']; + parseData(inputText, esacpedRegexs) { + const totalRegexs = [...esacpedRegexs, ',', ':']; const regex = new RegExp(totalRegexs.join('|')); return inputText .split(regex) @@ -15,10 +8,10 @@ export default class Parser { .map((item) => Number(item)); } - parseCustomRegex(inputText) { + parseCustomRegex(inputText, regexs) { let inputResult = inputText; - this.customRegexs.forEach((regex) => { + regexs.forEach((regex) => { const splitType = `//${regex}\\n`; if (inputResult.startsWith(splitType)) { inputResult = inputResult.slice(splitType.length); @@ -27,9 +20,4 @@ export default class Parser { return inputResult; } - - // 정규 문자열이 포함되는 경우 대비 - #escapeForCharClass(char) { - return char.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - } } diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index befebb1a..bf0223a0 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -11,9 +11,9 @@ const validate = { if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); }); }, - isRegexContinueError(inputText, customRegexs) { + isRegexContinueError(inputText, esacpedRegexs) { // 각 구분자마다 연속 검사 - [',', ':', ...customRegexs].forEach((delim) => { + [',', ':', ...esacpedRegexs].forEach((delim) => { const regex = new RegExp(`${delim}{2,}`); if (regex.test(inputText)) throw new Error('[ERROR] 연속된 구분자가 존재합니다'); diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 2c3de91d..28320b6d 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -121,34 +121,29 @@ const getRandomInput = () => { return [inputResult, outputResult, parseMumber, usedCustomRegex]; }; -describe('랜덤문자열 생성기', () => { - it('커스텀 구분자를 통해 테스트 합니다', async () => { - const [input, output, parseMumber, extracionRegex] = getRandomInput(); - - inputView.readLineMessage.mockImplementationOnce(() => input); - - const controller = new Controller(); - const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); - const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); - const extractionValueSpy = jest.spyOn( - Extraction.prototype, - 'extractCustom', - ); - - try { - await controller.run(); - - const extractionSpyValue = extractionValueSpy.mock.results[0].value; - const parsedSpyValue = parsedValueSpy.mock.results[0].value; - - compareArrays(parseMumber, parsedSpyValue); // 디버깅을 위한 콘솔 - expect(new Set(extracionRegex)).toEqual(new Set(extractionSpyValue)); - expect(parseMumber).toEqual(parsedSpyValue); - expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); - } catch (err) { - // 실패하면 에러 케이스 저장 - saveErrorCase(input, output, parseMumber, extracionRegex); - throw err; - } - }); +it('커스텀 구분자를 통해 테스트 합니다', async () => { + const [input, output, parseMumber, extracionRegex] = getRandomInput(); + + inputView.readLineMessage.mockImplementationOnce(() => input); + + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); + const extractionValueSpy = jest.spyOn(Extraction.prototype, 'extractCustom'); + + try { + await controller.run(); + + const extractionSpyValue = extractionValueSpy.mock.results[0].value; + const parsedSpyValue = parsedValueSpy.mock.results[0].value; + + compareArrays(parseMumber, parsedSpyValue); // 디버깅을 위한 콘솔 + expect(new Set(extracionRegex)).toEqual(new Set(extractionSpyValue.regexs)); + expect(parseMumber).toEqual(parsedSpyValue); + expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); + } catch (err) { + // 실패하면 에러 케이스 저장 + saveErrorCase(input, output, parseMumber, extracionRegex); + throw err; + } }); From 52d45575fa0a76f7c1ac5b99c69d8d8c2c04e74b Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 21:29:16 +0900 Subject: [PATCH 57/65] =?UTF-8?q?fix:=20/=EB=82=98=20\=20=EA=B0=80=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8=EB=90=9C=EA=B2=BD=EC=9A=B0=20=EC=97=B0?= =?UTF-8?q?=EC=86=8D=EB=90=9C=20=EA=B5=AC=EB=B6=84=EC=9E=90=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EB=AA=BB=ED=95=98=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- error_cases/last_error.json | 400 ------------------------------- src/controller/Controller.js | 5 +- src/service/validate/validate.js | 2 +- 3 files changed, 4 insertions(+), 403 deletions(-) delete mode 100644 error_cases/last_error.json diff --git a/error_cases/last_error.json b/error_cases/last_error.json deleted file mode 100644 index ddc7f0d5..00000000 --- a/error_cases/last_error.json +++ /dev/null @@ -1,400 +0,0 @@ -{ - "input": "//t\\n//n\\n//C\\n//\"\\n//K\\n//q\\n//L\\n///\\n//N\\n//g\\n//o\\n70282n88694\"73231g86588C57196n5651n27155t34673,35763,81836n77011C88823,8266t6838n2127N54395o22954:40274L17414,57977C99661/52607K55579,86119/49513n50076C20086C68652/47521t26586,93051C3553C99647o86512o63008L79293:63709,58323C23606g67859:1822\"63402K23216o30151\"38775g89323K67461L66252K73291N42808o4715K66659K87095t33744o82453/12792t69135K71687\"69857K95424g51972o79604\"35993N51221q39546:98679K29891g68766g50539t64037L97678L33733,64347K71679/4595/46831,65372/14485\"36887\"49393o28803C79322K31376,34671\"14388L53622/73578K87217q47026o72282g37515,39619q87848K32545K93417:75060o17211N6493/24250n92466n6317:61490N36568o71859L72546/32812,34778g99158K54096L8817g40289/21461\"43564o46653:71609q52191N55694q21555o47102o27245K6430t26154g15873n2923n16743/89993K58275o14739\"24152o62389N3559,11736o2127o56537,96334\"42958n37729q66791\"55582\"97971N11676N81604/49001,72257g21630g69847K17842o59572\"36800,60078o51686N61809t14667\"2493n17025t37522t80499:65393,88716g46689L6191,83905n50756\"97329o49648:6059,85380\"58916q95243L2144C16664C83656C25114C75272n56142g37862t2237C98609C48205o87455K56402K16757L57392:3987g13400\"44410t97302K56079:42565\"70816g77611C30563\"25495N85747q57579L1224C55880q94003K87294:54470\"71335L62592N28010g16354:8329:85358t30025L44365K22852\"93031:63318/39107C80163n67826q10391C14852g37880K53309q22302N84895t403o43770n88643K3138\"47341/64831/81442L93356N14728t59183K1946q56861C43705/29554o3018N58133N43115C11136q63400K58633:25816,10901g94844n93881g98790K4329\"44890n19439,45965o38798g24656/41694N46965C56949,88886t41901K82119:1212n82723o46780L92238/10939C41909g47171N3523C41081o50795N15293N5849,72823C44880o49369K25987o98221/36623t37281,5042\"19048C79624K83437N78237K23533o87971q9961g27562:75175n24948K13806L6582N71846t74622L22980\"50348g88296,26857t79257g11600:70094o44893o7232\"57427:59540C60167L14784q82597q84100:46398o57608:23135g90291,83665K48972n70802L82146t79681o19154,37684n28684C85383n98589C43226q17583C75992g4084L92212o17420n73329q93070L24968N31137L18968\"94382n97665q87498g44785o4665/45738o37833g83709:8100C31169:88812,33797\"87654\"59084L92357C16934L91817n63236n80118\"28662K16664t86775n88517/31377o75611,59723\"91176\"7818q80105,29935N26362:29458C73748\"71665K90876g92682t44865t78435t45n21147\"11468N84629/87506o89676N4311n36710,98259t38258,38784K36732", - "output": 19098725, - "extracionRegex": [ - "t", - "n", - "C", - "\"", - "K", - "q", - "L", - "/", - "N", - "g", - "o" - ], - "parseMumber": [ - 70282, - 88694, - 73231, - 86588, - 57196, - 5651, - 27155, - 34673, - 35763, - 81836, - 77011, - 88823, - 8266, - 6838, - 2127, - 54395, - 22954, - 40274, - 17414, - 57977, - 99661, - 52607, - 55579, - 86119, - 49513, - 50076, - 20086, - 68652, - 47521, - 26586, - 93051, - 3553, - 99647, - 86512, - 63008, - 79293, - 63709, - 58323, - 23606, - 67859, - 1822, - 63402, - 23216, - 30151, - 38775, - 89323, - 67461, - 66252, - 73291, - 42808, - 4715, - 66659, - 87095, - 33744, - 82453, - 12792, - 69135, - 71687, - 69857, - 95424, - 51972, - 79604, - 35993, - 51221, - 39546, - 98679, - 29891, - 68766, - 50539, - 64037, - 97678, - 33733, - 64347, - 71679, - 4595, - 46831, - 65372, - 14485, - 36887, - 49393, - 28803, - 79322, - 31376, - 34671, - 14388, - 53622, - 73578, - 87217, - 47026, - 72282, - 37515, - 39619, - 87848, - 32545, - 93417, - 75060, - 17211, - 6493, - 24250, - 92466, - 6317, - 61490, - 36568, - 71859, - 72546, - 32812, - 34778, - 99158, - 54096, - 8817, - 40289, - 21461, - 43564, - 46653, - 71609, - 52191, - 55694, - 21555, - 47102, - 27245, - 6430, - 26154, - 15873, - 2923, - 16743, - 89993, - 58275, - 14739, - 24152, - 62389, - 3559, - 11736, - 2127, - 56537, - 96334, - 42958, - 37729, - 66791, - 55582, - 97971, - 11676, - 81604, - 49001, - 72257, - 21630, - 69847, - 17842, - 59572, - 36800, - 60078, - 51686, - 61809, - 14667, - 2493, - 17025, - 37522, - 80499, - 65393, - 88716, - 46689, - 6191, - 83905, - 50756, - 97329, - 49648, - 6059, - 85380, - 58916, - 95243, - 2144, - 16664, - 83656, - 25114, - 75272, - 56142, - 37862, - 2237, - 98609, - 48205, - 87455, - 56402, - 16757, - 57392, - 3987, - 13400, - 44410, - 97302, - 56079, - 42565, - 70816, - 77611, - 30563, - 25495, - 85747, - 57579, - 1224, - 55880, - 94003, - 87294, - 54470, - 71335, - 62592, - 28010, - 16354, - 8329, - 85358, - 30025, - 44365, - 22852, - 93031, - 63318, - 39107, - 80163, - 67826, - 10391, - 14852, - 37880, - 53309, - 22302, - 84895, - 403, - 43770, - 88643, - 3138, - 47341, - 64831, - 81442, - 93356, - 14728, - 59183, - 1946, - 56861, - 43705, - 29554, - 3018, - 58133, - 43115, - 11136, - 63400, - 58633, - 25816, - 10901, - 94844, - 93881, - 98790, - 4329, - 44890, - 19439, - 45965, - 38798, - 24656, - 41694, - 46965, - 56949, - 88886, - 41901, - 82119, - 1212, - 82723, - 46780, - 92238, - 10939, - 41909, - 47171, - 3523, - 41081, - 50795, - 15293, - 5849, - 72823, - 44880, - 49369, - 25987, - 98221, - 36623, - 37281, - 5042, - 19048, - 79624, - 83437, - 78237, - 23533, - 87971, - 9961, - 27562, - 75175, - 24948, - 13806, - 6582, - 71846, - 74622, - 22980, - 50348, - 88296, - 26857, - 79257, - 11600, - 70094, - 44893, - 7232, - 57427, - 59540, - 60167, - 14784, - 82597, - 84100, - 46398, - 57608, - 23135, - 90291, - 83665, - 48972, - 70802, - 82146, - 79681, - 19154, - 37684, - 28684, - 85383, - 98589, - 43226, - 17583, - 75992, - 4084, - 92212, - 17420, - 73329, - 93070, - 24968, - 31137, - 18968, - 94382, - 97665, - 87498, - 44785, - 4665, - 45738, - 37833, - 83709, - 8100, - 31169, - 88812, - 33797, - 87654, - 59084, - 92357, - 16934, - 91817, - 63236, - 80118, - 28662, - 16664, - 86775, - 88517, - 31377, - 75611, - 59723, - 91176, - 7818, - 80105, - 29935, - 26362, - 29458, - 73748, - 71665, - 90876, - 92682, - 44865, - 78435, - 45, - 21147, - 11468, - 84629, - 87506, - 89676, - 4311, - 36710, - 98259, - 38258, - 38784, - 36732 - ] -} \ No newline at end of file diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 68b04b71..1c378447 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -19,14 +19,15 @@ export default class Controller { // validate.isLong(input); const { regexs, esacpedRegexs } = this.extraction.extractCustom(input); + console.log(regexs, esacpedRegexs); validate.isRegexValidError(regexs); - validate.isRegexContinueError(input, esacpedRegexs); - console.log(regexs); const parsingCustomText = this.parser.parseCustomRegex(input, regexs); console.log(parsingCustomText); + validate.isRegexContinueError(parsingCustomText, esacpedRegexs); + const parsedNumber = this.parser.parseData( parsingCustomText, esacpedRegexs, diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index bf0223a0..186b3a18 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -16,7 +16,7 @@ const validate = { [',', ':', ...esacpedRegexs].forEach((delim) => { const regex = new RegExp(`${delim}{2,}`); if (regex.test(inputText)) - throw new Error('[ERROR] 연속된 구분자가 존재합니다'); + throw new Error(`[ERROR] 연속된 구분자가 존재합니다 ${delim}`); }); }, isRegexValidError(customRegexs) { From df765a82ba666a6e0992c2644453d584506e5ad2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 21:56:18 +0900 Subject: [PATCH 58/65] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=98=EB=B3=B5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=88=98=EC=A0=95=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/RandomMaker.test.js | 193 +++++++++++++-------------------------- test/randomUtil.js | 100 ++++++++++++++++++++ test/validate.test.js | 122 +++++++++++++++---------- 3 files changed, 235 insertions(+), 180 deletions(-) create mode 100644 test/randomUtil.js diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 28320b6d..1523ce33 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -1,12 +1,12 @@ // 랜덤 문자열 생성기 -import { Console, Random } from '@woowacourse/mission-utils'; -import fs from 'fs'; -import path from 'path'; +import { Console } from '@woowacourse/mission-utils'; + import Controller from '../src/controller/Controller.js'; import inputView from '../src/view/InputView.js'; import Parser from '../src/service/model/Parser.js'; import Extraction from '../src/service/model/Extraction.js'; +import { compareArrays, getRandomInput, saveErrorCase } from './randomUtil.js'; jest.mock('../src/view/InputView.js', () => ({ __esModule: true, @@ -14,136 +14,67 @@ jest.mock('../src/view/InputView.js', () => ({ readLineMessage: jest.fn(), }, })); -// 1. 커스텀 구분자를 포함하는 경우 -// 2. 커스텀 구분자를 포함하지 않는경우 - -// 32 ~ 126 텍스트 -// 48 ~ 57 : 숫자 - -// 숫자 추가하기 함수 getRandomNumber -// 커스텀 구분자 추가하기 함수 getCustomRegexWithText -// 구분자 추가하기 함수 - -const compareArrays = (expected, actual) => { - const maxLength = Math.max(expected.length, actual.length); - let hasDifference = false; - - for (let i = 0; i < maxLength; i++) { - const exp = expected[i]; - const act = actual[i]; - if (exp !== act) { - console.log(`인덱스 ${i}: expected=${exp}, actual=${act}`); - hasDifference = true; - } - } - - if (!hasDifference) { - console.log('두 배열은 동일합니다.'); - } -}; - -const ERROR_DIR = path.join(process.cwd(), 'error_cases'); -if (!fs.existsSync(ERROR_DIR)) fs.mkdirSync(ERROR_DIR); -const errorFilePath = path.join(ERROR_DIR, 'last_error.json'); - -export const saveErrorCase = (input, output, parseMumber, extracionRegex) => { - fs.writeFileSync( - errorFilePath, - JSON.stringify({ input, output, extracionRegex, parseMumber }, null, 4), - ); -}; -const getCustomRegexWithText = (text) => `//${text}\\n`; -const getRandomNumber = (start = 1, end = 9) => - Random.pickNumberInRange(start, end); - -const makeCustomRegex = (customRegexLength) => { - const customRegexs = []; - for (let i = 0; i < customRegexLength; i++) { - let charCode; - do { - charCode = Random.pickNumberInRange(33, 126); // 32번(공백) 제외 - } while (charCode >= 48 && charCode <= 57); // 숫자 제외 - customRegexs.push(String.fromCharCode(charCode)); - } - return [...new Set(customRegexs)]; -}; - -const getRandomValueInArray = (values) => { - const index = getRandomNumber(0, values.length - 1); - return values[index]; -}; - -const getRandomInput = () => { - if (fs.existsSync(errorFilePath)) { - const data = JSON.parse(fs.readFileSync(errorFilePath, 'utf-8')); - return [data.input, data.output, data.parseMumber, data.extracionRegex]; - } - - let definitionString = ''; // "//P\n//l\n" 등 정의가 담길 부분 - let numberString = ''; // "123P456l789..." 숫자가 담길 부분 - let outputResult = 0; - - const customRegexLength = getRandomNumber(1, 100); - const madeCustomRegex = makeCustomRegex(customRegexLength); - const parseMumber = []; - const usedCustomRegex = []; - const numToUse = getRandomNumber(1, madeCustomRegex.length); // 최소 1개는 사용 - while (usedCustomRegex.length < numToUse) { - const regex = getRandomValueInArray(madeCustomRegex); - if (!usedCustomRegex.includes(regex)) { - usedCustomRegex.push(regex); +const TOTAL_ITERATIONS = 10000; + +describe(`랜덤 테스트 ${TOTAL_ITERATIONS}`, () => { + it(`커스텀 구분자를 통해 ${TOTAL_ITERATIONS} 테스트 합니다`, async () => { + for (let i = 1; i <= TOTAL_ITERATIONS; i++) { + const [input, output, parseMumber, extracionRegex] = getRandomInput(); + + inputView.readLineMessage.mockImplementationOnce(() => input); + + const controller = new Controller(); + const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); + const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); + const extractionValueSpy = jest.spyOn( + Extraction.prototype, + 'extractCustom', + ); + + try { + await controller.run(); + + const extractionSpyValue = extractionValueSpy.mock.results[0].value; + const parsedSpyValue = parsedValueSpy.mock.results[0].value; + + // 진행 상황 표시 (100번마다) + if (i % 100 === 0) { + console.log(`진행 중: ${i}/${TOTAL_ITERATIONS} `); + } + + compareArrays(parseMumber, parsedSpyValue); + + expect(new Set(extracionRegex)).toEqual( + new Set(extractionSpyValue.regexs), + ); + expect(parseMumber).toEqual(parsedSpyValue); + expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); + } catch (err) { + // 에러 발생 시 즉시 저장하고 테스트 중단 + saveErrorCase( + input, + output, + parseMumber, + extracionRegex, + i, + err.message, + ); + + // 에러를 다시 던져서 테스트 실패 처리 + throw new Error( + `테스트 실패 (${i}/${TOTAL_ITERATIONS}번째)\n원본 에러: ${err.message}`, + ); + } + + // spy 정리 + logSpy.mockRestore(); + parsedValueSpy.mockRestore(); + extractionValueSpy.mockRestore(); } - } - usedCustomRegex.forEach((regex) => { - definitionString += getCustomRegexWithText(regex); // `//${regex}\n` + console.log( + `\n모든 테스트 통과! (${TOTAL_ITERATIONS}/${TOTAL_ITERATIONS})`, + ); }); - - const allAvailableDelimiters = [...usedCustomRegex, ':', ',']; - - const numberLength = getRandomNumber(1, 1000); - - for (let i = 0; i < numberLength; i++) { - const randNumber = getRandomNumber(0, 100000); - numberString += randNumber; // 숫자 문자열에 추가 - outputResult += randNumber; - parseMumber.push(randNumber); - - if (i < numberLength - 1) { - const delimiter = getRandomValueInArray(allAvailableDelimiters); - numberString += delimiter; // `//P\n`가 아닌 `P` 자체가 추가됨 - } - } - - const inputResult = definitionString + numberString; - - return [inputResult, outputResult, parseMumber, usedCustomRegex]; -}; - -it('커스텀 구분자를 통해 테스트 합니다', async () => { - const [input, output, parseMumber, extracionRegex] = getRandomInput(); - - inputView.readLineMessage.mockImplementationOnce(() => input); - - const controller = new Controller(); - const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); - const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); - const extractionValueSpy = jest.spyOn(Extraction.prototype, 'extractCustom'); - - try { - await controller.run(); - - const extractionSpyValue = extractionValueSpy.mock.results[0].value; - const parsedSpyValue = parsedValueSpy.mock.results[0].value; - - compareArrays(parseMumber, parsedSpyValue); // 디버깅을 위한 콘솔 - expect(new Set(extracionRegex)).toEqual(new Set(extractionSpyValue.regexs)); - expect(parseMumber).toEqual(parsedSpyValue); - expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); - } catch (err) { - // 실패하면 에러 케이스 저장 - saveErrorCase(input, output, parseMumber, extracionRegex); - throw err; - } }); diff --git a/test/randomUtil.js b/test/randomUtil.js new file mode 100644 index 00000000..0c8c88f9 --- /dev/null +++ b/test/randomUtil.js @@ -0,0 +1,100 @@ +import fs from 'fs'; +import path from 'path'; +import { Random } from '@woowacourse/mission-utils'; + +const ERROR_DIR = path.join(process.cwd(), 'error_cases'); +if (!fs.existsSync(ERROR_DIR)) fs.mkdirSync(ERROR_DIR); +const errorFilePath = path.join(ERROR_DIR, 'last_error.json'); + +const getCustomRegexWithText = (text) => `//${text}\\n`; +const getRandomNumber = (start = 1, end = 9) => + Random.pickNumberInRange(start, end); + +// 커스텀 구분자 생성기 +const makeCustomRegex = (customRegexCount) => { + const customRegexs = []; + for (let i = 0; i < customRegexCount; i++) { + let charCode; + do { + charCode = Random.pickNumberInRange(33, 126); // 32번(공백) 제외 + } while (charCode >= 48 && charCode <= 57); // 숫자 제외 + customRegexs.push(String.fromCharCode(charCode)); + } + return [...new Set(customRegexs)]; +}; + +// ARRAY -> 랜덤한 value 추출 +const getRandomValueInArray = (values) => { + const index = getRandomNumber(0, values.length - 1); + return values[index]; +}; + +// 애러 저장 +export const saveErrorCase = (input, output, parseMumber, extracionRegex) => { + fs.writeFileSync( + errorFilePath, + JSON.stringify({ input, output, extracionRegex, parseMumber }, null, 4), + ); +}; + +// 랜덤 인풋 생성기 +export const getRandomInput = () => { + // 애러가 나면 애러부터 다시 검증 + if (fs.existsSync(errorFilePath)) { + const data = JSON.parse(fs.readFileSync(errorFilePath, 'utf-8')); + return [data.input, data.output, data.parseMumber, data.extracionRegex]; + } + + let definitionString = ''; // "//P\n//l\n" 등 정의가 담길 부분 + let numberString = ''; // "123P456l789..." 숫자가 담길 부분 + let outputResult = 0; + + const customRegexCount = getRandomNumber(1, 100); + const madeCustomRegex = makeCustomRegex(customRegexCount); + const parseMumber = []; + + const usedCustomRegex = []; + const customRegexNumToUse = getRandomNumber(0, madeCustomRegex.length); + while (usedCustomRegex.length < customRegexNumToUse) { + const regex = getRandomValueInArray(madeCustomRegex); + if (!usedCustomRegex.includes(regex)) { + usedCustomRegex.push(regex); + } + } + + usedCustomRegex.forEach((regex) => { + definitionString += getCustomRegexWithText(regex); // `//${regex}\n` + }); + + const allAvailableDelimiters = [...usedCustomRegex, ':', ',']; + + const numberCount = getRandomNumber(1, 1000); + + for (let i = 0; i < numberCount; i++) { + const randNumber = getRandomNumber(0, 100000); + numberString += randNumber; // 숫자 문자열에 추가 + outputResult += randNumber; + parseMumber.push(randNumber); + + if (i < numberCount - 1) { + const delimiter = getRandomValueInArray(allAvailableDelimiters); + numberString += delimiter; // //P\n가 아닌 P 자체가 추가됨 + } + } + + const inputResult = definitionString + numberString; + + return [inputResult, outputResult, parseMumber, usedCustomRegex]; +}; + +export const compareArrays = (expected, actual) => { + const maxLength = Math.max(expected.length, actual.length); + + for (let i = 0; i < maxLength; i++) { + const exp = expected[i]; + const act = actual[i]; + if (exp !== act) { + throw new Error(`인덱스 ${i}: expected=${exp}, actual=${act}`); + } + } +}; diff --git a/test/validate.test.js b/test/validate.test.js index 7c16e955..c67add88 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -2,35 +2,35 @@ import validate from '../src/service/validate/validate.js'; import { ERROR_MESSAGE } from '../src/constant/error.js'; -describe('validate.isNumber - 숫자 검증', () => { +describe('validate.validateNumbers - 숫자 검증', () => { describe('정상 케이스', () => { it('정상적인 숫자 배열은 에러를 발생시키지 않는다', () => { const numbers = [1, 2, 3]; - expect(() => validate.isNumber(numbers)).not.toThrow(); + expect(() => validate.validateNumbers(numbers)).not.toThrow(); }); it('0을 포함한 숫자 배열은 정상 처리된다', () => { const numbers = [0, 1, 2]; - expect(() => validate.isNumber(numbers)).not.toThrow(); + expect(() => validate.validateNumbers(numbers)).not.toThrow(); }); it('문자열 형태의 숫자는 정상 처리된다', () => { const numbers = ['1', '2', '3']; - expect(() => validate.isNumber(numbers)).not.toThrow(); + expect(() => validate.validateNumbers(numbers)).not.toThrow(); }); }); describe('예외 케이스 - 숫자가 아닌 값', () => { it('숫자가 아닌 문자가 포함된 경우 에러를 발생시킨다', () => { const numbers = ['우', '테', '코', 1]; - expect(() => validate.isNumber(numbers)).toThrow( + expect(() => validate.validateNumbers(numbers)).toThrow( ERROR_MESSAGE.NOT_NUMBER, ); }); it('알파벳이 포함된 경우 에러를 발생시킨다', () => { const numbers = ['a', 'b', 1]; - expect(() => validate.isNumber(numbers)).toThrow( + expect(() => validate.validateNumbers(numbers)).toThrow( ERROR_MESSAGE.NOT_NUMBER, ); }); @@ -39,48 +39,58 @@ describe('validate.isNumber - 숫자 검증', () => { describe('예외 케이스 - 공백/빈값', () => { it('빈 문자열이 포함된 경우 에러를 발생시킨다', () => { const numbers = ['', 1, 2, 1]; - expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + expect(() => validate.validateNumbers(numbers)).toThrow( + ERROR_MESSAGE.NOT_EMPTY, + ); }); it('공백 문자열이 포함된 경우 에러를 발생시킨다', () => { const numbers = [' ', 1, 2]; - expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + expect(() => validate.validateNumbers(numbers)).toThrow( + ERROR_MESSAGE.NOT_EMPTY, + ); }); it('여러 개의 공백이 포함된 경우 에러를 발생시킨다', () => { const numbers = [' ', 1, 2]; - expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_EMPTY); + expect(() => validate.validateNumbers(numbers)).toThrow( + ERROR_MESSAGE.NOT_EMPTY, + ); }); }); describe('예외 케이스 - 음수', () => { it('음수가 포함된 경우 에러를 발생시킨다', () => { const numbers = [-1, 2, 3]; - expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_MINUS); + expect(() => validate.validateNumbers(numbers)).toThrow( + ERROR_MESSAGE.NOT_MINUS, + ); }); it('0보다 작은 음수가 포함된 경우 에러를 발생시킨다', () => { const numbers = [1, -5, 3]; - expect(() => validate.isNumber(numbers)).toThrow(ERROR_MESSAGE.NOT_MINUS); + expect(() => validate.validateNumbers(numbers)).toThrow( + ERROR_MESSAGE.NOT_MINUS, + ); }); }); }); -describe('validate.isRegexContinueError - 연속된 구분자 검증', () => { +describe('validate.validateContinuousDelimiters - 연속된 구분자 검증', () => { describe('정상 케이스', () => { it('구분자가 연속되지 않은 경우 에러를 발생시키지 않는다', () => { const inputText = '1,2:3'; - const customRegexs = []; + const escapedDelimiters = []; expect(() => - validate.isRegexContinueError(inputText, customRegexs), + validate.validateContinuousDelimiters(inputText, escapedDelimiters), ).not.toThrow(); }); it('커스텀 구분자가 연속되지 않은 경우 에러를 발생시키지 않는다', () => { const inputText = '1;2;3'; - const customRegexs = [';']; + const escapedDelimiters = [';']; expect(() => - validate.isRegexContinueError(inputText, customRegexs), + validate.validateContinuousDelimiters(inputText, escapedDelimiters), ).not.toThrow(); }); }); @@ -88,94 +98,108 @@ describe('validate.isRegexContinueError - 연속된 구분자 검증', () => { describe('예외 케이스 - 연속된 구분자', () => { it('쉼표가 연속된 경우 에러를 발생시킨다', () => { const inputText = '1,,2'; - const customRegexs = []; + const escapedDelimiters = []; expect(() => - validate.isRegexContinueError(inputText, customRegexs), - ).toThrow('[ERROR]'); + validate.validateContinuousDelimiters(inputText, escapedDelimiters), + ).toThrow(ERROR_MESSAGE.DELIMITER_CONTINUOUS); }); it('콜론이 연속된 경우 에러를 발생시킨다', () => { const inputText = '1::2'; - const customRegexs = []; + const escapedDelimiters = []; expect(() => - validate.isRegexContinueError(inputText, customRegexs), - ).toThrow('[ERROR]'); + validate.validateContinuousDelimiters(inputText, escapedDelimiters), + ).toThrow(ERROR_MESSAGE.DELIMITER_CONTINUOUS); }); it('커스텀 구분자가 연속된 경우 에러를 발생시킨다', () => { const inputText = '1;;2'; - const customRegexs = [';']; + const escapedDelimiters = [';']; expect(() => - validate.isRegexContinueError(inputText, customRegexs), - ).toThrow('[ERROR]'); + validate.validateContinuousDelimiters(inputText, escapedDelimiters), + ).toThrow(ERROR_MESSAGE.DELIMITER_CONTINUOUS); }); it('세 개 이상 연속된 구분자도 에러를 발생시킨다', () => { const inputText = '1,,,2'; - const customRegexs = []; + const escapedDelimiters = []; expect(() => - validate.isRegexContinueError(inputText, customRegexs), - ).toThrow('[ERROR]'); + validate.validateContinuousDelimiters(inputText, escapedDelimiters), + ).toThrow(ERROR_MESSAGE.DELIMITER_CONTINUOUS); }); }); }); -describe('validate.isRegexValidError - 구분자 유효성 검증', () => { +describe('validate.validateDelimiters - 구분자 유효성 검증', () => { describe('정상 케이스', () => { it('올바른 커스텀 구분자는 에러를 발생시키지 않는다', () => { - const customRegexs = [';', '|']; - expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + const customDelimiters = [';', '|']; + expect(() => validate.validateDelimiters(customDelimiters)).not.toThrow(); }); it('특수문자 구분자는 정상 처리된다', () => { - const customRegexs = ['!', '@', '#']; - expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + const customDelimiters = ['!', '@', '#']; + expect(() => validate.validateDelimiters(customDelimiters)).not.toThrow(); }); it('한글 구분자는 정상 처리된다', () => { - const customRegexs = ['가', 'ㄱ']; - expect(() => validate.isRegexValidError(customRegexs)).not.toThrow(); + const customDelimiters = ['가', 'ㄱ']; + expect(() => validate.validateDelimiters(customDelimiters)).not.toThrow(); }); }); describe('예외 케이스 - 빈 구분자', () => { it('빈 문자열 구분자는 에러를 발생시킨다', () => { - const customRegexs = ['']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = ['']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_EMPTY, + ); }); it('여러 구분자 중 빈 값이 포함된 경우 에러를 발생시킨다', () => { - const customRegexs = [';', '', '|']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = [';', '', '|']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_EMPTY, + ); }); }); describe('예외 케이스 - 공백 포함', () => { it('공백이 포함된 구분자는 에러를 발생시킨다', () => { - const customRegexs = [' ']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = [' ']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_HAS_WHITESPACE, + ); }); it('구분자에 공백이 섞여있는 경우 에러를 발생시킨다', () => { - const customRegexs = ['a b']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = ['a b']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_HAS_WHITESPACE, + ); }); }); describe('예외 케이스 - 숫자 포함', () => { it('숫자가 포함된 구분자는 에러를 발생시킨다', () => { - const customRegexs = ['1']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = ['1']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_HAS_NUMBER, + ); }); it('문자와 숫자가 섞인 구분자는 에러를 발생시킨다', () => { - const customRegexs = ['a1']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = ['a1']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_HAS_NUMBER, + ); }); it('여러 구분자 중 숫자가 포함된 경우 에러를 발생시킨다', () => { - const customRegexs = [';', '5', '|']; - expect(() => validate.isRegexValidError(customRegexs)).toThrow('[ERROR]'); + const customDelimiters = [';', '5', '|']; + expect(() => validate.validateDelimiters(customDelimiters)).toThrow( + ERROR_MESSAGE.DELIMITER_HAS_NUMBER, + ); }); }); }); From 7d28a8065dd0df99a22b58f06f3c297b9d7acfc7 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 21:58:48 +0900 Subject: [PATCH 59/65] =?UTF-8?q?refactor:=20validate=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/error.js | 8 +++-- src/constant/regex.js | 51 ++++++++++++++++++++++++++ src/controller/Controller.js | 15 +++----- src/service/model/Extraction.js | 11 +++--- src/service/model/Parser.js | 8 +++-- src/service/validate/validate.js | 61 ++++++++++++++++++-------------- 6 files changed, 109 insertions(+), 45 deletions(-) diff --git a/src/constant/error.js b/src/constant/error.js index c19f6414..eaf4cf26 100644 --- a/src/constant/error.js +++ b/src/constant/error.js @@ -1,5 +1,9 @@ export const ERROR_MESSAGE = { NOT_MINUS: '[ERROR] 음수가 포함되어 있습니다.', - NOT_EMPTY: '[ERROR] 공백이 포함되어 있습니다', - NOT_NUMBER: '[ERROR] 숫자가 아닌것이 포함되어 있습니다', + NOT_EMPTY: '[ERROR] 공백이 포함되어 있습니다.', + NOT_NUMBER: '[ERROR] 숫자가 아닌것이 포함되어 있습니다.', + DELIMITER_CONTINUOUS: '[ERROR] 연속된 구분자가 존재합니다.', + DELIMITER_EMPTY: '[ERROR] 구분자가 공백입니다.', + DELIMITER_HAS_WHITESPACE: '[ERROR] 구분자에 공백이 포함되어 있습니다.', + DELIMITER_HAS_NUMBER: '[ERROR] 구분자에 숫자가 포함되어 있습니다.', }; diff --git a/src/constant/regex.js b/src/constant/regex.js index e69de29b..53faf7d1 100644 --- a/src/constant/regex.js +++ b/src/constant/regex.js @@ -0,0 +1,51 @@ +/** + * 정규식 패턴 상수 + */ +export const REGEX_PATTERNS = { + // 커스텀 구분자 추출: //;\\n 형식에서 ; 추출 + CUSTOM_DELIMITER_EXTRACT: /\/\/(.*?)\\n/g, + + // 커스텀 구분자 정의 부분 제거: //;\\n 제거 + CUSTOM_DELIMITER_DEFINITION: /\/\/.+?\\n/g, + + // 정규식 특수문자 이스케이프용 + REGEX_SPECIAL_CHARS: /[.*+?^${}()|[\]\\]/g, + + // 공백 검사 + WHITESPACE: /\s/, + + // 숫자 검사 + DIGIT: /\d/, +}; + +/** + * 정규식 관련 유틸리티 함수 + */ +export const RegexUtils = { + /** + * 정규식 특수문자를 이스케이프합니다. + * @param {string} str - 이스케이프할 문자열 + * @returns {string} 이스케이프된 문자열 + */ + escapeRegexChars(str) { + return str.replace(REGEX_PATTERNS.REGEX_SPECIAL_CHARS, '\\$&'); + }, + + /** + * 연속된 구분자를 검사하는 정규식을 생성합니다. + * @param {string} delimiter - 이스케이프된 구분자 + * @returns {RegExp} 연속 검사용 정규식 + */ + createContinuousPattern(delimiter) { + return new RegExp(`${delimiter}{2,}`); + }, + + /** + * 여러 구분자로 분할하는 정규식을 생성합니다. + * @param {Array} delimiters - 구분자 배열 + * @returns {RegExp} 분할용 정규식 + */ + createSplitPattern(delimiters) { + return new RegExp(delimiters.join('|')); + }, +}; diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 1c378447..715bb58d 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -16,24 +16,19 @@ export default class Controller { const input = await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); - // validate.isLong(input); + const { regexs, escapedRegexs } = this.extraction.extractCustom(input); - const { regexs, esacpedRegexs } = this.extraction.extractCustom(input); - console.log(regexs, esacpedRegexs); - - validate.isRegexValidError(regexs); + validate.validateDelimiters(regexs); const parsingCustomText = this.parser.parseCustomRegex(input, regexs); - console.log(parsingCustomText); - validate.isRegexContinueError(parsingCustomText, esacpedRegexs); + validate.validateContinuousDelimiters(parsingCustomText, escapedRegexs); const parsedNumber = this.parser.parseData( parsingCustomText, - esacpedRegexs, + escapedRegexs, ); - console.log(parsedNumber); - validate.isNumber(parsedNumber); + validate.validateNumbers(parsedNumber); const numbers = new Number(parsedNumber); await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); } catch (error) { diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index 37906545..ad8796c7 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -1,15 +1,18 @@ -const regex = /\/\/(.*?)\\n/g; +import { REGEX_PATTERNS, RegexUtils } from '../../constant/regex.js'; export default class Extraction { extractCustom(inputText) { - const matches = Array.from(inputText.matchAll(regex), (match) => match[1]); + const matches = Array.from( + inputText.matchAll(REGEX_PATTERNS.CUSTOM_DELIMITER_EXTRACT), + (match) => match[1], + ); return { regexs: matches, // 원본 - esacpedRegexs: this.#escapeRegex(matches), // 이스케이프 (사용용) + escapedRegexs: this.#escapeRegex(matches), // 이스케이프 (사용용) }; } #escapeRegex(regexs) { - return regexs.map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + return regexs.map((item) => RegexUtils.escapeRegexChars(item)); } } diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index 6ee94f12..ccfa35c5 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,7 +1,9 @@ +import { RegexUtils } from '../../constant/regex.js'; + export default class Parser { - parseData(inputText, esacpedRegexs) { - const totalRegexs = [...esacpedRegexs, ',', ':']; - const regex = new RegExp(totalRegexs.join('|')); + parseData(inputText, escapedRegexs) { + const totalRegexs = [...escapedRegexs, ',', ':']; + const regex = RegexUtils.createSplitPattern(totalRegexs); return inputText .split(regex) .filter((item) => item !== '') // 빈문자열 제거 diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 186b3a18..f2375c02 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,45 +1,54 @@ import { ERROR_MESSAGE } from '../../constant/error.js'; +const DEFAULT_DELIMITERS = [',', ':']; + const validate = { - isNumber(numbers) { + validateNumbers(numbers) { numbers.forEach((number) => { - // 공백을 허용하지 않음 - if (typeof number === 'string' && number.trim() === '') + // 공백 체크 + if (typeof number === 'string' && number.trim() === '') { throw new Error(ERROR_MESSAGE.NOT_EMPTY); - if (Number.isNaN(Number(number))) + } + // 숫자 여부 체크 + if (Number.isNaN(Number(number))) { throw new Error(ERROR_MESSAGE.NOT_NUMBER); - if (number < 0) throw new Error(ERROR_MESSAGE.NOT_MINUS); + } + // 음수 체크 + if (number < 0) { + throw new Error(ERROR_MESSAGE.NOT_MINUS); + } }); }, - isRegexContinueError(inputText, esacpedRegexs) { - // 각 구분자마다 연속 검사 - [',', ':', ...esacpedRegexs].forEach((delim) => { - const regex = new RegExp(`${delim}{2,}`); - if (regex.test(inputText)) - throw new Error(`[ERROR] 연속된 구분자가 존재합니다 ${delim}`); + + validateContinuousDelimiters(inputText, escapedDelimiters) { + const textToValidate = inputText.replace(/\/\/.+?\\n/g, ''); + const allDelimiters = [...DEFAULT_DELIMITERS, ...escapedDelimiters]; + allDelimiters.forEach((delimiter) => { + const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const continuousPattern = new RegExp(`${escapedDelimiter}{2,}`); + + if (continuousPattern.test(textToValidate)) { + throw new Error(ERROR_MESSAGE.DELIMITER_CONTINUOUS); + } }); }, - isRegexValidError(customRegexs) { - customRegexs.forEach((regex) => { - if (!regex) { - throw new Error('[ERROR]구분자가 공백입니다'); + + validateDelimiters(customDelimiters) { + customDelimiters.forEach((delimiter) => { + // 빈 값 체크 + if (!delimiter) { + throw new Error(ERROR_MESSAGE.DELIMITER_EMPTY); } // 공백 포함 체크 - if (/\s/.test(regex)) { - throw new Error('[ERROR] 구분자가 공백입니다'); + if (/\s/.test(delimiter)) { + throw new Error(ERROR_MESSAGE.DELIMITER_HAS_WHITESPACE); } // 숫자 포함 체크 - if (/\d/.test(regex)) { - throw new Error('[ERROR] 구분자가 숫자입니다'); + if (/\d/.test(delimiter)) { + throw new Error(ERROR_MESSAGE.DELIMITER_HAS_NUMBER); } }); }, - - // node js 환경에서 입력은 512MB~1GB 로 isLong 함수 불필요 - // isLong(numbers) { - // if (numbers.length >= 50) { - // throw new Error('[ERROR]'); - // } - // }, }; + export default validate; From 03c02670b03c88161a27123ba6fd78d2e5b89c89 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 22:00:50 +0900 Subject: [PATCH 60/65] =?UTF-8?q?refactor:=20regex=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=EB=A5=BC=20=EB=B6=84=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/regex.js | 30 ------------------------------ src/service/validate/validate.js | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/constant/regex.js b/src/constant/regex.js index 53faf7d1..8776ede0 100644 --- a/src/constant/regex.js +++ b/src/constant/regex.js @@ -1,50 +1,20 @@ -/** - * 정규식 패턴 상수 - */ export const REGEX_PATTERNS = { - // 커스텀 구분자 추출: //;\\n 형식에서 ; 추출 CUSTOM_DELIMITER_EXTRACT: /\/\/(.*?)\\n/g, - - // 커스텀 구분자 정의 부분 제거: //;\\n 제거 CUSTOM_DELIMITER_DEFINITION: /\/\/.+?\\n/g, - - // 정규식 특수문자 이스케이프용 REGEX_SPECIAL_CHARS: /[.*+?^${}()|[\]\\]/g, - - // 공백 검사 WHITESPACE: /\s/, - - // 숫자 검사 DIGIT: /\d/, }; -/** - * 정규식 관련 유틸리티 함수 - */ export const RegexUtils = { - /** - * 정규식 특수문자를 이스케이프합니다. - * @param {string} str - 이스케이프할 문자열 - * @returns {string} 이스케이프된 문자열 - */ escapeRegexChars(str) { return str.replace(REGEX_PATTERNS.REGEX_SPECIAL_CHARS, '\\$&'); }, - /** - * 연속된 구분자를 검사하는 정규식을 생성합니다. - * @param {string} delimiter - 이스케이프된 구분자 - * @returns {RegExp} 연속 검사용 정규식 - */ createContinuousPattern(delimiter) { return new RegExp(`${delimiter}{2,}`); }, - /** - * 여러 구분자로 분할하는 정규식을 생성합니다. - * @param {Array} delimiters - 구분자 배열 - * @returns {RegExp} 분할용 정규식 - */ createSplitPattern(delimiters) { return new RegExp(delimiters.join('|')); }, diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index f2375c02..6aa28147 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -1,4 +1,5 @@ import { ERROR_MESSAGE } from '../../constant/error.js'; +import { REGEX_PATTERNS, RegexUtils } from '../../constant/regex.js'; const DEFAULT_DELIMITERS = [',', ':']; @@ -21,11 +22,17 @@ const validate = { }, validateContinuousDelimiters(inputText, escapedDelimiters) { - const textToValidate = inputText.replace(/\/\/.+?\\n/g, ''); + // 커스텀 구분자 정의 부분 제거 + const textToValidate = inputText.replace( + REGEX_PATTERNS.CUSTOM_DELIMITER_DEFINITION, + '', + ); const allDelimiters = [...DEFAULT_DELIMITERS, ...escapedDelimiters]; + allDelimiters.forEach((delimiter) => { - const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const continuousPattern = new RegExp(`${escapedDelimiter}{2,}`); + const escapedDelimiter = RegexUtils.escapeRegexChars(delimiter); + const continuousPattern = + RegexUtils.createContinuousPattern(escapedDelimiter); if (continuousPattern.test(textToValidate)) { throw new Error(ERROR_MESSAGE.DELIMITER_CONTINUOUS); @@ -40,11 +47,11 @@ const validate = { throw new Error(ERROR_MESSAGE.DELIMITER_EMPTY); } // 공백 포함 체크 - if (/\s/.test(delimiter)) { + if (REGEX_PATTERNS.WHITESPACE.test(delimiter)) { throw new Error(ERROR_MESSAGE.DELIMITER_HAS_WHITESPACE); } // 숫자 포함 체크 - if (/\d/.test(delimiter)) { + if (REGEX_PATTERNS.DIGIT.test(delimiter)) { throw new Error(ERROR_MESSAGE.DELIMITER_HAS_NUMBER); } }); From b66422a98184136a76b2fa387097ba7d10dfba1f Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 22:05:20 +0900 Subject: [PATCH 61/65] =?UTF-8?q?refactor:=20Parser=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=A0=95=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constant/regex.js | 4 ++++ src/controller/Controller.js | 18 ++++++++++------ src/service/model/Parser.js | 30 ++++++++++++++------------ test/Parser.test.js | 42 +++++++++++++++++------------------- test/RandomMaker.test.js | 2 +- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/constant/regex.js b/src/constant/regex.js index 8776ede0..24b7f8fb 100644 --- a/src/constant/regex.js +++ b/src/constant/regex.js @@ -18,4 +18,8 @@ export const RegexUtils = { createSplitPattern(delimiters) { return new RegExp(delimiters.join('|')); }, + + createCusomPattern(delimiter) { + return `//${delimiter}\\n`; + }, }; diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 715bb58d..91259a8c 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -20,17 +20,21 @@ export default class Controller { validate.validateDelimiters(regexs); - const parsingCustomText = this.parser.parseCustomRegex(input, regexs); + const textWithoutDefinitions = + this.parser.removeCustomDelimiterDefinitions(input, regexs); - validate.validateContinuousDelimiters(parsingCustomText, escapedRegexs); + validate.validateContinuousDelimiters( + textWithoutDefinitions, + escapedRegexs, + ); - const parsedNumber = this.parser.parseData( - parsingCustomText, + const numbers = this.parser.parseToNumbers( + textWithoutDefinitions, escapedRegexs, ); - validate.validateNumbers(parsedNumber); - const numbers = new Number(parsedNumber); - await outputView.printMessage(`결과 : ${numbers.getAddedNumbers()}`); + validate.validateNumbers(numbers); + const numberModel = new Number(numbers); + await outputView.printMessage(`결과 : ${numberModel.getAddedNumbers()}`); } catch (error) { await outputView.printMessage(error.message); throw new Error(error.message); diff --git a/src/service/model/Parser.js b/src/service/model/Parser.js index ccfa35c5..a46a43c6 100644 --- a/src/service/model/Parser.js +++ b/src/service/model/Parser.js @@ -1,25 +1,27 @@ import { RegexUtils } from '../../constant/regex.js'; +const DEFAULT_DELIMITERS = [',', ':']; + export default class Parser { - parseData(inputText, escapedRegexs) { - const totalRegexs = [...escapedRegexs, ',', ':']; - const regex = RegexUtils.createSplitPattern(totalRegexs); - return inputText - .split(regex) - .filter((item) => item !== '') // 빈문자열 제거 - .map((item) => Number(item)); + parseToNumbers(text, escapedDelimiters) { + const allDelimiters = [...escapedDelimiters, ...DEFAULT_DELIMITERS]; + const splitPattern = RegexUtils.createSplitPattern(allDelimiters); + return text + .split(splitPattern) + .filter((value) => value !== '') + .map((value) => Number(value)); } - parseCustomRegex(inputText, regexs) { - let inputResult = inputText; + removeCustomDelimiterDefinitions(text, delimiters) { + let result = text; - regexs.forEach((regex) => { - const splitType = `//${regex}\\n`; - if (inputResult.startsWith(splitType)) { - inputResult = inputResult.slice(splitType.length); + delimiters.forEach((delimiter) => { + const definition = RegexUtils.createCusomPattern(delimiter); + if (result.startsWith(definition)) { + result = result.slice(definition.length); } }); - return inputResult; + return result; } } diff --git a/test/Parser.test.js b/test/Parser.test.js index 8c9ecb9a..1f796ded 100644 --- a/test/Parser.test.js +++ b/test/Parser.test.js @@ -3,42 +3,40 @@ import Parser from '../src/service/model/Parser.js'; describe('Parser 클래스를 테스트 하다', () => { const parser = new Parser(); it('구분자와 입력을 받아 파싱을 하다', () => { - const regxs = ['/', '|']; - // regexs 테스트시 이스케이프 처리를 해주고 넘겨야함 - const inputText = '2/123/2|31'; - expect(parser.parseData(regxs, inputText)).toEqual([2, 123, 2, 31]); + const escapedDelimiters = ['\\/', '\\|']; + const text = '2/123/2|31'; + expect(parser.parseToNumbers(text, escapedDelimiters)).toEqual([ + 2, 123, 2, 31, + ]); }); }); -// issue 객체나 배열은 toEqual을 사용해야함... - describe('Parser 클래스의 useCase를 추가하다', () => { const parser = new Parser(); it('구분자는 기본적으로 , : 를 포함합니다', () => { - const regxs = []; - const inputText = '11:6,1'; - expect(parser.parseData(regxs, inputText)).toEqual([11, 6, 1]); + const escapedDelimiters = []; + const text = '11:6,1'; + expect(parser.parseToNumbers(text, escapedDelimiters)).toEqual([11, 6, 1]); }); it('구분자가 여러글자 인경우', () => { - const regxs = ['ab', 'bc', 'cd']; - const inputText = '1ab2bc2cd3'; - expect(parser.parseData(regxs, inputText)).toEqual([1, 2, 2, 3]); + const escapedDelimiters = ['ab', 'bc', 'cd']; + const text = '1ab2bc2cd3'; + expect(parser.parseToNumbers(text, escapedDelimiters)).toEqual([ + 1, 2, 2, 3, + ]); }); }); -describe('파싱 - 커스텀 구분자', () => { +describe('파싱 - 커스텀 구분자 정의 제거', () => { const parser = new Parser(); - it('커스텀 구분자가 여러개 적히는 경우', () => { - const regxs = [';', '|']; - const inputText = '//;\\n//|\\n1'; - expect(parser.parseCustomRegex(regxs, inputText)).toEqual('1'); + it('커스텀 구분자 정의가 여러개 있는 경우', () => { + const delimiters = [';', '|']; + const text = '//;\\n//|\\n1'; + expect(parser.removeCustomDelimiterDefinitions(text, delimiters)).toEqual( + '1', + ); }); }); - -// issue \ 처리가 매우 까다로움 -// \\ 의 경우에는 \\\\ 로 처리해야한다 -// js에서 \는 \\로 작성해야하므로 -// 따라서 inputText의 \\는 \\\\로 처리해야하는것 diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index 1523ce33..a53b0d66 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -26,7 +26,7 @@ describe(`랜덤 테스트 ${TOTAL_ITERATIONS}`, () => { const controller = new Controller(); const logSpy = jest.spyOn(Console, 'print').mockImplementation(() => {}); - const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseData'); + const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseToNumbers'); const extractionValueSpy = jest.spyOn( Extraction.prototype, 'extractCustom', From 3f482f9d4dd293845d8a8416fdd6daf1635a3559 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 22:08:47 +0900 Subject: [PATCH 62/65] =?UTF-8?q?refactor:=20Extraction=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 13 +++----- src/service/model/Extraction.js | 16 +++++----- test/Extraction.test.js | 54 +++++++++++++++++++++------------ test/RandomMaker.test.js | 5 ++- 4 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 91259a8c..8c6b996b 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -16,21 +16,18 @@ export default class Controller { const input = await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); - const { regexs, escapedRegexs } = this.extraction.extractCustom(input); + const { raw, escaped } = this.extraction.extractCustomDelimiters(input); - validate.validateDelimiters(regexs); + validate.validateDelimiters(raw); const textWithoutDefinitions = - this.parser.removeCustomDelimiterDefinitions(input, regexs); + this.parser.removeCustomDelimiterDefinitions(input, raw); - validate.validateContinuousDelimiters( - textWithoutDefinitions, - escapedRegexs, - ); + validate.validateContinuousDelimiters(textWithoutDefinitions, escaped); const numbers = this.parser.parseToNumbers( textWithoutDefinitions, - escapedRegexs, + escaped, ); validate.validateNumbers(numbers); const numberModel = new Number(numbers); diff --git a/src/service/model/Extraction.js b/src/service/model/Extraction.js index ad8796c7..853ed04a 100644 --- a/src/service/model/Extraction.js +++ b/src/service/model/Extraction.js @@ -1,18 +1,20 @@ import { REGEX_PATTERNS, RegexUtils } from '../../constant/regex.js'; export default class Extraction { - extractCustom(inputText) { - const matches = Array.from( - inputText.matchAll(REGEX_PATTERNS.CUSTOM_DELIMITER_EXTRACT), + extractCustomDelimiters(text) { + const delimiters = Array.from( + text.matchAll(REGEX_PATTERNS.CUSTOM_DELIMITER_EXTRACT), (match) => match[1], ); return { - regexs: matches, // 원본 - escapedRegexs: this.#escapeRegex(matches), // 이스케이프 (사용용) + raw: delimiters, + escaped: this.#escapeDelimiters(delimiters), }; } - #escapeRegex(regexs) { - return regexs.map((item) => RegexUtils.escapeRegexChars(item)); + #escapeDelimiters(delimiters) { + return delimiters.map((delimiter) => + RegexUtils.escapeRegexChars(delimiter), + ); } } diff --git a/test/Extraction.test.js b/test/Extraction.test.js index ab050fc1..e4ab1731 100644 --- a/test/Extraction.test.js +++ b/test/Extraction.test.js @@ -1,33 +1,47 @@ -// 추출기 테스트 - import Extraction from '../src/service/model/Extraction.js'; describe('Extraction 클래스 테스트', () => { + const extraction = new Extraction(); + it('//ㅌ\\n 사이의 커스텀 구분자 ㅌ을 추출한다', () => { - const inputText = 'e//ㅌ\\neasd123'; - const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual(['ㅌ']); + const text = 'e//ㅌ\\neasd123'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual(['ㅌ']); + expect(result.escaped).toEqual(['ㅌ']); }); - it('커스텀 구분자가 없다면 빈값으로 반환된다', () => { - const inputText = 'eneasd123'; - const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual([]); + + it('커스텀 구분자가 없다면 빈 배열로 반환된다', () => { + const text = 'eneasd123'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual([]); + expect(result.escaped).toEqual([]); }); + it('커스텀 구분자 한개를 테스트하다 //;\\n1', () => { - const inputText = '//;\\n1'; - const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual([';']); + const text = '//;\\n1'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual([';']); + expect(result.escaped).toEqual([';']); + }); + + it('커스텀 구분자 두개를 테스트하다 //;\\n1,//ㅁ\\n1', () => { + const text = '//;\\n1,//ㅁ\\n1'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual([';', 'ㅁ']); + expect(result.escaped).toEqual([';', 'ㅁ']); }); - it('커스텀 구분자 두개를 테스트하다 //;\\n1', () => { - const inputText = '//;\\n1,//ㅁ\\n1'; - const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual([';', 'ㅁ']); + it('커스텀 구분자가 특수문자인 경우 이스케이프 처리된다', () => { + const text = '//.\\n1'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual(['.']); + expect(result.escaped).toEqual(['\\.']); }); - it('커스텀 구분자가 \\인경우', () => { - const inputText = '//\\\\n'; - const extraction = new Extraction(); - expect(extraction.extractCustom(inputText)).toEqual(['\\\\']); + it('여러 특수문자를 포함한 경우 이스케이프 처리된다', () => { + const text = '//*\\n1//+\\n2'; + const result = extraction.extractCustomDelimiters(text); + expect(result.raw).toEqual(['*', '+']); + expect(result.escaped).toEqual(['\\*', '\\+']); }); }); diff --git a/test/RandomMaker.test.js b/test/RandomMaker.test.js index a53b0d66..3ad9ee57 100644 --- a/test/RandomMaker.test.js +++ b/test/RandomMaker.test.js @@ -29,7 +29,7 @@ describe(`랜덤 테스트 ${TOTAL_ITERATIONS}`, () => { const parsedValueSpy = jest.spyOn(Parser.prototype, 'parseToNumbers'); const extractionValueSpy = jest.spyOn( Extraction.prototype, - 'extractCustom', + 'extractCustomDelimiters', ); try { @@ -38,7 +38,6 @@ describe(`랜덤 테스트 ${TOTAL_ITERATIONS}`, () => { const extractionSpyValue = extractionValueSpy.mock.results[0].value; const parsedSpyValue = parsedValueSpy.mock.results[0].value; - // 진행 상황 표시 (100번마다) if (i % 100 === 0) { console.log(`진행 중: ${i}/${TOTAL_ITERATIONS} `); } @@ -46,7 +45,7 @@ describe(`랜덤 테스트 ${TOTAL_ITERATIONS}`, () => { compareArrays(parseMumber, parsedSpyValue); expect(new Set(extracionRegex)).toEqual( - new Set(extractionSpyValue.regexs), + new Set(extractionSpyValue.raw), ); expect(parseMumber).toEqual(parsedSpyValue); expect(logSpy).toHaveBeenCalledWith(`결과 : ${output}`); From 30972ee08c8c3fe9db2d750f42db997258b8befb Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 22:10:32 +0900 Subject: [PATCH 63/65] =?UTF-8?q?style:=20=EC=A3=BC=EC=84=9D=EC=9D=84=20?= =?UTF-8?q?=EC=A0=81=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/Controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/Controller.js b/src/controller/Controller.js index 8c6b996b..d4663211 100644 --- a/src/controller/Controller.js +++ b/src/controller/Controller.js @@ -16,13 +16,12 @@ export default class Controller { const input = await inputView.readLineMessage('덧셈할 문자열을 입력해 주세요.'); + // 구분자 추출 {raw:원본,escaped:정규식 이스케이프} const { raw, escaped } = this.extraction.extractCustomDelimiters(input); - validate.validateDelimiters(raw); const textWithoutDefinitions = this.parser.removeCustomDelimiterDefinitions(input, raw); - validate.validateContinuousDelimiters(textWithoutDefinitions, escaped); const numbers = this.parser.parseToNumbers( @@ -30,6 +29,7 @@ export default class Controller { escaped, ); validate.validateNumbers(numbers); + const numberModel = new Number(numbers); await outputView.printMessage(`결과 : ${numberModel.getAddedNumbers()}`); } catch (error) { From 11a2cee370adbed6eec772ba31793e28d08d5714 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 23:15:49 +0900 Subject: [PATCH 64/65] =?UTF-8?q?feat:=20=EC=86=8C=EC=88=98=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- src/service/validate/validate.js | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6eed678b..10bad526 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ ### 5. 추가사항 -- 연속된 구분자의 입력을 허용할것인가 -- 공백 입력을 허용할것인가 -- 소수점을 허용할것인가 +- 연속된 구분자의 입력을 허용할것인가 - 미허용 +- 공백 입력을 허용할것인가 - 미허용 +- 소수점을 허용할것인가 - 미허용 -> 소수점이 포함된경우 커스텀 구분자로 . 이 오면 허용할것인가 -- 커스텀 구분자 여러개를 허용할것 인가 ? -- 커스텀 구분자가 중간에 나오는것을 허용할것인가? +- 커스텀 구분자 여러개를 허용할것 인가 ? - 허용 +- 커스텀 구분자가 중간에 나오는것을 허용할것인가? - 미 허용 - 커스텀 구분자가 . 일때 .. 처리를 어케할것인지 diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 6aa28147..7acb0ad8 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -18,6 +18,10 @@ const validate = { if (number < 0) { throw new Error(ERROR_MESSAGE.NOT_MINUS); } + // 소수 체크 + if (!Number.isInteger(Number(number))) { + throw new Error(ERROR_MESSAGE.NOT_INTEGER); + } }); }, From 6c44eacc787e78e1b3421abc0d306a9df334c82a Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 19 Oct 2025 23:17:53 +0900 Subject: [PATCH 65/65] =?UTF-8?q?feat:=20=EA=B5=AC=EB=B6=84=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=ED=95=9C=EA=B8=80=EC=9E=90=EB=A7=8C=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9=ED=95=A0=EA=B2=83=EC=9D=B8=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/constant/error.js | 2 ++ src/service/validate/validate.js | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 10bad526..76f9d6f9 100644 --- a/README.md +++ b/README.md @@ -54,4 +54,4 @@ -> 소수점이 포함된경우 커스텀 구분자로 . 이 오면 허용할것인가 - 커스텀 구분자 여러개를 허용할것 인가 ? - 허용 - 커스텀 구분자가 중간에 나오는것을 허용할것인가? - 미 허용 -- 커스텀 구분자가 . 일때 .. 처리를 어케할것인지 +- 커스텀 구분자가 . 일때 .. 처리를 어케할것인지 - 미 허용 구분자는 한글자 diff --git a/src/constant/error.js b/src/constant/error.js index eaf4cf26..a78a633d 100644 --- a/src/constant/error.js +++ b/src/constant/error.js @@ -6,4 +6,6 @@ export const ERROR_MESSAGE = { DELIMITER_EMPTY: '[ERROR] 구분자가 공백입니다.', DELIMITER_HAS_WHITESPACE: '[ERROR] 구분자에 공백이 포함되어 있습니다.', DELIMITER_HAS_NUMBER: '[ERROR] 구분자에 숫자가 포함되어 있습니다.', + DELIMITER_NOT_SINGLE_CHAR: '[ERROR] 구분자는 한 글자여야 합니다.', + NOT_INTEGER: '[ERROR] 소수가 포함되어 있습니다.', }; diff --git a/src/service/validate/validate.js b/src/service/validate/validate.js index 7acb0ad8..26f34d49 100644 --- a/src/service/validate/validate.js +++ b/src/service/validate/validate.js @@ -58,6 +58,10 @@ const validate = { if (REGEX_PATTERNS.DIGIT.test(delimiter)) { throw new Error(ERROR_MESSAGE.DELIMITER_HAS_NUMBER); } + // 구분자가 한글자인지 체크 + if (delimiter.length !== 1) { + throw new Error(ERROR_MESSAGE.DELIMITER_NOT_SINGLE_CHAR); + } }); }, };