@simplysm/core-common 13.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/.cache/typecheck-browser.tsbuildinfo +1 -0
  2. package/.cache/typecheck-node.tsbuildinfo +1 -0
  3. package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
  4. package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
  5. package/README.md +887 -0
  6. package/dist/common.types.d.ts +74 -0
  7. package/dist/common.types.d.ts.map +1 -0
  8. package/dist/common.types.js +5 -0
  9. package/dist/common.types.js.map +7 -0
  10. package/dist/env.d.ts +6 -0
  11. package/dist/env.d.ts.map +1 -0
  12. package/dist/env.js +9 -0
  13. package/dist/env.js.map +7 -0
  14. package/dist/errors/argument-error.d.ts +25 -0
  15. package/dist/errors/argument-error.d.ts.map +1 -0
  16. package/dist/errors/argument-error.js +18 -0
  17. package/dist/errors/argument-error.js.map +7 -0
  18. package/dist/errors/not-implemented-error.d.ts +29 -0
  19. package/dist/errors/not-implemented-error.d.ts.map +1 -0
  20. package/dist/errors/not-implemented-error.js +14 -0
  21. package/dist/errors/not-implemented-error.js.map +7 -0
  22. package/dist/errors/sd-error.d.ts +27 -0
  23. package/dist/errors/sd-error.d.ts.map +1 -0
  24. package/dist/errors/sd-error.js +23 -0
  25. package/dist/errors/sd-error.js.map +7 -0
  26. package/dist/errors/timeout-error.d.ts +31 -0
  27. package/dist/errors/timeout-error.d.ts.map +1 -0
  28. package/dist/errors/timeout-error.js +17 -0
  29. package/dist/errors/timeout-error.js.map +7 -0
  30. package/dist/extensions/arr-ext.d.ts +15 -0
  31. package/dist/extensions/arr-ext.d.ts.map +1 -0
  32. package/dist/extensions/arr-ext.helpers.d.ts +19 -0
  33. package/dist/extensions/arr-ext.helpers.d.ts.map +1 -0
  34. package/dist/extensions/arr-ext.helpers.js +35 -0
  35. package/dist/extensions/arr-ext.helpers.js.map +7 -0
  36. package/dist/extensions/arr-ext.js +546 -0
  37. package/dist/extensions/arr-ext.js.map +7 -0
  38. package/dist/extensions/arr-ext.types.d.ts +215 -0
  39. package/dist/extensions/arr-ext.types.d.ts.map +1 -0
  40. package/dist/extensions/arr-ext.types.js +1 -0
  41. package/dist/extensions/arr-ext.types.js.map +7 -0
  42. package/dist/extensions/map-ext.d.ts +57 -0
  43. package/dist/extensions/map-ext.d.ts.map +1 -0
  44. package/dist/extensions/map-ext.js +26 -0
  45. package/dist/extensions/map-ext.js.map +7 -0
  46. package/dist/extensions/set-ext.d.ts +36 -0
  47. package/dist/extensions/set-ext.d.ts.map +1 -0
  48. package/dist/extensions/set-ext.js +29 -0
  49. package/dist/extensions/set-ext.js.map +7 -0
  50. package/dist/features/debounce-queue.d.ts +53 -0
  51. package/dist/features/debounce-queue.d.ts.map +1 -0
  52. package/dist/features/debounce-queue.js +80 -0
  53. package/dist/features/debounce-queue.js.map +7 -0
  54. package/dist/features/event-emitter.d.ts +66 -0
  55. package/dist/features/event-emitter.d.ts.map +1 -0
  56. package/dist/features/event-emitter.js +82 -0
  57. package/dist/features/event-emitter.js.map +7 -0
  58. package/dist/features/serial-queue.d.ts +47 -0
  59. package/dist/features/serial-queue.d.ts.map +1 -0
  60. package/dist/features/serial-queue.js +66 -0
  61. package/dist/features/serial-queue.js.map +7 -0
  62. package/dist/globals.d.ts +12 -0
  63. package/dist/globals.d.ts.map +1 -0
  64. package/dist/globals.js +1 -0
  65. package/dist/globals.js.map +7 -0
  66. package/dist/index.d.ts +32 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +31 -0
  69. package/dist/index.js.map +7 -0
  70. package/dist/types/date-only.d.ts +152 -0
  71. package/dist/types/date-only.d.ts.map +1 -0
  72. package/dist/types/date-only.js +251 -0
  73. package/dist/types/date-only.js.map +7 -0
  74. package/dist/types/date-time.d.ts +96 -0
  75. package/dist/types/date-time.d.ts.map +1 -0
  76. package/dist/types/date-time.js +220 -0
  77. package/dist/types/date-time.js.map +7 -0
  78. package/dist/types/lazy-gc-map.d.ts +80 -0
  79. package/dist/types/lazy-gc-map.d.ts.map +1 -0
  80. package/dist/types/lazy-gc-map.js +179 -0
  81. package/dist/types/lazy-gc-map.js.map +7 -0
  82. package/dist/types/time.d.ts +68 -0
  83. package/dist/types/time.d.ts.map +1 -0
  84. package/dist/types/time.js +151 -0
  85. package/dist/types/time.js.map +7 -0
  86. package/dist/types/uuid.d.ts +35 -0
  87. package/dist/types/uuid.d.ts.map +1 -0
  88. package/dist/types/uuid.js +71 -0
  89. package/dist/types/uuid.js.map +7 -0
  90. package/dist/utils/bytes.d.ts +51 -0
  91. package/dist/utils/bytes.d.ts.map +1 -0
  92. package/dist/utils/bytes.js +89 -0
  93. package/dist/utils/bytes.js.map +7 -0
  94. package/dist/utils/date-format.d.ts +90 -0
  95. package/dist/utils/date-format.d.ts.map +1 -0
  96. package/dist/utils/date-format.js +106 -0
  97. package/dist/utils/date-format.js.map +7 -0
  98. package/dist/utils/json.d.ts +34 -0
  99. package/dist/utils/json.d.ts.map +1 -0
  100. package/dist/utils/json.js +152 -0
  101. package/dist/utils/json.js.map +7 -0
  102. package/dist/utils/num.d.ts +60 -0
  103. package/dist/utils/num.d.ts.map +1 -0
  104. package/dist/utils/num.js +39 -0
  105. package/dist/utils/num.js.map +7 -0
  106. package/dist/utils/obj.d.ts +258 -0
  107. package/dist/utils/obj.d.ts.map +1 -0
  108. package/dist/utils/obj.js +538 -0
  109. package/dist/utils/obj.js.map +7 -0
  110. package/dist/utils/path.d.ts +23 -0
  111. package/dist/utils/path.d.ts.map +1 -0
  112. package/dist/utils/path.js +21 -0
  113. package/dist/utils/path.js.map +7 -0
  114. package/dist/utils/primitive.d.ts +18 -0
  115. package/dist/utils/primitive.d.ts.map +1 -0
  116. package/dist/utils/primitive.js +20 -0
  117. package/dist/utils/primitive.js.map +7 -0
  118. package/dist/utils/str.d.ts +103 -0
  119. package/dist/utils/str.d.ts.map +1 -0
  120. package/dist/utils/str.js +128 -0
  121. package/dist/utils/str.js.map +7 -0
  122. package/dist/utils/template-strings.d.ts +84 -0
  123. package/dist/utils/template-strings.d.ts.map +1 -0
  124. package/dist/utils/template-strings.js +49 -0
  125. package/dist/utils/template-strings.js.map +7 -0
  126. package/dist/utils/transferable.d.ts +47 -0
  127. package/dist/utils/transferable.d.ts.map +1 -0
  128. package/dist/utils/transferable.js +153 -0
  129. package/dist/utils/transferable.js.map +7 -0
  130. package/dist/utils/wait.d.ts +19 -0
  131. package/dist/utils/wait.d.ts.map +1 -0
  132. package/dist/utils/wait.js +19 -0
  133. package/dist/utils/wait.js.map +7 -0
  134. package/dist/utils/xml.d.ts +36 -0
  135. package/dist/utils/xml.d.ts.map +1 -0
  136. package/dist/utils/xml.js +51 -0
  137. package/dist/utils/xml.js.map +7 -0
  138. package/dist/zip/sd-zip.d.ts +80 -0
  139. package/dist/zip/sd-zip.d.ts.map +1 -0
  140. package/dist/zip/sd-zip.js +153 -0
  141. package/dist/zip/sd-zip.js.map +7 -0
  142. package/package.json +31 -0
  143. package/src/common.types.ts +91 -0
  144. package/src/env.ts +11 -0
  145. package/src/errors/argument-error.ts +40 -0
  146. package/src/errors/not-implemented-error.ts +32 -0
  147. package/src/errors/sd-error.ts +53 -0
  148. package/src/errors/timeout-error.ts +36 -0
  149. package/src/extensions/arr-ext.helpers.ts +53 -0
  150. package/src/extensions/arr-ext.ts +777 -0
  151. package/src/extensions/arr-ext.types.ts +258 -0
  152. package/src/extensions/map-ext.ts +86 -0
  153. package/src/extensions/set-ext.ts +68 -0
  154. package/src/features/debounce-queue.ts +116 -0
  155. package/src/features/event-emitter.ts +112 -0
  156. package/src/features/serial-queue.ts +94 -0
  157. package/src/globals.ts +12 -0
  158. package/src/index.ts +55 -0
  159. package/src/types/date-only.ts +329 -0
  160. package/src/types/date-time.ts +294 -0
  161. package/src/types/lazy-gc-map.ts +244 -0
  162. package/src/types/time.ts +210 -0
  163. package/src/types/uuid.ts +113 -0
  164. package/src/utils/bytes.ts +160 -0
  165. package/src/utils/date-format.ts +239 -0
  166. package/src/utils/json.ts +230 -0
  167. package/src/utils/num.ts +97 -0
  168. package/src/utils/obj.ts +956 -0
  169. package/src/utils/path.ts +40 -0
  170. package/src/utils/primitive.ts +33 -0
  171. package/src/utils/str.ts +252 -0
  172. package/src/utils/template-strings.ts +132 -0
  173. package/src/utils/transferable.ts +269 -0
  174. package/src/utils/wait.ts +40 -0
  175. package/src/utils/xml.ts +105 -0
  176. package/src/zip/sd-zip.ts +218 -0
  177. package/tests/errors/errors.spec.ts +196 -0
  178. package/tests/extensions/array-extension.spec.ts +790 -0
  179. package/tests/extensions/map-extension.spec.ts +147 -0
  180. package/tests/extensions/set-extension.spec.ts +74 -0
  181. package/tests/types/date-only.spec.ts +636 -0
  182. package/tests/types/date-time.spec.ts +391 -0
  183. package/tests/types/lazy-gc-map.spec.ts +692 -0
  184. package/tests/types/time.spec.ts +559 -0
  185. package/tests/types/types.spec.ts +55 -0
  186. package/tests/types/uuid.spec.ts +91 -0
  187. package/tests/utils/bytes-utils.spec.ts +230 -0
  188. package/tests/utils/date-format.spec.ts +371 -0
  189. package/tests/utils/debounce-queue.spec.ts +272 -0
  190. package/tests/utils/json.spec.ts +475 -0
  191. package/tests/utils/number.spec.ts +184 -0
  192. package/tests/utils/object.spec.ts +827 -0
  193. package/tests/utils/path.spec.ts +78 -0
  194. package/tests/utils/primitive.spec.ts +55 -0
  195. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  196. package/tests/utils/serial-queue.spec.ts +365 -0
  197. package/tests/utils/string.spec.ts +294 -0
  198. package/tests/utils/template-strings.spec.ts +96 -0
  199. package/tests/utils/transferable.spec.ts +698 -0
  200. package/tests/utils/wait.spec.ts +145 -0
  201. package/tests/utils/xml.spec.ts +146 -0
  202. package/tests/zip/sd-zip.spec.ts +234 -0
