├── .gitignore
├── LICENSE
├── README.md
├── babel.config.json
├── dist
├── mission-utils.cjs
└── mission-utils.js
├── package-lock.json
├── package.json
├── src
├── console.js
├── index.js
└── random.js
├── test
├── console.test.js
└── random.test.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 woowacourse-projects
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mission-utils
2 |
3 | 
4 | [](https://github.com/facebook/jest)
5 |
6 | ## Install
7 |
8 | ### with CDN
9 |
10 | 1. 스크립트 삽입하기
11 |
12 | ```html
13 |
14 |
15 | ```
16 |
17 | - `index.html`에 해당 스크립트 태그를 삽입해주세요.
18 | - 유틸 라이브러리의 경우 어플리케이션 스크립트 이전에 작성해야 합니다.
19 |
20 | 2. 유틸 사용하기
21 |
22 | ```js
23 | // example
24 | console.log(MissionUtils.Random.pickNumberInList([1, 2, 3]));
25 | ```
26 |
27 | - 스크립트 태그로 삽입된 경우 전역에 할당되어 `MissionUtils.[util]` 형태로 사용할 수 있습니다.
28 |
29 | ### with npm
30 |
31 | 1. 모듈 다운로드
32 |
33 | ```sh
34 | npm i @woowacourse/mission-utils
35 | ```
36 |
37 | 2. 모듈 사용하기
38 |
39 | #### ES Modules 방식으로 사용하는 경우
40 |
41 | ```js
42 | import { MissionUtils } from "@woowacourse/mission-utils";
43 |
44 | console.log(MissionUtils.Random.pickNumberInList([1, 2, 3]));
45 | ```
46 |
47 | #### CommonJS 방식으로 사용하는 경우
48 |
49 | ```js
50 | const MissionUtils = require("@woowacourse/mission-utils");
51 |
52 | console.log(MissionUtils.Random.pickNumberInList([1, 2, 3]));
53 | ```
54 |
55 | ## Features
56 |
57 | ### Console
58 |
59 | #### `readLine(query, callback)`
60 |
61 | 주어진 질문을 화면에 출력하고, 사용자가 답변을 입력할 때까지 기다린 다음 입력된 답변을 인수로 전달하는 콜백 함수를 호출한다.
62 |
63 | ```js
64 | Console.readLine('닉네임을 입력해주세요.', (answer) => {
65 | console.log(`닉네임: ${answer}`);
66 | });
67 | ```
68 |
69 | #### `readLineAsync(query)`
70 |
71 | 주어진 질문을 화면에 출력하고, 사용자가 입력한 답변을 Promise를 통해 반환한다.
72 |
73 | ```js
74 | async function getUsername() {
75 | try {
76 | const username = await Console.readLineAsync('닉네임을 입력해주세요.');
77 | } catch (error) {
78 | // reject 되는 경우
79 | }
80 | }
81 | ```
82 |
83 | #### `print(message)`
84 |
85 | 주어진 문자열을 콘솔에 출력한다.
86 |
87 | ```js
88 | Console.print('안녕하세요.');
89 | ```
90 |
91 | ### Random
92 |
93 | #### `pickNumberInRange(startInclusive, endInclusive)`
94 |
95 | 숫자 범위를 지정하면 시작 또는 끝 숫자를 포함하여 범위의 숫자를 반환한다.
96 |
97 | ```js
98 | Random.pickNumberInRange(1, 10); // 1
99 | Random.pickNumberInRange(1, 10); // 10
100 | Random.pickNumberInRange(1, 10); // 4
101 | Random.pickNumberInRange(1, 10); // 5
102 | ```
103 |
104 | #### `pickNumberInList(array)`
105 |
106 | 목록에 있는 숫자 중 하나를 반환한다.
107 |
108 | ```js
109 | Random.pickNumberInList([1, 3, 10]); // 1
110 | Random.pickNumberInList([1, 3, 10]); // 10
111 | Random.pickNumberInList([1, 3, 10]); // 3
112 | ```
113 |
114 | #### `pickUniqueNumbersInRange(startInclusive, endInclusive, count)`
115 |
116 | 숫자 범위 내에서 지정된 개수만큼 겹치지 않는 숫자를 반환한다.
117 |
118 | ```js
119 | Random.pickUniqueNumbersInRange(1, 10, 2); // [1, 2]
120 | Random.pickUniqueNumbersInRange(1, 10, 5); // [1, 10, 7, 8, 5]
121 | ```
122 |
123 | #### `shuffle(array)`
124 |
125 | 무작위로 섞인 새 목록을 반환한다.
126 |
127 | ```js
128 | Random.shuffle([1, 2, 3, 4, 5]); // [2, 4, 1, 3, 5]
129 | ```
130 |
131 | ## Contributors
132 |
133 | [
](https://github.com/2SOOY)
134 | [
](https://github.com/zereight)
135 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/dist/mission-utils.cjs:
--------------------------------------------------------------------------------
1 | (()=>{"use strict";var r,e={d:(r,t)=>{for(var n in t)e.o(t,n)&&!e.o(r,n)&&Object.defineProperty(r,n,{enumerable:!0,get:t[n]})},o:(r,e)=>Object.prototype.hasOwnProperty.call(r,e),r:r=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})}},t={};function n(r){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},n(r)}function o(r,e){for(var t=0;tp,MissionUtils:()=>v,Random:()=>s});var u=function(){function r(){!function(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}(this,r)}var e,t;return e=r,t=[{key:"pickNumberInRange",value:function(e,t){return i(r,r,c).call(r,e,t),e=Math.ceil(e),Math.floor(Math.random()*(t+1-e))+e}},{key:"pickNumberInList",value:function(e){return i(r,r,l).call(r,e),e[r.pickNumberInRange(0,e.length-1)]}},{key:"pickUniqueNumbersInRange",value:function(e,t,n){i(r,r,f).call(r,e,t,n);for(var o=[],u=e;u<=t;u++)o.push(u);return r.shuffle(o).slice(0,n)}},{key:"shuffle",value:function(e){return i(r,r,l).call(r,e),e.sort((function(){return Math.random()-.5}))}}],null&&o(e.prototype,null),t&&o(e,t),Object.defineProperty(e,"prototype",{writable:!1}),r}();function a(r){return"number"==typeof r}function c(e,t){if(!i(r,r,a).call(r,e)||!i(r,r,a).call(r,t))throw new Error("arguments must be numbers.");if(eNumber.MAX_SAFE_INTEGER)throw new Error("endInclusive cannot be greater than Number.MAX_SAFE_INTEGER.");if(e>t)throw new Error("startInclusive ".concat(e," cannot be greater than endInclusive ").concat(t,"."));if(t-e>=Number.MAX_VALUE)throw new Error("the input range is too large.")}function l(e){if(!Array.isArray(e))throw new Error("the argument must be an array.");if(!e.every((function(e){return i(r,r,a).call(r,e)})))throw new Error("array elements must be numbers.");if(0===e.length)throw new Error("argument array cannot be empty.")}function f(e,t,n){if(!i(r,r,a).call(r,e)||!i(r,r,a).call(r,t)||!i(r,r,a).call(r,n))throw new Error("arguments must be numbers.");if(n<0)throw new Error("count cannot be less than zero.");if(t-e+1{for(var n in e)t.o(e,n)&&!t.o(r,n)&&Object.defineProperty(r,n,{enumerable:!0,get:e[n]})},o:(r,e)=>Object.prototype.hasOwnProperty.call(r,e)},n={};function o(r){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},o(r)}function i(r,e){for(var t=0;tv,HN:()=>w,kk:()=>m});var a=function(){function r(){!function(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}(this,r)}var e,t;return e=r,t=[{key:"pickNumberInRange",value:function(e,t){return u(r,r,l).call(r,e,t),e=Math.ceil(e),Math.floor(Math.random()*(t+1-e))+e}},{key:"pickNumberInList",value:function(e){return u(r,r,f).call(r,e),e[r.pickNumberInRange(0,e.length-1)]}},{key:"pickUniqueNumbersInRange",value:function(e,t,n){u(r,r,s).call(r,e,t,n);for(var o=[],i=e;i<=t;i++)o.push(i);return r.shuffle(o).slice(0,n)}},{key:"shuffle",value:function(e){return u(r,r,f).call(r,e),e.sort((function(){return Math.random()-.5}))}}],null&&i(e.prototype,null),t&&i(e,t),Object.defineProperty(e,"prototype",{writable:!1}),r}();function c(r){return"number"==typeof r}function l(r,t){if(!u(e,e,c).call(e,r)||!u(e,e,c).call(e,t))throw new Error("arguments must be numbers.");if(rNumber.MAX_SAFE_INTEGER)throw new Error("endInclusive cannot be greater than Number.MAX_SAFE_INTEGER.");if(r>t)throw new Error("startInclusive ".concat(r," cannot be greater than endInclusive ").concat(t,"."));if(t-r>=Number.MAX_VALUE)throw new Error("the input range is too large.")}function f(r){if(!Array.isArray(r))throw new Error("the argument must be an array.");if(!r.every((function(r){return u(e,e,c).call(e,r)})))throw new Error("array elements must be numbers.");if(0===r.length)throw new Error("argument array cannot be empty.")}function s(r,t,n){if(!u(e,e,c).call(e,r)||!u(e,e,c).call(e,t)||!u(e,e,c).call(e,n))throw new Error("arguments must be numbers.");if(n<0)throw new Error("count cannot be less than zero.");if(t-r+1=9.6.7",
40 | "node": ">=18.17.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/console.js:
--------------------------------------------------------------------------------
1 | import readline from "readline";
2 |
3 | class Console {
4 | constructor() {}
5 |
6 | static readLine(query, callback) {
7 | if (arguments.length !== 2) {
8 | throw new Error("arguments must be 2.");
9 | }
10 |
11 | if (typeof query !== "string") {
12 | throw new Error("query must be string");
13 | }
14 |
15 | if (typeof callback !== "function") {
16 | throw new Error("callback must be function");
17 | }
18 |
19 | if (callback.length !== 1) {
20 | throw new Error("callback must have 1 argument");
21 | }
22 |
23 | const rl = readline.createInterface({
24 | input: process.stdin,
25 | output: process.stdout,
26 | });
27 |
28 | rl.question(query, callback);
29 | }
30 |
31 | static readLineAsync(query) {
32 | return new Promise((resolve, reject) => {
33 | if (arguments.length !== 1) {
34 | reject(new Error("arguments must be 1"));
35 | }
36 |
37 | if (typeof query !== "string") {
38 | reject(new Error("query must be string"));
39 | }
40 |
41 | const rl = readline.createInterface({
42 | input: process.stdin,
43 | output: process.stdout,
44 | });
45 |
46 | rl.question(query, (input) => {
47 | rl.close();
48 | resolve(input);
49 | });
50 | });
51 | }
52 |
53 | static print(message) {
54 | console.log(message);
55 | }
56 | }
57 |
58 | export default Console;
59 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Random from "./random.js";
2 | import Console from "./console.js";
3 |
4 | export { Random, Console };
5 |
6 | export const MissionUtils = {
7 | Random,
8 | Console,
9 | };
10 |
--------------------------------------------------------------------------------
/src/random.js:
--------------------------------------------------------------------------------
1 | class Random {
2 | constructor() {}
3 |
4 | static pickNumberInRange(startInclusive, endInclusive) {
5 | Random.#validateRange(startInclusive, endInclusive);
6 |
7 | startInclusive = Math.ceil(startInclusive);
8 |
9 | return (
10 | Math.floor(Math.random() * (endInclusive + 1 - startInclusive)) +
11 | startInclusive
12 | );
13 | }
14 |
15 | static #isNumber(value) {
16 | return typeof value === "number";
17 | }
18 |
19 | static #validateRange(startInclusive, endInclusive) {
20 | if (!Random.#isNumber(startInclusive) || !Random.#isNumber(endInclusive)) {
21 | throw new Error("arguments must be numbers.");
22 | }
23 |
24 | if (startInclusive < Number.MIN_SAFE_INTEGER) {
25 | throw new Error(
26 | "startInclusive cannot be less than Number.MIN_SAFE_INTEGER"
27 | );
28 | }
29 |
30 | if (endInclusive > Number.MAX_SAFE_INTEGER) {
31 | throw new Error(
32 | "endInclusive cannot be greater than Number.MAX_SAFE_INTEGER."
33 | );
34 | }
35 |
36 | if (startInclusive > endInclusive) {
37 | throw new Error(
38 | `startInclusive ${startInclusive} cannot be greater than endInclusive ${endInclusive}.`
39 | );
40 | }
41 |
42 | if (endInclusive - startInclusive >= Number.MAX_VALUE) {
43 | throw new Error("the input range is too large.");
44 | }
45 | }
46 |
47 | static pickNumberInList(array) {
48 | Random.#validateEmptyArray(array);
49 |
50 | return array[Random.pickNumberInRange(0, array.length - 1)];
51 | }
52 |
53 | static #validateEmptyArray(array) {
54 | if (!Array.isArray(array)) {
55 | throw new Error("the argument must be an array.");
56 | }
57 |
58 | if (!array.every((v) => Random.#isNumber(v))) {
59 | throw new Error("array elements must be numbers.");
60 | }
61 |
62 | if (array.length === 0) {
63 | throw new Error("argument array cannot be empty.");
64 | }
65 | }
66 |
67 | static pickUniqueNumbersInRange(startInclusive, endInclusive, count) {
68 | Random.#validateIntsRange(startInclusive, endInclusive, count);
69 |
70 | const result = [];
71 |
72 | for (let i = startInclusive; i <= endInclusive; i++) {
73 | result.push(i);
74 | }
75 |
76 | return Random.shuffle(result).slice(0, count);
77 | }
78 |
79 | static #validateIntsRange(startInclusive, endInclusive, count) {
80 | if (
81 | !Random.#isNumber(startInclusive) ||
82 | !Random.#isNumber(endInclusive) ||
83 | !Random.#isNumber(count)
84 | ) {
85 | throw new Error("arguments must be numbers.");
86 | }
87 |
88 | if (count < 0) {
89 | throw new Error("count cannot be less than zero.");
90 | }
91 |
92 | if (endInclusive - startInclusive + 1 < count) {
93 | throw new Error(
94 | `count: ${count} cannot be greater than the input range (endInclusive - startInclusive): ${
95 | endInclusive - startInclusive
96 | }.`
97 | );
98 | }
99 | }
100 |
101 | static shuffle(array) {
102 | Random.#validateEmptyArray(array);
103 |
104 | return array.sort(() => Math.random() - 0.5);
105 | }
106 | }
107 |
108 | export default Random;
109 |
--------------------------------------------------------------------------------
/test/console.test.js:
--------------------------------------------------------------------------------
1 | import readline from "readline";
2 | import * as MissionUtils from "../src";
3 |
4 | describe('Console.print', () => {
5 | test('주어진 메시지를 콘솔에 출력해야 한다.', () => {
6 | // given
7 | const message = 'test';
8 | const logSpy = jest.spyOn(console, "log");
9 |
10 | // when
11 | MissionUtils.Console.print(message)
12 |
13 | // then
14 | expect(logSpy).toHaveBeenCalledWith(message);
15 | });
16 | });
17 |
18 | describe('Console.readLine', () => {
19 | const query = "test";
20 | const callback = jest.fn();
21 |
22 | test('인자가 2개보다 적게 주어진 경우 예외가 발생해야 한다.', () => {
23 | // given
24 | // when
25 | // then
26 | expect(() => {
27 | MissionUtils.Console.readLine(query);
28 | }).toThrow();
29 | });
30 |
31 | test('인자가 2개보다 많이 주어진 경우 예외가 발생해야 한다.', () => {
32 | // given
33 | // when
34 | // then
35 | expect(() => {
36 | MissionUtils.Console.readLine(query, callback, 1);
37 | }).toThrow();
38 | });
39 |
40 | test('query가 문자열이 아닌 경우 예외가 발생해야 한다.', () => {
41 | // given
42 | const invalidQuery = 1;
43 |
44 | // when
45 | // then
46 | expect(() => {
47 | MissionUtils.Console.readLine(invalidQuery, callback);
48 | }).toThrow();
49 | });
50 |
51 | test('callback이 함수가 아닌 경우 예외가 발생해야 한다.', () => {
52 | // given
53 | const invalidCallback = "callback";
54 |
55 | // when
56 | // then
57 | expect(() => {
58 | MissionUtils.Console.readLine(query, invalidCallback);
59 | }).toThrow();
60 | });
61 |
62 | test('callback에 인자가 1개가 아닌 경우 예외가 발생해야 한다.', () => {
63 | // given
64 | const invalidCallback = (a, b) => {};
65 |
66 | // when
67 | // then
68 | expect(() => {
69 | MissionUtils.Console.readLine(query, invalidCallback);
70 | }).toThrow();
71 | });
72 | });
73 |
74 | describe('Console.readLineAsync', () => {
75 | const query = "test";
76 |
77 | test('인자가 주어지지 않은 경우 예외가 발생해야 한다.', async () => {
78 | // given
79 | // when
80 | // then
81 | await expect(MissionUtils.Console.readLineAsync()).rejects.toThrow();
82 | });
83 |
84 | test('인자가 1개보다 많이 주어진 경우 예외가 발생해야 한다.', async () => {
85 | // given
86 | // when
87 | // then
88 | await expect(MissionUtils.Console.readLineAsync(query, 1)).rejects.toThrow();
89 | });
90 |
91 | test('query가 문자열이 아닌 경우 예외가 발생해야 한다.', async () => {
92 | // given
93 | const invalidQuery = 1;
94 |
95 | // when
96 | // then
97 | await expect(MissionUtils.Console.readLineAsync(invalidQuery)).rejects.toThrow();
98 | });
99 |
100 | test('사용자가 입력한 값을 반환해야 한다.', async () => {
101 | // given
102 | const userInput = "user input";
103 | const createInterfaceMock = jest.spyOn(readline, "createInterface");
104 | const readlineMock = {
105 | question: jest.fn((query, callback) => {
106 | callback(userInput);
107 | }),
108 | close: jest.fn(),
109 | };
110 | createInterfaceMock.mockReturnValue(readlineMock);
111 |
112 | // when
113 | const result = await MissionUtils.Console.readLineAsync(query);
114 |
115 | // then
116 | expect(result).toBe(userInput);
117 | expect(readlineMock.question).toHaveBeenCalledWith(query, expect.any(Function));
118 | expect(readlineMock.close).toHaveBeenCalled();
119 |
120 | createInterfaceMock.mockRestore();
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/test/random.test.js:
--------------------------------------------------------------------------------
1 | import * as MissionUtils from "../src";
2 |
3 | expect.extend({
4 | toBeDistinct(received) {
5 | const pass =
6 | Array.isArray(received) && new Set(received).size === received.length;
7 | if (pass) {
8 | return {
9 | message: () => `expected [${received}] array is unique`,
10 | pass: true,
11 | };
12 | } else {
13 | return {
14 | message: () => `expected [${received}] array is not to unique`,
15 | pass: false,
16 | };
17 | }
18 | },
19 | });
20 |
21 | describe("Random.pickNumberInRange", () => {
22 | test("시작 또는 끝 숫자를 포함한 범위 내의 숫자를 반환해야 한다.", () => {
23 | // given
24 | const start = 1;
25 | const end = 5;
26 |
27 | // when
28 | const number = MissionUtils.Random.pickNumberInRange(start, end);
29 |
30 | // then
31 | expect(number).toBeGreaterThanOrEqual(start);
32 | expect(number).toBeLessThanOrEqual(end);
33 | });
34 |
35 | test("시작 숫자가 끝 숫자보다 크면 예외가 발생해야 한다.", () => {
36 | // given
37 | const start = 1;
38 | const end = start - 1;
39 |
40 | // when
41 | // then
42 | expect(() => {
43 | MissionUtils.Random.pickNumberInRange(start, end);
44 | }).toThrow();
45 | });
46 |
47 | test("시작 숫자가 Number.MIN_SAFE_INTEGER 작으면 예외가 발생해야 한다.", () => {
48 | // given
49 | const start = Number.MIN_SAFE_INTEGER - 1;
50 | const end = start + 10;
51 |
52 | // when
53 | // then
54 | expect(() => {
55 | MissionUtils.Random.pickNumberInRange(start, end);
56 | }).toThrow();
57 | });
58 |
59 | test("끝 숫자가 Number.MAX_SAFE_INTEGER보다 크면 예외가 발생해야 한다.", () => {
60 | // given
61 | const start = 1;
62 | const end = Number.MAX_SAFE_INTEGER + 1;
63 |
64 | // when
65 | // then
66 | expect(() => {
67 | MissionUtils.Random.pickNumberInRange(start, end);
68 | }).toThrow();
69 | });
70 | });
71 |
72 | describe("Random.pickNumberInList", () => {
73 | test("주어진 목록에 있는 숫자 중 하나를 반환해야 한다.", () => {
74 | // given
75 | const list = [1, 2, 3, 4, 5];
76 |
77 | // when
78 | const number = MissionUtils.Random.pickNumberInList(list);
79 |
80 | // then
81 | expect(list).toContain(number);
82 | });
83 |
84 | test("목록이 비어 있을 경우 예외가 발생해야 한다.", () => {
85 | // given
86 | const list = [];
87 |
88 | // when
89 | // then
90 | expect(() => {
91 | MissionUtils.Random.pickNumberInList(list);
92 | }).toThrow();
93 | });
94 |
95 | test("숫자로만 이루어진 목록이 아닌 경우 예외가 발생해야 한다.", () => {
96 | // given
97 | const list = [2, "text", 4];
98 |
99 | // when
100 | // then
101 | expect(() => {
102 | MissionUtils.Random.pickNumberInList(list);
103 | }).toThrow();
104 | });
105 |
106 | test("주어진 목록이 배열이 아닐 경우 예외가 발생해야 한다.", () => {
107 | // given
108 | // when
109 | // then
110 | expect(() => {
111 | MissionUtils.Random.pickNumberInList("");
112 | }).toThrow();
113 |
114 | expect(() => {
115 | MissionUtils.Random.pickNumberInList({});
116 | }).toThrow();
117 |
118 | expect(() => {
119 | MissionUtils.Random.pickNumberInList(1);
120 | }).toThrow();
121 |
122 | expect(() => {
123 | MissionUtils.Random.pickNumberInList();
124 | }).toThrow();
125 | });
126 | });
127 |
128 | describe("Random.pickUniqueNumbersInRange", () => {
129 | test("범위 내에서 지정된 개수만큼 겹치지 않는 숫자를 반환해야 한다.", () => {
130 | // given
131 | const start = 1;
132 | const end = 5;
133 | const count = 2;
134 |
135 | // when
136 | const numbers = MissionUtils.Random.pickUniqueNumbersInRange(
137 | start,
138 | end,
139 | count
140 | );
141 |
142 | // then
143 | expect(numbers).toHaveLength(count);
144 | numbers.forEach(
145 | (number) =>
146 | expect(number).toBeGreaterThanOrEqual(start) &&
147 | expect(number).toBeLessThanOrEqual(end)
148 | );
149 | expect(numbers).toBeDistinct();
150 | });
151 |
152 | test("개수가 음수이면 예외가 발생해야 한다.", () => {
153 | // given
154 | const start = 1;
155 | const end = 5;
156 | const count = -1;
157 |
158 | // when
159 | // then
160 | expect(() => {
161 | MissionUtils.Random.pickUniqueNumbersInRange(start, end, count);
162 | }).toThrow();
163 | });
164 |
165 | test("개수가 숫자 범위의 크기보다 크면 예외가 발생해야 한다.", () => {
166 | // given
167 | const start = 1;
168 | const end = 5;
169 | const count = 10;
170 |
171 | // when
172 | // then
173 | expect(() => {
174 | MissionUtils.Random.pickUniqueNumbersInRange(start, end, count);
175 | }).toThrow();
176 | });
177 |
178 | test("시작 숫자가 끝 숫자보다 크면 예외가 발생해야 한다.", () => {
179 | // given
180 | const start = 1;
181 | const end = start - 1;
182 |
183 | // when
184 | // then
185 | expect(() => {
186 | MissionUtils.Random.pickUniqueNumbersInRange(start, end);
187 | }).toThrow();
188 | });
189 |
190 | test("시작 숫자가 Number.MIN_SAFE_INTEGER 작으면 예외가 발생해야 한다.", () => {
191 | // given
192 | const start = Number.MIN_SAFE_INTEGER - 1;
193 | const end = start + 10;
194 |
195 | // when
196 | // then
197 | expect(() => {
198 | MissionUtils.Random.pickUniqueNumbersInRange(start, end);
199 | }).toThrow();
200 | });
201 |
202 | test("끝 숫자가 Number.MAX_SAFE_INTEGER보다 크면 예외가 발생해야 한다.", () => {
203 | // given
204 | const start = 1;
205 | const end = Number.MAX_SAFE_INTEGER + 1;
206 |
207 | // when
208 | // then
209 | expect(() => {
210 | MissionUtils.Random.pickUniqueNumbersInRange(start, end);
211 | }).toThrow();
212 | });
213 | });
214 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { fileURLToPath } from "url";
3 |
4 | const __filename = fileURLToPath(import.meta.url);
5 | const __dirname = path.dirname(__filename);
6 |
7 | const config = {
8 | entry: "./src/index.js",
9 | target: "node",
10 | resolve: {
11 | extensions: [".js"],
12 | },
13 | devtool: false,
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | exclude: [/node_modules/],
19 | use: {
20 | loader: "babel-loader",
21 | options: {
22 | presets: ["@babel/preset-env"]
23 | },
24 | },
25 | },
26 | ],
27 | },
28 | };
29 |
30 | export default [
31 | // CJS 빌드
32 | {
33 | ...config,
34 | output: {
35 | path: path.resolve(__dirname, "dist"),
36 | filename: "mission-utils.cjs",
37 | library: {
38 | type: "commonjs",
39 | },
40 | },
41 | },
42 | // ESM 빌드
43 | {
44 | ...config,
45 | output: {
46 | path: path.resolve(__dirname, "dist"),
47 | filename: "mission-utils.js",
48 | library: {
49 | type: "module",
50 | },
51 | chunkFormat: "module",
52 | },
53 | experiments: {
54 | outputModule: true, // ESM 출력 모듈 활성화
55 | },
56 | },
57 | ];
58 |
--------------------------------------------------------------------------------