@simplysm/core-common 13.0.0-beta.2 → 13.0.0-beta.21
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.
- package/dist/common.types.js +4 -4
- package/dist/errors/argument-error.js +1 -1
- package/dist/errors/not-implemented-error.js +1 -1
- package/dist/errors/timeout-error.js +1 -1
- package/dist/extensions/arr-ext.helpers.js +4 -4
- package/dist/extensions/arr-ext.js +9 -9
- package/dist/features/debounce-queue.js +2 -2
- package/dist/features/serial-queue.js +3 -3
- package/dist/index.js +30 -30
- package/dist/types/date-only.js +2 -2
- package/dist/types/date-time.js +2 -2
- package/dist/types/time.js +2 -2
- package/dist/types/uuid.js +1 -1
- package/dist/utils/bytes.js +1 -1
- package/dist/utils/json.js +8 -8
- package/dist/utils/obj.js +5 -5
- package/dist/utils/primitive.js +5 -5
- package/dist/utils/transferable.js +4 -4
- package/dist/utils/wait.js +1 -1
- package/package.json +7 -4
- package/.cache/typecheck-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-node.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-node.tsbuildinfo +0 -1
- package/src/common.types.ts +0 -91
- package/src/env.ts +0 -11
- package/src/errors/argument-error.ts +0 -40
- package/src/errors/not-implemented-error.ts +0 -32
- package/src/errors/sd-error.ts +0 -53
- package/src/errors/timeout-error.ts +0 -36
- package/src/extensions/arr-ext.helpers.ts +0 -53
- package/src/extensions/arr-ext.ts +0 -777
- package/src/extensions/arr-ext.types.ts +0 -258
- package/src/extensions/map-ext.ts +0 -86
- package/src/extensions/set-ext.ts +0 -68
- package/src/features/debounce-queue.ts +0 -116
- package/src/features/event-emitter.ts +0 -112
- package/src/features/serial-queue.ts +0 -94
- package/src/globals.ts +0 -12
- package/src/index.ts +0 -55
- package/src/types/date-only.ts +0 -329
- package/src/types/date-time.ts +0 -294
- package/src/types/lazy-gc-map.ts +0 -244
- package/src/types/time.ts +0 -210
- package/src/types/uuid.ts +0 -113
- package/src/utils/bytes.ts +0 -160
- package/src/utils/date-format.ts +0 -239
- package/src/utils/json.ts +0 -230
- package/src/utils/num.ts +0 -97
- package/src/utils/obj.ts +0 -956
- package/src/utils/path.ts +0 -40
- package/src/utils/primitive.ts +0 -33
- package/src/utils/str.ts +0 -252
- package/src/utils/template-strings.ts +0 -132
- package/src/utils/transferable.ts +0 -269
- package/src/utils/wait.ts +0 -40
- package/src/utils/xml.ts +0 -105
- package/src/zip/sd-zip.ts +0 -218
- package/tests/errors/errors.spec.ts +0 -196
- package/tests/extensions/array-extension.spec.ts +0 -790
- package/tests/extensions/map-extension.spec.ts +0 -147
- package/tests/extensions/set-extension.spec.ts +0 -74
- package/tests/types/date-only.spec.ts +0 -636
- package/tests/types/date-time.spec.ts +0 -391
- package/tests/types/lazy-gc-map.spec.ts +0 -692
- package/tests/types/time.spec.ts +0 -559
- package/tests/types/types.spec.ts +0 -55
- package/tests/types/uuid.spec.ts +0 -91
- package/tests/utils/bytes-utils.spec.ts +0 -230
- package/tests/utils/date-format.spec.ts +0 -371
- package/tests/utils/debounce-queue.spec.ts +0 -272
- package/tests/utils/json.spec.ts +0 -475
- package/tests/utils/number.spec.ts +0 -184
- package/tests/utils/object.spec.ts +0 -827
- package/tests/utils/path.spec.ts +0 -78
- package/tests/utils/primitive.spec.ts +0 -55
- package/tests/utils/sd-event-emitter.spec.ts +0 -216
- package/tests/utils/serial-queue.spec.ts +0 -365
- package/tests/utils/string.spec.ts +0 -294
- package/tests/utils/template-strings.spec.ts +0 -96
- package/tests/utils/transferable.spec.ts +0 -698
- package/tests/utils/wait.spec.ts +0 -145
- package/tests/utils/xml.spec.ts +0 -146
- package/tests/zip/sd-zip.spec.ts +0 -234
|
@@ -1,827 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
objClone as clone,
|
|
4
|
-
objEqual,
|
|
5
|
-
objMerge,
|
|
6
|
-
objMerge3,
|
|
7
|
-
objOmit,
|
|
8
|
-
objOmitByFilter,
|
|
9
|
-
objPick,
|
|
10
|
-
objGetChainValue,
|
|
11
|
-
objGetChainValueByDepth,
|
|
12
|
-
objSetChainValue,
|
|
13
|
-
objDeleteChainValue,
|
|
14
|
-
objClearUndefined,
|
|
15
|
-
objClear,
|
|
16
|
-
objNullToUndefined,
|
|
17
|
-
objUnflatten,
|
|
18
|
-
DateTime,
|
|
19
|
-
DateOnly,
|
|
20
|
-
Uuid,
|
|
21
|
-
} from "@simplysm/core-common";
|
|
22
|
-
|
|
23
|
-
describe("object utils", () => {
|
|
24
|
-
//#region clone
|
|
25
|
-
|
|
26
|
-
describe("objClone()", () => {
|
|
27
|
-
it("primitive 값을 복사한다", () => {
|
|
28
|
-
expect(clone(42)).toBe(42);
|
|
29
|
-
expect(clone("hello")).toBe("hello");
|
|
30
|
-
expect(clone(true)).toBe(true);
|
|
31
|
-
expect(clone(null)).toBe(null);
|
|
32
|
-
expect(clone(undefined)).toBe(undefined);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("배열을 깊은 복사한다", () => {
|
|
36
|
-
const arr = [1, [2, 3], { a: 4 }];
|
|
37
|
-
const cloned = clone(arr);
|
|
38
|
-
|
|
39
|
-
expect(cloned).toEqual(arr);
|
|
40
|
-
expect(cloned).not.toBe(arr);
|
|
41
|
-
expect(cloned[1]).not.toBe(arr[1]);
|
|
42
|
-
expect(cloned[2]).not.toBe(arr[2]);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("객체를 깊은 복사한다", () => {
|
|
46
|
-
const obj = { a: 1, b: { c: 2 }, d: [3, 4] };
|
|
47
|
-
const cloned = clone(obj);
|
|
48
|
-
|
|
49
|
-
expect(cloned).toEqual(obj);
|
|
50
|
-
expect(cloned).not.toBe(obj);
|
|
51
|
-
expect(cloned.b).not.toBe(obj.b);
|
|
52
|
-
expect(cloned.d).not.toBe(obj.d);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("Date를 복사한다", () => {
|
|
56
|
-
const date = new Date(2024, 2, 15);
|
|
57
|
-
const cloned = clone(date);
|
|
58
|
-
|
|
59
|
-
expect(cloned).toEqual(date);
|
|
60
|
-
expect(cloned).not.toBe(date);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("DateTime을 복사한다", () => {
|
|
64
|
-
const dt = new DateTime(2024, 3, 15, 10, 30);
|
|
65
|
-
const cloned = clone(dt);
|
|
66
|
-
|
|
67
|
-
expect(cloned.tick).toBe(dt.tick);
|
|
68
|
-
expect(cloned).not.toBe(dt);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("DateOnly를 복사한다", () => {
|
|
72
|
-
const d = new DateOnly(2024, 3, 15);
|
|
73
|
-
const cloned = clone(d);
|
|
74
|
-
|
|
75
|
-
expect(cloned.tick).toBe(d.tick);
|
|
76
|
-
expect(cloned).not.toBe(d);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("Uuid를 복사한다", () => {
|
|
80
|
-
const uuid = Uuid.new();
|
|
81
|
-
const cloned = clone(uuid);
|
|
82
|
-
|
|
83
|
-
expect(cloned.toString()).toBe(uuid.toString());
|
|
84
|
-
expect(cloned).not.toBe(uuid);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("Map을 복사한다", () => {
|
|
88
|
-
const map = new Map<string, number | { c: number }>([
|
|
89
|
-
["a", 1],
|
|
90
|
-
["b", { c: 2 }],
|
|
91
|
-
]);
|
|
92
|
-
const cloned = clone(map);
|
|
93
|
-
|
|
94
|
-
expect(cloned.get("a")).toBe(1);
|
|
95
|
-
expect(cloned.get("b")).toEqual({ c: 2 });
|
|
96
|
-
expect(cloned.get("b")).not.toBe(map.get("b"));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("Set을 복사한다", () => {
|
|
100
|
-
const obj = { a: 1 };
|
|
101
|
-
const set = new Set([1, 2, obj]);
|
|
102
|
-
const cloned = clone(set);
|
|
103
|
-
|
|
104
|
-
expect(cloned.has(1)).toBe(true);
|
|
105
|
-
expect(cloned.has(2)).toBe(true);
|
|
106
|
-
// Set에 있는 obj는 복사됨
|
|
107
|
-
const clonedObj = Array.from(cloned).find((item) => typeof item === "object");
|
|
108
|
-
expect(clonedObj).toEqual(obj);
|
|
109
|
-
expect(clonedObj).not.toBe(obj);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("순환 참조를 처리한다", () => {
|
|
113
|
-
const obj: Record<string, unknown> = { a: 1 };
|
|
114
|
-
obj["self"] = obj;
|
|
115
|
-
|
|
116
|
-
const cloned = clone(obj);
|
|
117
|
-
|
|
118
|
-
expect(cloned["a"]).toBe(1);
|
|
119
|
-
expect(cloned["self"]).toBe(cloned);
|
|
120
|
-
expect(cloned).not.toBe(obj);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("RegExp를 복사한다", () => {
|
|
124
|
-
const regex = /test/gi;
|
|
125
|
-
const cloned = clone(regex);
|
|
126
|
-
|
|
127
|
-
expect(cloned).toEqual(regex);
|
|
128
|
-
expect(cloned).not.toBe(regex);
|
|
129
|
-
expect(cloned.source).toBe("test");
|
|
130
|
-
expect(cloned.flags).toBe("gi");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("Error를 복사한다", () => {
|
|
134
|
-
const error = new Error("test error");
|
|
135
|
-
const cloned = clone(error);
|
|
136
|
-
|
|
137
|
-
expect(cloned.message).toBe("test error");
|
|
138
|
-
expect(cloned).not.toBe(error);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("Error의 cause를 복사한다", () => {
|
|
142
|
-
const cause = new Error("cause error");
|
|
143
|
-
const error = new Error("test error", { cause });
|
|
144
|
-
const cloned = clone(error);
|
|
145
|
-
|
|
146
|
-
expect(cloned.message).toBe("test error");
|
|
147
|
-
expect(cloned.cause).toBeInstanceOf(Error);
|
|
148
|
-
expect((cloned.cause as Error).message).toBe("cause error");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("Error의 커스텀 속성을 복사한다", () => {
|
|
152
|
-
const error = new Error("test") as Error & { code: string; detail: object };
|
|
153
|
-
error.code = "ERR_CODE";
|
|
154
|
-
error.detail = { key: "value" };
|
|
155
|
-
const cloned = clone(error);
|
|
156
|
-
|
|
157
|
-
expect(cloned.code).toBe("ERR_CODE");
|
|
158
|
-
expect(cloned.detail).toEqual({ key: "value" });
|
|
159
|
-
expect(cloned.detail).not.toBe(error.detail);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("Uint8Array를 복사한다", () => {
|
|
163
|
-
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
|
164
|
-
const cloned = clone(arr);
|
|
165
|
-
|
|
166
|
-
expect(cloned).toEqual(arr);
|
|
167
|
-
expect(cloned).not.toBe(arr);
|
|
168
|
-
expect(cloned.buffer).not.toBe(arr.buffer);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("Symbol 키는 복사되지 않는다", () => {
|
|
172
|
-
// Object.keys()는 Symbol 키를 열거하지 않으므로 복사되지 않음
|
|
173
|
-
const sym = Symbol("test");
|
|
174
|
-
const obj = { a: 1, [sym]: "symbol value" };
|
|
175
|
-
const cloned = clone(obj);
|
|
176
|
-
|
|
177
|
-
expect(cloned.a).toBe(1);
|
|
178
|
-
expect(cloned[sym]).toBeUndefined();
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
//#endregion
|
|
183
|
-
|
|
184
|
-
//#region equal
|
|
185
|
-
|
|
186
|
-
describe("objEqual()", () => {
|
|
187
|
-
it("primitive 값을 비교한다", () => {
|
|
188
|
-
expect(objEqual(1, 1)).toBe(true);
|
|
189
|
-
expect(objEqual(1, 2)).toBe(false);
|
|
190
|
-
expect(objEqual("a", "a")).toBe(true);
|
|
191
|
-
expect(objEqual(null, null)).toBe(true);
|
|
192
|
-
expect(objEqual(undefined, undefined)).toBe(true);
|
|
193
|
-
expect(objEqual(null, undefined)).toBe(false);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("배열을 비교한다", () => {
|
|
197
|
-
expect(objEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
198
|
-
expect(objEqual([1, 2, 3], [1, 2])).toBe(false);
|
|
199
|
-
expect(objEqual([1, 2, 3], [1, 3, 2])).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("객체를 비교한다", () => {
|
|
203
|
-
expect(objEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
|
|
204
|
-
expect(objEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false);
|
|
205
|
-
expect(objEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("중첩된 객체를 비교한다", () => {
|
|
209
|
-
expect(objEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } })).toBe(true);
|
|
210
|
-
expect(objEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } })).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("DateTime을 비교한다", () => {
|
|
214
|
-
const dt1 = new DateTime(2024, 3, 15);
|
|
215
|
-
const dt2 = new DateTime(2024, 3, 15);
|
|
216
|
-
const dt3 = new DateTime(2024, 3, 16);
|
|
217
|
-
|
|
218
|
-
expect(objEqual(dt1, dt2)).toBe(true);
|
|
219
|
-
expect(objEqual(dt1, dt3)).toBe(false);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("Uuid를 비교한다", () => {
|
|
223
|
-
const uuid1 = new Uuid("12345678-9abc-def0-1234-56789abcdef0");
|
|
224
|
-
const uuid2 = new Uuid("12345678-9abc-def0-1234-56789abcdef0");
|
|
225
|
-
const uuid3 = new Uuid("12345678-9abc-def0-1234-56789abcdef1");
|
|
226
|
-
|
|
227
|
-
expect(objEqual(uuid1, uuid2)).toBe(true);
|
|
228
|
-
expect(objEqual(uuid1, uuid3)).toBe(false);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("RegExp를 비교한다", () => {
|
|
232
|
-
const regex1 = /test/gi;
|
|
233
|
-
const regex2 = /test/gi;
|
|
234
|
-
const regex3 = /test/g;
|
|
235
|
-
const regex4 = /other/gi;
|
|
236
|
-
|
|
237
|
-
expect(objEqual(regex1, regex2)).toBe(true);
|
|
238
|
-
expect(objEqual(regex1, regex3)).toBe(false); // flags 다름
|
|
239
|
-
expect(objEqual(regex1, regex4)).toBe(false); // source 다름
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("Map을 비교한다", () => {
|
|
243
|
-
const map1 = new Map([
|
|
244
|
-
["a", 1],
|
|
245
|
-
["b", 2],
|
|
246
|
-
]);
|
|
247
|
-
const map2 = new Map([
|
|
248
|
-
["a", 1],
|
|
249
|
-
["b", 2],
|
|
250
|
-
]);
|
|
251
|
-
const map3 = new Map([
|
|
252
|
-
["a", 1],
|
|
253
|
-
["b", 3],
|
|
254
|
-
]);
|
|
255
|
-
|
|
256
|
-
expect(objEqual(map1, map2)).toBe(true);
|
|
257
|
-
expect(objEqual(map1, map3)).toBe(false);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("Set을 비교한다", () => {
|
|
261
|
-
const set1 = new Set([1, 2, 3]);
|
|
262
|
-
const set2 = new Set([1, 2, 3]);
|
|
263
|
-
const set3 = new Set([1, 2, 4]);
|
|
264
|
-
|
|
265
|
-
expect(objEqual(set1, set2)).toBe(true);
|
|
266
|
-
expect(objEqual(set1, set3)).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("topLevelIncludes 옵션으로 특정 키만 비교한다", () => {
|
|
270
|
-
const obj1 = { a: 1, b: 2, c: 3 };
|
|
271
|
-
const obj2 = { a: 1, b: 99, c: 99 };
|
|
272
|
-
|
|
273
|
-
expect(objEqual(obj1, obj2, { topLevelIncludes: ["a"] })).toBe(true);
|
|
274
|
-
expect(objEqual(obj1, obj2, { topLevelIncludes: ["a", "b"] })).toBe(false);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("topLevelExcludes 옵션으로 특정 키를 제외한다", () => {
|
|
278
|
-
const obj1 = { a: 1, b: 2, c: 3 };
|
|
279
|
-
const obj2 = { a: 1, b: 99, c: 99 };
|
|
280
|
-
|
|
281
|
-
expect(objEqual(obj1, obj2, { topLevelExcludes: ["b", "c"] })).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it("ignoreArrayIndex 옵션으로 배열 순서를 무시한다", () => {
|
|
285
|
-
expect(objEqual([1, 2, 3], [3, 2, 1], { ignoreArrayIndex: true })).toBe(true);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("onlyOneDepth 옵션으로 얕은 비교를 한다", () => {
|
|
289
|
-
const inner = { c: 1 };
|
|
290
|
-
const obj1 = { a: 1, b: inner };
|
|
291
|
-
const obj2 = { a: 1, b: inner };
|
|
292
|
-
const obj3 = { a: 1, b: { c: 1 } };
|
|
293
|
-
|
|
294
|
-
expect(objEqual(obj1, obj2, { onlyOneDepth: true })).toBe(true);
|
|
295
|
-
expect(objEqual(obj1, obj3, { onlyOneDepth: true })).toBe(false);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
//#endregion
|
|
300
|
-
|
|
301
|
-
//#region merge
|
|
302
|
-
|
|
303
|
-
describe("objMerge()", () => {
|
|
304
|
-
it("source가 null이면 target을 복사한다", () => {
|
|
305
|
-
const target = { a: 1 };
|
|
306
|
-
const result = objMerge(null, target);
|
|
307
|
-
|
|
308
|
-
expect(result).toEqual({ a: 1 });
|
|
309
|
-
expect(result).not.toBe(target);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it("target이 undefined면 source를 복사한다", () => {
|
|
313
|
-
const source = { a: 1 };
|
|
314
|
-
const result = objMerge(source, undefined);
|
|
315
|
-
|
|
316
|
-
expect(result).toEqual({ a: 1 });
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it("객체를 병합한다", () => {
|
|
320
|
-
const source = { a: 1, b: 2 };
|
|
321
|
-
const target = { b: 3, c: 4 };
|
|
322
|
-
const result = objMerge(source, target);
|
|
323
|
-
|
|
324
|
-
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("중첩된 객체를 병합한다", () => {
|
|
328
|
-
const source = { a: { b: 1, c: 2 } };
|
|
329
|
-
const target = { a: { c: 3, d: 4 } };
|
|
330
|
-
const result = objMerge(source, target);
|
|
331
|
-
|
|
332
|
-
expect(result).toEqual({ a: { b: 1, c: 3, d: 4 } });
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it("arrayProcess: replace로 배열을 대체한다", () => {
|
|
336
|
-
const source = { arr: [1, 2, 3] };
|
|
337
|
-
const target = { arr: [4, 5] };
|
|
338
|
-
const result = objMerge(source, target, { arrayProcess: "replace" });
|
|
339
|
-
|
|
340
|
-
expect(result.arr).toEqual([4, 5]);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("arrayProcess: concat으로 배열을 합친다", () => {
|
|
344
|
-
const source = { arr: [1, 2, 3] };
|
|
345
|
-
const target = { arr: [3, 4, 5] };
|
|
346
|
-
const result = objMerge(source, target, { arrayProcess: "concat" });
|
|
347
|
-
|
|
348
|
-
// Set으로 중복 제거됨
|
|
349
|
-
expect(result.arr).toEqual([1, 2, 3, 4, 5]);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it("useDelTargetNull로 null이면 삭제한다", () => {
|
|
353
|
-
const source = { a: 1, b: 2 };
|
|
354
|
-
const target = { b: null };
|
|
355
|
-
const result = objMerge(source, target, { useDelTargetNull: true });
|
|
356
|
-
|
|
357
|
-
expect(result).toEqual({ a: 1 });
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it("source가 object이고 target이 primitive면 target을 반환한다", () => {
|
|
361
|
-
const source = { a: 1 };
|
|
362
|
-
const target = "string";
|
|
363
|
-
|
|
364
|
-
const result = objMerge(source, target as any);
|
|
365
|
-
|
|
366
|
-
expect(result).toBe("string");
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("source가 primitive이고 target이 object면 target을 반환한다", () => {
|
|
370
|
-
const source = "string";
|
|
371
|
-
const target = { a: 1 };
|
|
372
|
-
|
|
373
|
-
const result = objMerge(source as any, target);
|
|
374
|
-
|
|
375
|
-
expect(result).toEqual({ a: 1 });
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it("source가 배열이고 target이 일반 객체면 target을 반환한다", () => {
|
|
379
|
-
const source = [1, 2, 3];
|
|
380
|
-
const target = { a: 1 };
|
|
381
|
-
|
|
382
|
-
const result = objMerge(source as any, target);
|
|
383
|
-
|
|
384
|
-
expect(result).toEqual({ a: 1 });
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("source가 일반 객체이고 target이 배열이면 target을 반환한다", () => {
|
|
388
|
-
const source = { a: 1 };
|
|
389
|
-
const target = [1, 2, 3];
|
|
390
|
-
|
|
391
|
-
const result = objMerge(source as any, target);
|
|
392
|
-
|
|
393
|
-
expect(result).toEqual([1, 2, 3]);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("3단계 이상 중첩된 객체를 병합한다", () => {
|
|
397
|
-
const source = {
|
|
398
|
-
level1: {
|
|
399
|
-
level2: {
|
|
400
|
-
level3: {
|
|
401
|
-
a: 1,
|
|
402
|
-
b: 2,
|
|
403
|
-
},
|
|
404
|
-
x: 10,
|
|
405
|
-
},
|
|
406
|
-
y: 20,
|
|
407
|
-
},
|
|
408
|
-
z: 30,
|
|
409
|
-
};
|
|
410
|
-
const target = {
|
|
411
|
-
level1: {
|
|
412
|
-
level2: {
|
|
413
|
-
level3: {
|
|
414
|
-
b: 3,
|
|
415
|
-
c: 4,
|
|
416
|
-
},
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const result = objMerge(source, target);
|
|
422
|
-
|
|
423
|
-
expect(result).toEqual({
|
|
424
|
-
level1: {
|
|
425
|
-
level2: {
|
|
426
|
-
level3: {
|
|
427
|
-
a: 1,
|
|
428
|
-
b: 3,
|
|
429
|
-
c: 4,
|
|
430
|
-
},
|
|
431
|
-
x: 10,
|
|
432
|
-
},
|
|
433
|
-
y: 20,
|
|
434
|
-
},
|
|
435
|
-
z: 30,
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it("4단계 중첩에서 깊은 값만 변경한다", () => {
|
|
440
|
-
const source = {
|
|
441
|
-
a: {
|
|
442
|
-
b: {
|
|
443
|
-
c: {
|
|
444
|
-
d: { value: 1 },
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
};
|
|
449
|
-
const target = {
|
|
450
|
-
a: {
|
|
451
|
-
b: {
|
|
452
|
-
c: {
|
|
453
|
-
d: { value: 2 },
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const result = objMerge(source, target);
|
|
460
|
-
|
|
461
|
-
expect(result.a.b.c.d.value).toBe(2);
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it("Map 병합 시 target의 새 키-값이 clone된다", () => {
|
|
465
|
-
const sourceMap = new Map<string, { value: number }>([["key1", { value: 1 }]]);
|
|
466
|
-
const targetObj = { value: 2 };
|
|
467
|
-
const targetMap = new Map<string, { value: number }>([["key2", targetObj]]);
|
|
468
|
-
|
|
469
|
-
const result = objMerge(sourceMap, targetMap);
|
|
470
|
-
|
|
471
|
-
// key2의 값이 clone되어 원본과 다른 참조여야 함
|
|
472
|
-
expect(result.get("key2")).toEqual({ value: 2 });
|
|
473
|
-
expect(result.get("key2")).not.toBe(targetObj);
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
describe("objMerge3()", () => {
|
|
478
|
-
it("source만 변경된 경우 source 값을 사용한다", () => {
|
|
479
|
-
const origin = { a: 1, b: 2 };
|
|
480
|
-
const source = { a: 1, b: 3 };
|
|
481
|
-
const target = { a: 1, b: 2 };
|
|
482
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
483
|
-
|
|
484
|
-
expect(conflict).toBe(false);
|
|
485
|
-
expect(result).toEqual({ a: 1, b: 3 });
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
it("target만 변경된 경우 target 값을 사용한다", () => {
|
|
489
|
-
const origin = { a: 1, b: 2 };
|
|
490
|
-
const source = { a: 1, b: 2 };
|
|
491
|
-
const target = { a: 1, b: 4 };
|
|
492
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
493
|
-
|
|
494
|
-
expect(conflict).toBe(false);
|
|
495
|
-
expect(result).toEqual({ a: 1, b: 4 });
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it("source와 target이 같은 값으로 변경된 경우 충돌 없이 해당 값을 사용한다", () => {
|
|
499
|
-
const origin = { a: 1, b: 2 };
|
|
500
|
-
const source = { a: 1, b: 5 };
|
|
501
|
-
const target = { a: 1, b: 5 };
|
|
502
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
503
|
-
|
|
504
|
-
expect(conflict).toBe(false);
|
|
505
|
-
expect(result).toEqual({ a: 1, b: 5 });
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it("source와 target이 다른 값으로 변경된 경우 충돌을 반환한다", () => {
|
|
509
|
-
const origin = { a: 1, b: 2 };
|
|
510
|
-
const source = { a: 1, b: 3 };
|
|
511
|
-
const target = { a: 1, b: 4 };
|
|
512
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
513
|
-
|
|
514
|
-
expect(conflict).toBe(true);
|
|
515
|
-
// origin 값 유지
|
|
516
|
-
expect(result.b).toBe(2);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it("여러 키에서 일부만 충돌하면 충돌을 반환한다", () => {
|
|
520
|
-
const origin = { a: 1, b: 2, c: 3 };
|
|
521
|
-
const source = { a: 10, b: 20, c: 3 };
|
|
522
|
-
const target = { a: 1, b: 30, c: 4 };
|
|
523
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
524
|
-
|
|
525
|
-
expect(conflict).toBe(true);
|
|
526
|
-
expect(result.a).toBe(10); // source만 변경
|
|
527
|
-
expect(result.b).toBe(2); // 둘 다 다르게 변경 → 충돌 → origin 유지
|
|
528
|
-
expect(result.c).toBe(4); // target만 변경
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it("중첩된 객체에서 충돌을 감지한다", () => {
|
|
532
|
-
const origin = { a: { b: 1, c: 2 } };
|
|
533
|
-
const source = { a: { b: 10, c: 2 } };
|
|
534
|
-
const target = { a: { b: 20, c: 2 } };
|
|
535
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
536
|
-
|
|
537
|
-
expect(conflict).toBe(true);
|
|
538
|
-
expect(result.a.b).toBe(1); // 둘 다 다르게 변경 → 충돌 → origin 유지
|
|
539
|
-
expect(result.a.c).toBe(2);
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it("중첩된 객체에서 각각 다른 내부 키가 변경되어도 객체 단위로 비교하므로 충돌로 감지된다", () => {
|
|
543
|
-
// merge3는 키 단위로 비교하므로, { a: {...} } 전체를 비교함
|
|
544
|
-
// source.a와 origin.a가 다르고, target.a와 origin.a도 다르면 충돌
|
|
545
|
-
const origin = { a: { b: 1, c: 2 } };
|
|
546
|
-
const source = { a: { b: 10, c: 2 } };
|
|
547
|
-
const target = { a: { b: 1, c: 20 } };
|
|
548
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
549
|
-
|
|
550
|
-
expect(conflict).toBe(true);
|
|
551
|
-
expect(result.a.b).toBe(1); // 충돌 시 origin 유지
|
|
552
|
-
expect(result.a.c).toBe(2);
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
it("배열에서 충돌을 감지한다", () => {
|
|
556
|
-
const origin = { arr: [1, 2, 3] };
|
|
557
|
-
const source = { arr: [1, 2, 4] };
|
|
558
|
-
const target = { arr: [1, 2, 5] };
|
|
559
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
560
|
-
|
|
561
|
-
expect(conflict).toBe(true);
|
|
562
|
-
expect(result.arr).toEqual([1, 2, 3]); // 충돌 시 origin 유지
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
it("primitive 값에서 충돌을 감지한다", () => {
|
|
566
|
-
const origin = { value: "original" };
|
|
567
|
-
const source = { value: "from source" };
|
|
568
|
-
const target = { value: "from target" };
|
|
569
|
-
const { conflict, result } = objMerge3(source, origin, target);
|
|
570
|
-
|
|
571
|
-
expect(conflict).toBe(true);
|
|
572
|
-
expect(result.value).toBe("original"); // 충돌 시 origin 유지
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
//#endregion
|
|
577
|
-
|
|
578
|
-
//#region omit / pick
|
|
579
|
-
|
|
580
|
-
describe("objOmit()", () => {
|
|
581
|
-
it("특정 키들을 제외한다", () => {
|
|
582
|
-
const obj = { a: 1, b: 2, c: 3 };
|
|
583
|
-
const result = objOmit(obj, ["b"]);
|
|
584
|
-
|
|
585
|
-
expect(result).toEqual({ a: 1, c: 3 });
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it("여러 키를 제외한다", () => {
|
|
589
|
-
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
|
590
|
-
const result = objOmit(obj, ["a", "c"]);
|
|
591
|
-
|
|
592
|
-
expect(result).toEqual({ b: 2, d: 4 });
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
describe("objOmitByFilter()", () => {
|
|
597
|
-
it("조건에 맞는 키를 제외한다", () => {
|
|
598
|
-
const obj = { a: 1, b: 2, c: 3 };
|
|
599
|
-
const result = objOmitByFilter(obj, (key) => key === "b");
|
|
600
|
-
|
|
601
|
-
expect(result).toEqual({ a: 1, c: 3 });
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
describe("objPick()", () => {
|
|
606
|
-
it("특정 키들만 선택한다", () => {
|
|
607
|
-
const obj = { a: 1, b: 2, c: 3 };
|
|
608
|
-
const result = objPick(obj, ["a", "c"]);
|
|
609
|
-
|
|
610
|
-
expect(result).toEqual({ a: 1, c: 3 });
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
//#endregion
|
|
615
|
-
|
|
616
|
-
//#region chain value
|
|
617
|
-
|
|
618
|
-
describe("objGetChainValue()", () => {
|
|
619
|
-
it("점 표기법으로 값을 가져온다", () => {
|
|
620
|
-
const obj = { a: { b: { c: 1 } } };
|
|
621
|
-
|
|
622
|
-
expect(objGetChainValue(obj, "a.b.c")).toBe(1);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it("배열 표기법으로 값을 가져온다", () => {
|
|
626
|
-
const obj = { arr: [{ name: "first" }, { name: "second" }] };
|
|
627
|
-
|
|
628
|
-
expect(objGetChainValue(obj, "arr[1].name")).toBe("second");
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it("optional: true로 없는 경로는 undefined를 반환한다", () => {
|
|
632
|
-
const obj = { a: 1 };
|
|
633
|
-
|
|
634
|
-
expect(objGetChainValue(obj, "b.c.d", true)).toBe(undefined);
|
|
635
|
-
});
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
describe("objGetChainValueByDepth()", () => {
|
|
639
|
-
it("depth만큼 같은 키로 내려간다", () => {
|
|
640
|
-
const obj = {
|
|
641
|
-
parent: {
|
|
642
|
-
parent: {
|
|
643
|
-
parent: {
|
|
644
|
-
name: "leaf",
|
|
645
|
-
},
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
const result = objGetChainValueByDepth(obj, "parent", 2);
|
|
651
|
-
|
|
652
|
-
expect(result).toEqual({ parent: { name: "leaf" } });
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it("depth가 0이면 에러를 던진다", () => {
|
|
656
|
-
const obj = { parent: { name: "child" } };
|
|
657
|
-
|
|
658
|
-
expect(() => objGetChainValueByDepth(obj, "parent", 0)).toThrow("depth는 1 이상이어야 합니다.");
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
it("depth가 1이면 한 단계만 내려간다", () => {
|
|
662
|
-
const obj = { parent: { name: "child" } };
|
|
663
|
-
|
|
664
|
-
const result = objGetChainValueByDepth(obj, "parent", 1);
|
|
665
|
-
|
|
666
|
-
expect(result).toEqual({ name: "child" });
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
it("optional: true로 중간 경로가 없으면 undefined를 반환한다", () => {
|
|
670
|
-
const obj = { parent: { name: "child" } };
|
|
671
|
-
|
|
672
|
-
const result = objGetChainValueByDepth(obj, "parent", 5, true);
|
|
673
|
-
|
|
674
|
-
expect(result).toBe(undefined);
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it("optional 없이 중간 경로가 없으면 에러 발생 가능", () => {
|
|
678
|
-
const obj = { parent: undefined as unknown };
|
|
679
|
-
|
|
680
|
-
// optional이 없으면 undefined에서 키를 접근하려 해서 에러 발생 가능
|
|
681
|
-
// 하지만 현재 구현에서는 result == null 체크가 optional 조건 내에서만 동작
|
|
682
|
-
// 따라서 optional 없이 사용 시 주의 필요
|
|
683
|
-
expect(() => objGetChainValueByDepth(obj as any, "parent", 2)).toThrow();
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
describe("objSetChainValue()", () => {
|
|
688
|
-
it("점 표기법으로 값을 설정한다", () => {
|
|
689
|
-
const obj: Record<string, unknown> = {};
|
|
690
|
-
objSetChainValue(obj, "a.b.c", 1);
|
|
691
|
-
|
|
692
|
-
expect(obj).toEqual({ a: { b: { c: 1 } } });
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it("기존 값을 덮어쓴다", () => {
|
|
696
|
-
const obj = { a: { b: { c: 1 } } };
|
|
697
|
-
objSetChainValue(obj, "a.b.c", 2);
|
|
698
|
-
|
|
699
|
-
expect(obj.a.b.c).toBe(2);
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it("빈 체인은 에러를 던진다", () => {
|
|
703
|
-
const obj: Record<string, unknown> = {};
|
|
704
|
-
|
|
705
|
-
expect(() => objSetChainValue(obj, "", 1)).toThrow();
|
|
706
|
-
});
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
describe("objDeleteChainValue()", () => {
|
|
710
|
-
it("체인 경로의 값을 삭제한다", () => {
|
|
711
|
-
const obj = { a: { b: { c: 1, d: 2 } } };
|
|
712
|
-
objDeleteChainValue(obj, "a.b.c");
|
|
713
|
-
|
|
714
|
-
expect(obj.a.b).toEqual({ d: 2 });
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
it("존재하지 않는 경로는 에러 없이 무시한다", () => {
|
|
718
|
-
const obj = { a: 1 };
|
|
719
|
-
|
|
720
|
-
// 중간 경로가 없어도 에러 없음
|
|
721
|
-
expect(() => objDeleteChainValue(obj, "b.c.d")).not.toThrow();
|
|
722
|
-
expect(obj).toEqual({ a: 1 });
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
it("중간 경로가 undefined여도 에러 없이 무시한다", () => {
|
|
726
|
-
const obj: Record<string, unknown> = { a: undefined };
|
|
727
|
-
|
|
728
|
-
expect(() => objDeleteChainValue(obj, "a.b.c")).not.toThrow();
|
|
729
|
-
expect(obj).toEqual({ a: undefined });
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
it("중간 경로가 null이어도 에러 없이 무시한다", () => {
|
|
733
|
-
const obj: Record<string, unknown> = { a: null };
|
|
734
|
-
|
|
735
|
-
expect(() => objDeleteChainValue(obj, "a.b.c")).not.toThrow();
|
|
736
|
-
expect(obj).toEqual({ a: null });
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it("배열 인덱스 경로도 삭제한다", () => {
|
|
740
|
-
const obj = { arr: [{ name: "first" }, { name: "second" }] };
|
|
741
|
-
objDeleteChainValue(obj, "arr[0].name");
|
|
742
|
-
|
|
743
|
-
expect(obj.arr[0]).toEqual({});
|
|
744
|
-
expect(obj.arr[1]).toEqual({ name: "second" });
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it("빈 체인은 에러를 던진다", () => {
|
|
748
|
-
const obj = { a: 1 };
|
|
749
|
-
|
|
750
|
-
expect(() => objDeleteChainValue(obj, "")).toThrow();
|
|
751
|
-
});
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
//#endregion
|
|
755
|
-
|
|
756
|
-
//#region clear / transform
|
|
757
|
-
|
|
758
|
-
describe("objClearUndefined()", () => {
|
|
759
|
-
it("undefined 값을 가진 키를 삭제한다", () => {
|
|
760
|
-
const obj = { a: 1, b: undefined, c: 3 };
|
|
761
|
-
const result = objClearUndefined(obj);
|
|
762
|
-
|
|
763
|
-
expect(result).toEqual({ a: 1, c: 3 });
|
|
764
|
-
expect("b" in result).toBe(false);
|
|
765
|
-
});
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
describe("objClear()", () => {
|
|
769
|
-
it("모든 키를 삭제한다", () => {
|
|
770
|
-
const obj = { a: 1, b: 2, c: 3 };
|
|
771
|
-
const result = objClear(obj);
|
|
772
|
-
|
|
773
|
-
expect(Object.keys(result)).toHaveLength(0);
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
describe("objNullToUndefined()", () => {
|
|
778
|
-
it("null을 undefined로 변환한다", () => {
|
|
779
|
-
expect(objNullToUndefined(null)).toBe(undefined);
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
it("중첩된 null도 변환한다", () => {
|
|
783
|
-
const obj = { a: 1, b: null, c: { d: null } };
|
|
784
|
-
const result = objNullToUndefined(obj);
|
|
785
|
-
|
|
786
|
-
expect(result).toEqual({ a: 1, b: undefined, c: { d: undefined } });
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
it("배열의 null도 변환한다", () => {
|
|
790
|
-
const arr = [1, null, { a: null }];
|
|
791
|
-
const result = objNullToUndefined(arr);
|
|
792
|
-
|
|
793
|
-
expect(result).toEqual([1, undefined, { a: undefined }]);
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
it("순환 참조가 있는 객체를 안전하게 처리한다", () => {
|
|
797
|
-
const obj: Record<string, unknown> = { a: null };
|
|
798
|
-
obj.self = obj;
|
|
799
|
-
const result = objNullToUndefined(obj);
|
|
800
|
-
expect(result).toBeDefined();
|
|
801
|
-
expect((result as Record<string, unknown>).a).toBeUndefined();
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it("순환 참조가 있는 배열을 안전하게 처리한다", () => {
|
|
805
|
-
const arr: unknown[] = [null, 1];
|
|
806
|
-
arr.push(arr);
|
|
807
|
-
const result = objNullToUndefined(arr);
|
|
808
|
-
expect(result).toBeDefined();
|
|
809
|
-
expect((result as unknown[])[0]).toBeUndefined();
|
|
810
|
-
expect((result as unknown[])[1]).toBe(1);
|
|
811
|
-
});
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
describe("objUnflatten()", () => {
|
|
815
|
-
it("flat된 객체를 nested로 변환한다", () => {
|
|
816
|
-
const flat = { "a.b.c": 1, "a.b.d": 2, "e": 3 };
|
|
817
|
-
const result = objUnflatten(flat);
|
|
818
|
-
|
|
819
|
-
expect(result).toEqual({
|
|
820
|
-
a: { b: { c: 1, d: 2 } },
|
|
821
|
-
e: 3,
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
//#endregion
|
|
827
|
-
});
|