@@ -0,0 +1,790 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import "@simplysm/core-common"; // $ 확장 활성화
3
+
4
+ describe("Array.prototype 확장", () => {
5
+ //#region 기본 체이닝
6
+
7
+ describe("기본 체이닝", () => {
8
+ it("기존 Array 메서드를 체이닝할 수 있다", () => {
9
+ const result = [1, 2, 3, 4, 5].filter((x) => x > 2).map((x) => x * 10);
10
+
11
+ expect(result).toEqual([30, 40, 50]);
12
+ });
13
+
14
+ it("확장 메서드를 체이닝할 수 있다", () => {
15
+ const result = [
16
+ { id: 1, name: "a" },
17
+ { id: 2, name: "b" },
18
+ ].toMap((x) => x.id);
19
+
20
+ expect(result.get(1)).toEqual({ id: 1, name: "a" });
21
+ expect(result.get(2)).toEqual({ id: 2, name: "b" });
22
+ });
23
+
24
+ it("Array 메서드와 확장 메서드를 혼합하여 체이닝할 수 있다", () => {
25
+ const users = [
26
+ { id: 1, name: "Kim", active: true },
27
+ { id: 2, name: "Lee", active: false },
28
+ { id: 3, name: "Park", active: true },
29
+ ];
30
+
31
+ const result = users.filter((u) => u.active).toMap((u) => u.id);
32
+
33
+ expect(result.size).toBe(2);
34
+ expect(result.has(1)).toBe(true);
35
+ expect(result.has(3)).toBe(true);
36
+ expect(result.has(2)).toBe(false);
37
+ });
38
+
39
+ it("여러 단계 체이닝이 가능하다", () => {
40
+ const result = [1, 2, 3, 4, 5]
41
+ .filter((x) => x > 1)
42
+ .map((x) => x * 2)
43
+ .filter((x) => x < 10)
44
+ .toMap((x) => x);
45
+
46
+ expect(result.size).toBe(3);
47
+ expect(result.has(4)).toBe(true);
48
+ expect(result.has(6)).toBe(true);
49
+ expect(result.has(8)).toBe(true);
50
+ });
51
+
52
+ it("배열 속성에 접근할 수 있다", () => {
53
+ const arr = [1, 2, 3];
54
+
55
+ expect(arr.length).toBe(3);
56
+ expect(arr[0]).toBe(1);
57
+ expect(arr[1]).toBe(2);
58
+ expect(arr[2]).toBe(3);
59
+ });
60
+ });
61
+
62
+ //#endregion
63
+
64
+ //#region single
65
+
66
+ describe("single()", () => {
67
+ it("조건에 맞는 단일 요소를 반환한다", () => {
68
+ const result = [1, 2, 3].single((x) => x === 2);
69
+
70
+ expect(result).toBe(2);
71
+ });
72
+
73
+ it("조건에 맞는 요소가 없으면 undefined를 반환한다", () => {
74
+ const result = [1, 2, 3].single((x) => x === 4);
75
+
76
+ expect(result).toBe(undefined);
77
+ });
78
+
79
+ it("조건에 맞는 요소가 여러 개면 에러를 던진다", () => {
80
+ expect(() => [1, 1, 2].single((x) => x === 1)).toThrow();
81
+ });
82
+
83
+ it("조건 없이 호출하면 배열 전체를 대상으로 한다", () => {
84
+ expect([1].single()).toBe(1);
85
+ expect(([] as number[]).single()).toBe(undefined);
86
+ expect(() => [1, 2].single()).toThrow();
87
+ });
88
+
89
+ it("체이닝 후 single을 사용할 수 있다", () => {
90
+ const result = [1, 2, 3, 4, 5].filter((x) => x > 3).single((x) => x === 4);
91
+
92
+ expect(result).toBe(4);
93
+ });
94
+ });
95
+
96
+ //#endregion
97
+
98
+ //#region 비동기 메서드
99
+
100
+ describe("parallelAsync()", () => {
101
+ it("병렬 비동기 실행을 수행한다", async () => {
102
+ const result = await [1, 2, 3].parallelAsync(async (x) => Promise.resolve(x * 2));
103
+
104
+ expect(result).toEqual([2, 4, 6]);
105
+ });
106
+ });
107
+
108
+ describe("mapAsync()", () => {
109
+ it("비동기 순차 매핑을 수행한다", async () => {
110
+ const result = await [1, 2, 3].mapAsync(async (x) => Promise.resolve(x * 2));
111
+
112
+ expect(result).toEqual([2, 4, 6]);
113
+ });
114
+
115
+ it("체이닝 후 mapAsync를 사용할 수 있다", async () => {
116
+ const result = await [1, 2, 3, 4, 5].filter((x) => x > 2).mapAsync(async (x) => Promise.resolve(x * 10));
117
+
118
+ expect(result).toEqual([30, 40, 50]);
119
+ });
120
+ });
121
+
122
+ describe("filterAsync()", () => {
123
+ it("비동기 필터링을 수행한다", async () => {
124
+ const result = await [1, 2, 3, 4, 5].filterAsync(async (x) => Promise.resolve(x > 2));
125
+
126
+ expect(result).toEqual([3, 4, 5]);
127
+ });
128
+ });
129
+
130
+ //#endregion
131
+
132
+ //#region Map 변환
133
+
134
+ describe("toMap()", () => {
135
+ it("키 함수로 Map을 생성한다", () => {
136
+ const result = [
137
+ { id: 1, name: "a" },
138
+ { id: 2, name: "b" },
139
+ ].toMap((x) => x.id);
140
+
141
+ expect(result.get(1)).toEqual({ id: 1, name: "a" });
142
+ expect(result.get(2)).toEqual({ id: 2, name: "b" });
143
+ });
144
+
145
+ it("값 함수로 값을 변환한다", () => {
146
+ const result = [
147
+ { id: 1, name: "a" },
148
+ { id: 2, name: "b" },
149
+ ].toMap(
150
+ (x) => x.id,
151
+ (x) => x.name,
152
+ );
153
+
154
+ expect(result.get(1)).toBe("a");
155
+ expect(result.get(2)).toBe("b");
156
+ });
157
+
158
+ it("키가 중복되면 에러를 던진다", () => {
159
+ expect(() =>
160
+ [
161
+ { id: 1, name: "a" },
162
+ { id: 1, name: "b" },
163
+ ].toMap((x) => x.id),
164
+ ).toThrow("키가 중복되었습니다");
165
+ });
166
+ });
167
+
168
+ describe("toMapAsync()", () => {
169
+ it("비동기 키/값 함수로 Map을 생성한다", async () => {
170
+ const result = await [
171
+ { id: 1, name: "a" },
172
+ { id: 2, name: "b" },
173
+ ].toMapAsync(async (x) => Promise.resolve(x.id));
174
+
175
+ expect(result.get(1)).toEqual({ id: 1, name: "a" });
176
+ expect(result.get(2)).toEqual({ id: 2, name: "b" });
177
+ });
178
+ });
179
+
180
+ describe("toArrayMap()", () => {
181
+ it("배열 값을 가진 Map을 생성한다", () => {
182
+ const result = [
183
+ { type: "a", v: 1 },
184
+ { type: "b", v: 2 },
185
+ { type: "a", v: 3 },
186
+ ].toArrayMap((x) => x.type);
187
+
188
+ expect(result.get("a")).toHaveLength(2);
189
+ expect(result.get("b")).toHaveLength(1);
190
+ });
191
+
192
+ it("값 함수로 값을 변환한다", () => {
193
+ const result = [
194
+ { type: "a", v: 1 },
195
+ { type: "a", v: 2 },
196
+ ].toArrayMap(
197
+ (x) => x.type,
198
+ (x) => x.v,
199
+ );
200
+
201
+ expect(result.get("a")).toEqual([1, 2]);
202
+ });
203
+ });
204
+
205
+ describe("toSetMap()", () => {
206
+ it("Set 값을 가진 Map을 생성한다", () => {
207
+ const result = [
208
+ { type: "a", v: 1 },
209
+ { type: "a", v: 1 }, // 중복
210
+ { type: "a", v: 2 },
211
+ ].toSetMap(
212
+ (x) => x.type,
213
+ (x) => x.v,
214
+ );
215
+
216
+ expect(result.get("a")?.size).toBe(2); // Set이므로 중복 제거
217
+ });
218
+ });
219
+
220
+ describe("toMapValues()", () => {
221
+ it("그룹별 집계 결과를 Map으로 생성한다", () => {
222
+ const result = [
223
+ { type: "a", v: 10 },
224
+ { type: "b", v: 20 },
225
+ { type: "a", v: 30 },
226
+ ].toMapValues(
227
+ (x) => x.type,
228
+ (items) => items.reduce((sum, x) => sum + x.v, 0),
229
+ );
230
+
231
+ expect(result.get("a")).toBe(40);
232
+ expect(result.get("b")).toBe(20);
233
+ });
234
+ });
235
+
236
+ //#endregion
237
+
238
+ //#region 트리 변환
239
+
240
+ describe("toTree()", () => {
241
+ it("트리 구조로 변환한다", () => {
242
+ interface Item {
243
+ id: number;
244
+ parentId?: number;
245
+ name: string;
246
+ }
247
+
248
+ const items: Item[] = [
249
+ { id: 1, name: "root" },
250
+ { id: 2, parentId: 1, name: "child1" },
251
+ { id: 3, parentId: 1, name: "child2" },
252
+ { id: 4, parentId: 2, name: "grandchild" },
253
+ ];
254
+
255
+ const result = items.toTree("id", "parentId");
256
+
257
+ expect(result).toHaveLength(1);
258
+ expect(result[0].children).toHaveLength(2);
259
+ expect(result[0].children[0].children).toHaveLength(1);
260
+ });
261
+ });
262
+
263
+ //#endregion
264
+
265
+ //#region 배열 비교
266
+
267
+ describe("diffs()", () => {
268
+ it("배열 간 차이를 분석한다", () => {
269
+ interface Item {
270
+ id: number;
271
+ value: string;
272
+ }
273
+
274
+ const source: Item[] = [
275
+ { id: 1, value: "a" },
276
+ { id: 2, value: "b" },
277
+ { id: 3, value: "c" },
278
+ ];
279
+
280
+ const target: Item[] = [
281
+ { id: 2, value: "b" },
282
+ { id: 3, value: "changed" },
283
+ { id: 4, value: "d" },
284
+ ];
285
+
286
+ const result = source.diffs(target, { keys: ["id"] });
287
+
288
+ const deleted = result.find((d) => d.source?.id === 1);
289
+ expect(deleted?.target).toBe(undefined);
290
+
291
+ const updated = result.find((d) => d.source?.id === 3);
292
+ expect(updated?.target?.value).toBe("changed");
293
+
294
+ const inserted = result.find((d) => d.target?.id === 4);
295
+ expect(inserted?.source).toBe(undefined);
296
+ });
297
+ });
298
+
299
+ describe("oneWayDiffs()", () => {
300
+ it("단방향 차이를 분석한다", () => {
301
+ interface Item {
302
+ id: number;
303
+ value: string;
304
+ }
305
+
306
+ const orgItems: Item[] = [
307
+ { id: 1, value: "a" },
308
+ { id: 2, value: "b" },
309
+ ];
310
+
311
+ const items: Item[] = [
312
+ { id: 2, value: "changed" },
313
+ { id: 3, value: "c" },
314
+ ];
315
+
316
+ const result = items.oneWayDiffs(orgItems, "id");
317
+
318
+ const updated = result.find((d) => d.item.id === 2);
319
+ expect(updated?.type).toBe("update");
320
+
321
+ const created = result.find((d) => d.item.id === 3);
322
+ expect(created?.type).toBe("create");
323
+ });
324
+
325
+ it("includeSame=true로 변경 없는 항목도 포함한다", () => {
326
+ interface Item {
327
+ id: number;
328
+ value: string;
329
+ }
330
+
331
+ const orgItems: Item[] = [
332
+ { id: 1, value: "a" },
333
+ { id: 2, value: "b" },
334
+ ];
335
+
336
+ const items: Item[] = [
337
+ { id: 1, value: "a" }, // 변경 없음
338
+ { id: 2, value: "changed" },
339
+ ];
340
+
341
+ const result = items.oneWayDiffs(orgItems, "id", { includeSame: true });
342
+
343
+ const same = result.find((d) => d.item.id === 1);
344
+ expect(same?.type).toBe("same");
345
+
346
+ const updated = result.find((d) => d.item.id === 2);
347
+ expect(updated?.type).toBe("update");
348
+ });
349
+ });
350
+
351
+ describe("merge()", () => {
352
+ it("변경된 항목을 병합한다", () => {
353
+ interface Item {
354
+ id: number;
355
+ value: string;
356
+ }
357
+
358
+ const source: Item[] = [
359
+ { id: 1, value: "a" },
360
+ { id: 2, value: "b" },
361
+ ];
362
+ const target: Item[] = [
363
+ { id: 1, value: "a" },
364
+ { id: 2, value: "changed" },
365
+ ];
366
+
367
+ const result = source.merge(target, { keys: ["id"] });
368
+
369
+ expect(result).toHaveLength(2);
370
+ expect(result.find((r) => r.id === 2)?.value).toBe("changed");
371
+ });
372
+ });
373
+
374
+ //#endregion
375
+
376
+ //#region ReadonlyArray 지원
377
+
378
+ describe("ReadonlyArray 지원", () => {
379
+ it("readonly 배열에서도 $를 사용할 수 있다", () => {
380
+ const arr: readonly number[] = [1, 2, 3];
381
+ const result = arr.filter((x) => x > 1).toMap((x) => x);
382
+
383
+ expect(result.size).toBe(2);
384
+ expect(result.has(2)).toBe(true);
385
+ expect(result.has(3)).toBe(true);
386
+ });
387
+ });
388
+
389
+ //#endregion
390
+
391
+ //#region 다양한 Array 메서드 체이닝
392
+
393
+ describe("다양한 Array 메서드 체이닝", () => {
394
+ it("flatMap을 체이닝할 수 있다", () => {
395
+ const result = [
396
+ [1, 2],
397
+ [3, 4],
398
+ ]
399
+ .flatMap((x) => x)
400
+ .toMap((x) => x);
401
+
402
+ expect(result.size).toBe(4);
403
+ });
404
+
405
+ it("slice를 체이닝할 수 있다", () => {
406
+ const result = [1, 2, 3, 4, 5].slice(1, 4).toMap((x) => x);
407
+
408
+ expect(result.size).toBe(3);
409
+ expect(result.has(2)).toBe(true);
410
+ expect(result.has(3)).toBe(true);
411
+ expect(result.has(4)).toBe(true);
412
+ });
413
+
414
+ it("concat을 체이닝할 수 있다", () => {
415
+ const result = [1, 2].concat([3, 4]).toMap((x) => x);
416
+
417
+ expect(result.size).toBe(4);
418
+ });
419
+
420
+ it("sort를 체이닝할 수 있다", () => {
421
+ const result = [3, 1, 2].sort((a, b) => a - b).toMap((x, i) => i);
422
+
423
+ expect(result.get(0)).toBe(1);
424
+ expect(result.get(1)).toBe(2);
425
+ expect(result.get(2)).toBe(3);
426
+ });
427
+ });
428
+
429
+ //#endregion
430
+
431
+ //#region first, last
432
+
433
+ describe("first()", () => {
434
+ it("첫 번째 요소를 반환한다", () => {
435
+ expect([1, 2, 3].first()).toBe(1);
436
+ });
437
+
438
+ it("조건에 맞는 첫 번째 요소를 반환한다", () => {
439
+ expect([1, 2, 3, 4, 5].first((x) => x > 3)).toBe(4);
440
+ });
441
+
442
+ it("빈 배열에서 undefined를 반환한다", () => {
443
+ expect(([] as number[]).first()).toBe(undefined);
444
+ });
445
+ });
446
+
447
+ describe("last()", () => {
448
+ it("마지막 요소를 반환한다", () => {
449
+ expect([1, 2, 3].last()).toBe(3);
450
+ });
451
+
452
+ it("조건에 맞는 마지막 요소를 반환한다", () => {
453
+ expect([1, 2, 3, 4, 5].last((x) => x < 4)).toBe(3);
454
+ });
455
+
456
+ it("빈 배열에서 undefined를 반환한다", () => {
457
+ expect(([] as number[]).last()).toBe(undefined);
458
+ });
459
+ });
460
+
461
+ //#endregion
462
+
463
+ //#region filterExists, ofType
464
+
465
+ describe("filterExists()", () => {
466
+ it("null/undefined를 제거한다", () => {
467
+ const arr = [1, null, 2, undefined, 3];
468
+ const result = arr.filterExists();
469
+ expect(result).toEqual([1, 2, 3]);
470
+ });
471
+
472
+ it("체이닝이 가능하다", () => {
473
+ const arr = [1, null, 2, undefined, 3];
474
+ const result = arr.filterExists().map((x) => x * 2);
475
+ expect(result).toEqual([2, 4, 6]);
476
+ });
477
+ });
478
+
479
+ describe("ofType()", () => {
480
+ it("특정 타입의 요소만 필터링한다 (string)", () => {
481
+ const arr = [1, "a", 2, "b", true];
482
+ const result = arr.ofType("string");
483
+ expect(result).toEqual(["a", "b"]);
484
+ });
485
+
486
+ it("특정 타입의 요소만 필터링한다 (number)", () => {
487
+ const arr = [1, "a", 2, "b", 3];
488
+ const result = arr.ofType("number");
489
+ expect(result).toEqual([1, 2, 3]);
490
+ });
491
+
492
+ it("특정 타입의 요소만 필터링한다 (boolean)", () => {
493
+ const arr = [1, "a", true, false, 2];
494
+ const result = arr.ofType("boolean");
495
+ expect(result).toEqual([true, false]);
496
+ });
497
+ });
498
+
499
+ //#endregion
500
+
501
+ //#region mapMany
502
+
503
+ describe("mapMany()", () => {
504
+ it("매핑 후 평탄화한다", () => {
505
+ const result = [1, 2, 3].mapMany((x) => [x, x * 10]);
506
+ expect(result).toEqual([1, 10, 2, 20, 3, 30]);
507
+ });
508
+ });
509
+
510
+ describe("mapManyAsync()", () => {
511
+ it("비동기 매핑 후 평탄화한다", async () => {
512
+ const result = await [1, 2, 3].mapManyAsync(async (x) => Promise.resolve([x, x * 10]));
513
+ expect(result).toEqual([1, 10, 2, 20, 3, 30]);
514
+ });
515
+
516
+ it("중첩 Promise 배열을 비동기 매핑 후 평탄화한다", async () => {
517
+ const result = await [1, 2].mapManyAsync(async (x) => Promise.resolve([x, x + 1, x + 2]));
518
+ expect(result).toEqual([1, 2, 3, 2, 3, 4]);
519
+ });
520
+ });
521
+
522
+ //#endregion
523
+
524
+ //#region groupBy
525
+
526
+ describe("groupBy()", () => {
527
+ it("키로 그룹화한다", () => {
528
+ const items = [
529
+ { type: "a", value: 1 },
530
+ { type: "b", value: 2 },
531
+ { type: "a", value: 3 },
532
+ ];
533
+ const result = items.groupBy((x) => x.type);
534
+
535
+ expect(result).toHaveLength(2);
536
+ expect(result.find((g) => g.key === "a")?.values).toHaveLength(2);
537
+ expect(result.find((g) => g.key === "b")?.values).toHaveLength(1);
538
+ });
539
+ });
540
+
541
+ //#endregion
542
+
543
+ //#region toObject
544
+
545
+ describe("toObject()", () => {
546
+ it("배열을 객체로 변환한다", () => {
547
+ const items = [
548
+ { key: "a", value: 1 },
549
+ { key: "b", value: 2 },
550
+ ];
551
+ const result = items.toObject(
552
+ (x) => x.key,
553
+ (x) => x.value,
554
+ );
555
+
556
+ expect(result).toEqual({ a: 1, b: 2 });
557
+ });
558
+
559
+ it("키가 중복되면 에러를 던진다", () => {
560
+ const items = [
561
+ { key: "a", value: 1 },
562
+ { key: "a", value: 2 },
563
+ ];
564
+ expect(() => items.toObject((x) => x.key)).toThrow();
565
+ });
566
+ });
567
+
568
+ //#endregion
569
+
570
+ //#region distinct
571
+
572
+ describe("distinct()", () => {
573
+ it("중복을 제거한다", () => {
574
+ expect([1, 2, 2, 3, 3, 3].distinct()).toEqual([1, 2, 3]);
575
+ });
576
+
577
+ it("객체 배열에서 중복을 제거한다", () => {
578
+ const arr = [{ a: 1 }, { a: 2 }, { a: 1 }];
579
+ const result = arr.distinct();
580
+ expect(result).toHaveLength(2);
581
+ });
582
+
583
+ it("체이닝이 가능하다", () => {
584
+ const result = [1, 2, 2, 3].distinct().map((x) => x * 2);
585
+ expect(result).toEqual([2, 4, 6]);
586
+ });
587
+
588
+ it("keyFn으로 커스텀 키를 사용할 수 있다", () => {
589
+ const arr = [
590
+ { id: 1, name: "a" },
591
+ { id: 2, name: "b" },
592
+ { id: 1, name: "c" },
593
+ ];
594
+ const result = arr.distinct({ keyFn: (x) => x.id });
595
+ expect(result).toHaveLength(2);
596
+ });
597
+
598
+ it("matchAddress=true로 참조 비교로 중복을 제거한다", () => {
599
+ const obj1 = { a: 1 };
600
+ const obj2 = { a: 1 }; // 같은 값이지만 다른 참조
601
+ const arr = [obj1, obj1, obj2];
602
+ const result = arr.distinct({ matchAddress: true });
603
+ expect(result).toHaveLength(2);
604
+ expect(result).toContain(obj1);
605
+ expect(result).toContain(obj2);
606
+ });
607
+ });
608
+
609
+ //#endregion
610
+
611
+ //#region orderBy, orderByDesc
612
+
613
+ describe("orderBy()", () => {
614
+ it("오름차순 정렬한다", () => {
615
+ expect([3, 1, 2].orderBy()).toEqual([1, 2, 3]);
616
+ });
617
+
618
+ it("selector로 정렬 기준을 지정할 수 있다", () => {
619
+ const items = [
620
+ { name: "b", age: 30 },
621
+ { name: "a", age: 20 },
622
+ { name: "c", age: 25 },
623
+ ];
624
+ const result = items.orderBy((x) => x.age);
625
+ expect(result.map((x) => x.age)).toEqual([20, 25, 30]);
626
+ });
627
+
628
+ it("체이닝이 가능하다", () => {
629
+ const result = [3, 1, 2].orderBy().map((x) => x * 2);
630
+ expect(result).toEqual([2, 4, 6]);
631
+ });
632
+ });
633
+
634
+ describe("orderByDesc()", () => {
635
+ it("내림차순 정렬한다", () => {
636
+ expect([1, 3, 2].orderByDesc()).toEqual([3, 2, 1]);
637
+ });
638
+ });
639
+
640
+ //#endregion
641
+
642
+ //#region sum, min, max
643
+
644
+ describe("sum()", () => {
645
+ it("합계를 반환한다", () => {
646
+ expect([1, 2, 3, 4, 5].sum()).toBe(15);
647
+ });
648
+
649
+ it("selector로 값을 추출할 수 있다", () => {
650
+ const items = [{ value: 10 }, { value: 20 }, { value: 30 }];
651
+ expect(items.sum((x) => x.value)).toBe(60);
652
+ });
653
+
654
+ it("빈 배열은 0을 반환한다", () => {
655
+ expect(([] as number[]).sum()).toBe(0);
656
+ });
657
+
658
+ it("number가 아닌 타입에서 에러를 던진다", () => {
659
+ expect(() => (["a", "b"] as unknown as number[]).sum()).toThrow("sum 은 number 에 대해서만");
660
+ });
661
+ });
662
+
663
+ describe("min()", () => {
664
+ it("최소값을 반환한다", () => {
665
+ expect([3, 1, 2].min()).toBe(1);
666
+ });
667
+
668
+ it("빈 배열은 undefined를 반환한다", () => {
669
+ expect(([] as number[]).min()).toBe(undefined);
670
+ });
671
+
672
+ it("number/string이 아닌 타입에서 에러를 던진다", () => {
673
+ expect(() => ([true, false] as unknown as number[]).min()).toThrow("min 은 number/string 에 대해서만");
674
+ });
675
+ });
676
+
677
+ describe("max()", () => {
678
+ it("최대값을 반환한다", () => {
679
+ expect([1, 3, 2].max()).toBe(3);
680
+ });
681
+
682
+ it("빈 배열은 undefined를 반환한다", () => {
683
+ expect(([] as number[]).max()).toBe(undefined);
684
+ });
685
+
686
+ it("number/string이 아닌 타입에서 에러를 던진다", () => {
687
+ expect(() => ([{}, {}] as unknown as number[]).max()).toThrow("max 은 number/string 에 대해서만");
688
+ });
689
+ });
690
+
691
+ //#endregion
692
+
693
+ //#region shuffle
694
+
695
+ describe("shuffle()", () => {
696
+ it("배열을 섞는다 (원본 유지)", () => {
697
+ const original = [1, 2, 3, 4, 5];
698
+ const shuffled = original.shuffle();
699
+
700
+ // 원본은 변경되지 않음
701
+ expect(original).toEqual([1, 2, 3, 4, 5]);
702
+ // 같은 요소들을 가짐
703
+ expect(shuffled.sort()).toEqual([1, 2, 3, 4, 5]);
704
+ });
705
+ });
706
+
707
+ //#endregion
708
+
709
+ //#region Mutable 메서드
710
+
711
+ describe("distinctThis()", () => {
712
+ it("원본 배열에서 중복을 제거한다", () => {
713
+ const arr = [1, 2, 2, 3, 3, 3];
714
+ const result = arr.distinctThis();
715
+
716
+ expect(arr).toEqual([1, 2, 3]);
717
+ expect(result).toEqual([1, 2, 3]);
718
+ });
719
+ });
720
+
721
+ describe("orderByThis()", () => {
722
+ it("원본 배열을 오름차순 정렬한다", () => {
723
+ const arr = [3, 1, 2];
724
+ arr.orderByThis();
725
+
726
+ expect(arr).toEqual([1, 2, 3]);
727
+ });
728
+ });
729
+
730
+ describe("orderByDescThis()", () => {
731
+ it("원본 배열을 내림차순 정렬한다", () => {
732
+ const arr = [1, 3, 2];
733
+ arr.orderByDescThis();
734
+
735
+ expect(arr).toEqual([3, 2, 1]);
736
+ });
737
+ });
738
+
739
+ describe("insert()", () => {
740
+ it("원본 배열에 항목을 삽입한다", () => {
741
+ const arr = [1, 3];
742
+ arr.insert(1, 2);
743
+
744
+ expect(arr).toEqual([1, 2, 3]);
745
+ });
746
+ });
747
+
748
+ describe("remove()", () => {
749
+ it("원본 배열에서 항목을 제거한다", () => {
750
+ const arr = [1, 2, 3];
751
+ arr.remove(2);
752
+
753
+ expect(arr).toEqual([1, 3]);
754
+ });
755
+
756
+ it("조건 함수로 항목을 제거한다", () => {
757
+ const arr = [1, 2, 3, 4];
758
+ arr.remove((x) => x % 2 === 0);
759
+
760
+ expect(arr).toEqual([1, 3]);
761
+ });
762
+ });
763
+
764
+ describe("toggle()", () => {
765
+ it("항목이 있으면 제거한다", () => {
766
+ const arr = [1, 2, 3];
767
+ arr.toggle(2);
768
+
769
+ expect(arr).toEqual([1, 3]);
770
+ });
771
+
772
+ it("항목이 없으면 추가한다", () => {
773
+ const arr = [1, 3];
774
+ arr.toggle(2);
775
+
776
+ expect(arr).toEqual([1, 3, 2]);
777
+ });
778
+ });
779
+
780
+ describe("clear()", () => {
781
+ it("원본 배열을 비운다", () => {
782
+ const arr = [1, 2, 3];
783
+ arr.clear();
784
+
785
+ expect(arr).toEqual([]);
786
+ });
787
+ });
788
+
789
+ //#endregion
790
+ });