@simplysm/core-common 13.0.69 → 13.0.71

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 (151) hide show
  1. package/README.md +66 -267
  2. package/dist/common.types.d.ts +14 -14
  3. package/dist/errors/argument-error.d.ts +10 -10
  4. package/dist/errors/argument-error.d.ts.map +1 -1
  5. package/dist/errors/argument-error.js +2 -2
  6. package/dist/errors/argument-error.js.map +1 -1
  7. package/dist/errors/not-implemented-error.d.ts +8 -8
  8. package/dist/errors/not-implemented-error.js +2 -2
  9. package/dist/errors/not-implemented-error.js.map +1 -1
  10. package/dist/errors/sd-error.d.ts +10 -10
  11. package/dist/errors/sd-error.d.ts.map +1 -1
  12. package/dist/errors/timeout-error.d.ts +10 -10
  13. package/dist/errors/timeout-error.js +3 -3
  14. package/dist/errors/timeout-error.js.map +1 -1
  15. package/dist/extensions/arr-ext.d.ts +2 -2
  16. package/dist/extensions/arr-ext.helpers.d.ts +8 -8
  17. package/dist/extensions/arr-ext.helpers.js +1 -1
  18. package/dist/extensions/arr-ext.helpers.js.map +1 -1
  19. package/dist/extensions/arr-ext.js +13 -13
  20. package/dist/extensions/arr-ext.js.map +1 -1
  21. package/dist/extensions/arr-ext.types.d.ts +57 -57
  22. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  23. package/dist/extensions/map-ext.d.ts +16 -16
  24. package/dist/extensions/set-ext.d.ts +11 -11
  25. package/dist/features/debounce-queue.d.ts +17 -15
  26. package/dist/features/debounce-queue.d.ts.map +1 -1
  27. package/dist/features/debounce-queue.js +6 -6
  28. package/dist/features/debounce-queue.js.map +1 -1
  29. package/dist/features/event-emitter.d.ts +20 -20
  30. package/dist/features/event-emitter.js +17 -17
  31. package/dist/features/serial-queue.d.ts +11 -11
  32. package/dist/features/serial-queue.js +5 -5
  33. package/dist/features/serial-queue.js.map +1 -1
  34. package/dist/globals.d.ts +4 -4
  35. package/dist/types/date-only.d.ts +64 -64
  36. package/dist/types/date-only.d.ts.map +1 -1
  37. package/dist/types/date-only.js +63 -63
  38. package/dist/types/date-time.d.ts +37 -37
  39. package/dist/types/date-time.d.ts.map +1 -1
  40. package/dist/types/date-time.js +54 -37
  41. package/dist/types/date-time.js.map +1 -1
  42. package/dist/types/lazy-gc-map.d.ts +26 -26
  43. package/dist/types/lazy-gc-map.d.ts.map +1 -1
  44. package/dist/types/lazy-gc-map.js +26 -26
  45. package/dist/types/lazy-gc-map.js.map +1 -1
  46. package/dist/types/time.d.ts +25 -25
  47. package/dist/types/time.d.ts.map +1 -1
  48. package/dist/types/time.js +25 -25
  49. package/dist/types/time.js.map +1 -1
  50. package/dist/types/uuid.d.ts +11 -11
  51. package/dist/types/uuid.d.ts.map +1 -1
  52. package/dist/types/uuid.js +12 -12
  53. package/dist/types/uuid.js.map +1 -1
  54. package/dist/utils/bytes.d.ts +17 -17
  55. package/dist/utils/bytes.js +4 -4
  56. package/dist/utils/bytes.js.map +1 -1
  57. package/dist/utils/date-format.d.ts +45 -45
  58. package/dist/utils/date-format.js +1 -1
  59. package/dist/utils/date-format.js.map +1 -1
  60. package/dist/utils/error.d.ts +4 -4
  61. package/dist/utils/json.d.ts +17 -17
  62. package/dist/utils/json.js +3 -3
  63. package/dist/utils/json.js.map +1 -1
  64. package/dist/utils/num.d.ts +23 -23
  65. package/dist/utils/obj.d.ts +111 -111
  66. package/dist/utils/obj.d.ts.map +1 -1
  67. package/dist/utils/obj.js +3 -3
  68. package/dist/utils/obj.js.map +1 -1
  69. package/dist/utils/path.d.ts +10 -10
  70. package/dist/utils/primitive.d.ts +5 -5
  71. package/dist/utils/primitive.js +1 -1
  72. package/dist/utils/primitive.js.map +1 -1
  73. package/dist/utils/str.d.ts +46 -46
  74. package/dist/utils/str.d.ts.map +1 -1
  75. package/dist/utils/str.js +5 -5
  76. package/dist/utils/str.js.map +1 -1
  77. package/dist/utils/template-strings.d.ts +26 -26
  78. package/dist/utils/transferable.d.ts +18 -18
  79. package/dist/utils/transferable.js +1 -1
  80. package/dist/utils/transferable.js.map +1 -1
  81. package/dist/utils/wait.d.ts +9 -9
  82. package/dist/utils/xml.d.ts +13 -13
  83. package/dist/utils/xml.d.ts.map +1 -1
  84. package/dist/utils/xml.js +1 -0
  85. package/dist/utils/xml.js.map +1 -1
  86. package/dist/zip/sd-zip.d.ts +22 -22
  87. package/dist/zip/sd-zip.js +16 -16
  88. package/package.json +4 -4
  89. package/src/common.types.ts +17 -17
  90. package/src/errors/argument-error.ts +15 -15
  91. package/src/errors/not-implemented-error.ts +9 -9
  92. package/src/errors/sd-error.ts +12 -12
  93. package/src/errors/timeout-error.ts +12 -12
  94. package/src/extensions/arr-ext.helpers.ts +10 -10
  95. package/src/extensions/arr-ext.ts +57 -57
  96. package/src/extensions/arr-ext.types.ts +59 -59
  97. package/src/extensions/map-ext.ts +16 -16
  98. package/src/extensions/set-ext.ts +11 -11
  99. package/src/features/debounce-queue.ts +21 -19
  100. package/src/features/event-emitter.ts +25 -25
  101. package/src/features/serial-queue.ts +13 -13
  102. package/src/globals.ts +4 -4
  103. package/src/index.ts +1 -1
  104. package/src/types/date-only.ts +83 -83
  105. package/src/types/date-time.ts +64 -44
  106. package/src/types/lazy-gc-map.ts +45 -45
  107. package/src/types/time.ts +34 -34
  108. package/src/types/uuid.ts +17 -17
  109. package/src/utils/bytes.ts +35 -35
  110. package/src/utils/date-format.ts +65 -65
  111. package/src/utils/error.ts +4 -4
  112. package/src/utils/json.ts +39 -39
  113. package/src/utils/num.ts +23 -23
  114. package/src/utils/obj.ts +138 -138
  115. package/src/utils/path.ts +10 -10
  116. package/src/utils/primitive.ts +6 -6
  117. package/src/utils/str.ts +260 -261
  118. package/src/utils/template-strings.ts +29 -29
  119. package/src/utils/transferable.ts +284 -284
  120. package/src/utils/wait.ts +10 -10
  121. package/src/utils/xml.ts +20 -19
  122. package/src/zip/sd-zip.ts +25 -25
  123. package/tests/errors/errors.spec.ts +80 -0
  124. package/tests/extensions/array-extension.spec.ts +796 -0
  125. package/tests/extensions/map-extension.spec.ts +147 -0
  126. package/tests/extensions/set-extension.spec.ts +74 -0
  127. package/tests/types/date-only.spec.ts +638 -0
  128. package/tests/types/date-time.spec.ts +391 -0
  129. package/tests/types/lazy-gc-map.spec.ts +692 -0
  130. package/tests/types/time.spec.ts +559 -0
  131. package/tests/types/uuid.spec.ts +74 -0
  132. package/tests/utils/bytes-utils.spec.ts +230 -0
  133. package/tests/utils/date-format.spec.ts +373 -0
  134. package/tests/utils/debounce-queue.spec.ts +272 -0
  135. package/tests/utils/json.spec.ts +486 -0
  136. package/tests/utils/number.spec.ts +157 -0
  137. package/tests/utils/object.spec.ts +829 -0
  138. package/tests/utils/path.spec.ts +78 -0
  139. package/tests/utils/primitive.spec.ts +43 -0
  140. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  141. package/tests/utils/serial-queue.spec.ts +365 -0
  142. package/tests/utils/string.spec.ts +281 -0
  143. package/tests/utils/template-strings.spec.ts +57 -0
  144. package/tests/utils/transferable.spec.ts +703 -0
  145. package/tests/utils/wait.spec.ts +145 -0
  146. package/tests/utils/xml.spec.ts +146 -0
  147. package/tests/zip/sd-zip.spec.ts +238 -0
  148. package/docs/extensions.md +0 -503
  149. package/docs/features.md +0 -109
  150. package/docs/types.md +0 -486
  151. package/docs/utils.md +0 -780
@@ -0,0 +1,692 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { LazyGcMap } from "@simplysm/core-common";
3
+
4
+ describe("LazyGcMap", () => {
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ });
8
+
9
+ afterEach(() => {
10
+ vi.useRealTimers();
11
+ });
12
+
13
+ //#region Basic Map operations
14
+
15
+ describe("Basic Map operations", () => {
16
+ it("Stores and retrieves values with set/get", () => {
17
+ const map = new LazyGcMap<string, number>({
18
+ gcInterval: 1000,
19
+ expireTime: 5000,
20
+ });
21
+
22
+ map.set("key1", 100);
23
+ expect(map.get("key1")).toBe(100);
24
+ });
25
+
26
+ it("Checks key existence with has", () => {
27
+ const map = new LazyGcMap<string, number>({
28
+ gcInterval: 1000,
29
+ expireTime: 5000,
30
+ });
31
+
32
+ map.set("key1", 100);
33
+ expect(map.has("key1")).toBe(true);
34
+ expect(map.has("key2")).toBe(false);
35
+ });
36
+
37
+ it("Deletes values with delete", () => {
38
+ const map = new LazyGcMap<string, number>({
39
+ gcInterval: 1000,
40
+ expireTime: 5000,
41
+ });
42
+
43
+ map.set("key1", 100);
44
+ expect(map.delete("key1")).toBe(true);
45
+ expect(map.has("key1")).toBe(false);
46
+ expect(map.delete("key1")).toBe(false); // Already deleted
47
+ });
48
+
49
+ it("Deletes all values with dispose", () => {
50
+ const map = new LazyGcMap<string, number>({
51
+ gcInterval: 1000,
52
+ expireTime: 5000,
53
+ });
54
+
55
+ map.set("key1", 100);
56
+ map.set("key2", 200);
57
+ expect(map.size).toBe(2);
58
+
59
+ map.dispose();
60
+ expect(map.size).toBe(0);
61
+ expect(map.has("key1")).toBe(false);
62
+ expect(map.has("key2")).toBe(false);
63
+ });
64
+
65
+ it("Gets size with size property", () => {
66
+ const map = new LazyGcMap<string, number>({
67
+ gcInterval: 1000,
68
+ expireTime: 5000,
69
+ });
70
+
71
+ expect(map.size).toBe(0);
72
+ map.set("key1", 100);
73
+ expect(map.size).toBe(1);
74
+ map.set("key2", 200);
75
+ expect(map.size).toBe(2);
76
+ map.delete("key1");
77
+ expect(map.size).toBe(1);
78
+ });
79
+
80
+ it("Returns undefined for non-existent keys", () => {
81
+ const map = new LazyGcMap<string, number>({
82
+ gcInterval: 1000,
83
+ expireTime: 5000,
84
+ });
85
+
86
+ expect(map.get("nonexistent")).toBe(undefined);
87
+ });
88
+ });
89
+
90
+ //#endregion
91
+
92
+ //#region getOrCreate
93
+
94
+ describe("getOrCreate()", () => {
95
+ it("Creates new value with factory for non-existent keys", () => {
96
+ const map = new LazyGcMap<string, number>({
97
+ gcInterval: 1000,
98
+ expireTime: 5000,
99
+ });
100
+
101
+ const value = map.getOrCreate("key1", () => 100);
102
+ expect(value).toBe(100);
103
+ expect(map.get("key1")).toBe(100);
104
+ });
105
+
106
+ it("Returns existing value without calling factory", () => {
107
+ const map = new LazyGcMap<string, number>({
108
+ gcInterval: 1000,
109
+ expireTime: 5000,
110
+ });
111
+
112
+ map.set("key1", 100);
113
+ const factoryCalls: number[] = [];
114
+ const value = map.getOrCreate("key1", () => {
115
+ factoryCalls.push(1);
116
+ return 200;
117
+ });
118
+
119
+ expect(value).toBe(100); // Existing value
120
+ expect(factoryCalls).toHaveLength(0); // Factory not called
121
+ });
122
+
123
+ it("Factory creates new value each time for different keys", () => {
124
+ const map = new LazyGcMap<string, { id: number }>({
125
+ gcInterval: 1000,
126
+ expireTime: 5000,
127
+ });
128
+
129
+ const value1 = map.getOrCreate("key1", () => ({ id: 1 }));
130
+ const value2 = map.getOrCreate("key2", () => ({ id: 2 }));
131
+
132
+ expect(value1).toEqual({ id: 1 });
133
+ expect(value2).toEqual({ id: 2 });
134
+ expect(value1).not.toBe(value2);
135
+ });
136
+ });
137
+
138
+ //#endregion
139
+
140
+ //#region Automatic expiration (GC)
141
+
142
+ describe("Automatic expiration (GC)", () => {
143
+ it("Automatically deletes values after expireTime without access", async () => {
144
+ const map = new LazyGcMap<string, number>({
145
+ gcInterval: 100, // GC every 100ms
146
+ expireTime: 200, // Expire after 200ms
147
+ });
148
+
149
+ map.set("key1", 100);
150
+ expect(map.has("key1")).toBe(true);
151
+
152
+ // GC runs after expireTime(200) + gcInterval(100) = 300ms
153
+ await vi.advanceTimersByTimeAsync(350);
154
+
155
+ expect(map.has("key1")).toBe(false);
156
+ });
157
+
158
+ it("Refreshes expiration time on access (LRU)", async () => {
159
+ const map = new LazyGcMap<string, number>({
160
+ gcInterval: 100,
161
+ expireTime: 200,
162
+ });
163
+
164
+ map.set("key1", 100);
165
+
166
+ // Access every 150ms (less than expireTime 200ms)
167
+ await vi.advanceTimersByTimeAsync(150);
168
+ map.get("key1"); // Access refreshes time
169
+
170
+ await vi.advanceTimersByTimeAsync(150);
171
+ map.get("key1"); // Access refreshes time
172
+
173
+ await vi.advanceTimersByTimeAsync(150);
174
+
175
+ // Continuous access prevents expiration
176
+ expect(map.has("key1")).toBe(true);
177
+ });
178
+
179
+ it("has() does not refresh access time", async () => {
180
+ const map = new LazyGcMap<string, number>({
181
+ gcInterval: 100,
182
+ expireTime: 200,
183
+ });
184
+
185
+ map.set("key1", 100);
186
+
187
+ // Call has() every 150ms (not get())
188
+ await vi.advanceTimersByTimeAsync(150);
189
+ map.has("key1"); // has() does not refresh access time
190
+
191
+ await vi.advanceTimersByTimeAsync(150);
192
+ map.has("key1"); // has() does not refresh access time
193
+
194
+ await vi.advanceTimersByTimeAsync(100);
195
+
196
+ // has() does not refresh, so expires
197
+ expect(map.has("key1")).toBe(false);
198
+ });
199
+
200
+ it("getOrCreate also refreshes access time", async () => {
201
+ const map = new LazyGcMap<string, number>({
202
+ gcInterval: 100,
203
+ expireTime: 200,
204
+ });
205
+
206
+ map.set("key1", 100);
207
+
208
+ await vi.advanceTimersByTimeAsync(150);
209
+ map.getOrCreate("key1", () => 200); // Access refreshes time
210
+
211
+ await vi.advanceTimersByTimeAsync(150);
212
+
213
+ expect(map.has("key1")).toBe(true);
214
+ });
215
+
216
+ it("Deletes only expired items among multiple items", async () => {
217
+ const map = new LazyGcMap<string, number>({
218
+ gcInterval: 100,
219
+ expireTime: 200,
220
+ });
221
+
222
+ map.set("key1", 100);
223
+ await vi.advanceTimersByTimeAsync(150);
224
+ map.set("key2", 200); // Added 150ms after key1
225
+
226
+ await vi.advanceTimersByTimeAsync(200);
227
+
228
+ // key1 expires, key2 still alive
229
+ expect(map.has("key1")).toBe(false);
230
+ expect(map.has("key2")).toBe(true);
231
+ });
232
+
233
+ it("GC timer stops when all items expire", async () => {
234
+ const map = new LazyGcMap<string, number>({
235
+ gcInterval: 100,
236
+ expireTime: 200,
237
+ });
238
+
239
+ map.set("key1", 100);
240
+
241
+ // Wait for expiration
242
+ await vi.advanceTimersByTimeAsync(350);
243
+
244
+ expect(map.size).toBe(0);
245
+ // Verify GC timer stopped (waiting more is safe)
246
+ await vi.advanceTimersByTimeAsync(200);
247
+ expect(map.size).toBe(0);
248
+ });
249
+ });
250
+
251
+ //#endregion
252
+
253
+ //#region onExpire callback
254
+
255
+ describe("onExpire callback", () => {
256
+ it("Calls onExpire callback when item expires", async () => {
257
+ const expired: Array<[string, number]> = [];
258
+ const map = new LazyGcMap<string, number>({
259
+ gcInterval: 100,
260
+ expireTime: 200,
261
+ onExpire: (key, value) => {
262
+ expired.push([key, value]);
263
+ },
264
+ });
265
+
266
+ map.set("key1", 100);
267
+ await vi.advanceTimersByTimeAsync(350);
268
+
269
+ expect(expired).toEqual([["key1", 100]]);
270
+ });
271
+
272
+ it("Supports async onExpire callback", async () => {
273
+ const expired: Array<[string, number]> = [];
274
+ const map = new LazyGcMap<string, number>({
275
+ gcInterval: 100,
276
+ expireTime: 200,
277
+ onExpire: async (key, value) => {
278
+ await new Promise((r) => setTimeout(r, 10));
279
+ expired.push([key, value]);
280
+ },
281
+ });
282
+
283
+ map.set("key1", 100);
284
+ // expireTime(200) + gcInterval(100) + callback(10) = 310ms
285
+ await vi.advanceTimersByTimeAsync(350);
286
+
287
+ expect(expired).toEqual([["key1", 100]]);
288
+ });
289
+
290
+ it("Calls onExpire for each expired item", async () => {
291
+ const expired: Array<[string, number]> = [];
292
+ const map = new LazyGcMap<string, number>({
293
+ gcInterval: 100,
294
+ expireTime: 200,
295
+ onExpire: (key, value) => {
296
+ expired.push([key, value]);
297
+ },
298
+ });
299
+
300
+ map.set("key1", 100);
301
+ map.set("key2", 200);
302
+ await vi.advanceTimersByTimeAsync(350);
303
+
304
+ expect(expired).toHaveLength(2);
305
+ expect(expired).toContainEqual(["key1", 100]);
306
+ expect(expired).toContainEqual(["key2", 200]);
307
+ });
308
+
309
+ it("Ignores onExpire errors", async () => {
310
+ const expired: Array<[string, number]> = [];
311
+ const map = new LazyGcMap<string, number>({
312
+ gcInterval: 100,
313
+ expireTime: 200,
314
+ onExpire: (key, value) => {
315
+ expired.push([key, value]);
316
+ throw new Error("callback error");
317
+ },
318
+ });
319
+
320
+ map.set("key1", 100);
321
+ await vi.advanceTimersByTimeAsync(350);
322
+
323
+ // Expiration handled normally despite error
324
+ expect(expired).toEqual([["key1", 100]]);
325
+ expect(map.has("key1")).toBe(false);
326
+ });
327
+
328
+ it("Maintains new value if set is called during onExpire for same key", async () => {
329
+ let map: LazyGcMap<string, number>;
330
+ const expired: Array<[string, number]> = [];
331
+
332
+ map = new LazyGcMap<string, number>({
333
+ gcInterval: 100,
334
+ expireTime: 200,
335
+ onExpire: (key, value) => {
336
+ expired.push([key, value]);
337
+ // Register new value for same key during onExpire
338
+ map.set(key, value + 1000);
339
+ },
340
+ });
341
+
342
+ map.set("key1", 100);
343
+ await vi.advanceTimersByTimeAsync(350);
344
+
345
+ // onExpire called, but new value not deleted
346
+ expect(expired).toEqual([["key1", 100]]);
347
+ expect(map.has("key1")).toBe(true);
348
+ expect(map.get("key1")).toBe(1100);
349
+ });
350
+
351
+ it("Unaffected by set during onExpire for different key", async () => {
352
+ let map: LazyGcMap<string, number>;
353
+ const expired: Array<[string, number]> = [];
354
+
355
+ map = new LazyGcMap<string, number>({
356
+ gcInterval: 100,
357
+ expireTime: 200,
358
+ onExpire: (key, value) => {
359
+ expired.push([key, value]);
360
+ // Register new value for different key during onExpire (only for key1)
361
+ if (key === "key1") {
362
+ map.set("key2", 200);
363
+ }
364
+ },
365
+ });
366
+
367
+ map.set("key1", 100);
368
+ // GC runs after expireTime(200) + gcInterval(100) = 300ms
369
+ // key2 not expired yet after being registered
370
+ await vi.advanceTimersByTimeAsync(350);
371
+
372
+ // key1 deleted after expiration
373
+ expect(expired).toEqual([["key1", 100]]);
374
+ expect(map.has("key1")).toBe(false);
375
+ // key2 newly registered (not yet expired)
376
+ expect(map.has("key2")).toBe(true);
377
+ expect(map.get("key2")).toBe(200);
378
+ });
379
+ });
380
+
381
+ //#endregion
382
+
383
+ //#region dispose (Timer and resource cleanup)
384
+
385
+ describe("dispose() - Timer cleanup", () => {
386
+ it("GC callback not called after dispose when timer is cleaned up", async () => {
387
+ const expired: Array<[string, number]> = [];
388
+ const map = new LazyGcMap<string, number>({
389
+ gcInterval: 100,
390
+ expireTime: 200,
391
+ onExpire: (key, value) => {
392
+ expired.push([key, value]);
393
+ },
394
+ });
395
+
396
+ map.set("key1", 100);
397
+ expect(map.has("key1")).toBe(true);
398
+
399
+ // Clean up timer with dispose
400
+ map.dispose();
401
+ expect(map.size).toBe(0);
402
+
403
+ // Wait expireTime + gcInterval or more
404
+ await vi.advanceTimersByTimeAsync(400);
405
+
406
+ // GC callback should not be called (already cleaned by dispose)
407
+ expect(expired).toHaveLength(0);
408
+ });
409
+
410
+ it("set is ignored after dispose", () => {
411
+ const map = new LazyGcMap<string, number>({
412
+ gcInterval: 100,
413
+ expireTime: 200,
414
+ });
415
+
416
+ map.set("key1", 100);
417
+ map.dispose();
418
+
419
+ // set ignored after dispose
420
+ map.set("key2", 200);
421
+ expect(map.has("key2")).toBe(false);
422
+ expect(map.get("key2")).toBeUndefined();
423
+ expect(map.size).toBe(0);
424
+ });
425
+
426
+ it("dispose is safe to call multiple times", () => {
427
+ const map = new LazyGcMap<string, number>({
428
+ gcInterval: 1000,
429
+ expireTime: 5000,
430
+ });
431
+
432
+ map.set("key1", 100);
433
+
434
+ // Safe to call multiple times
435
+ map.dispose();
436
+ map.dispose();
437
+ map.dispose();
438
+
439
+ expect(map.size).toBe(0);
440
+ });
441
+
442
+ it("Automatically disposed with using statement", async () => {
443
+ const expired: Array<[string, number]> = [];
444
+ {
445
+ using map = new LazyGcMap<string, number>({
446
+ gcInterval: 100,
447
+ expireTime: 200,
448
+ onExpire: (key, value) => {
449
+ expired.push([key, value]);
450
+ },
451
+ });
452
+ map.set("key1", 100);
453
+ expect(map.has("key1")).toBe(true);
454
+ } // dispose auto-called at end of using block
455
+ await vi.advanceTimersByTimeAsync(350);
456
+ // Cleaned up by dispose (onExpire not called)
457
+ expect(expired).toHaveLength(0);
458
+ });
459
+ });
460
+
461
+ //#endregion
462
+
463
+ //#region clear
464
+
465
+ describe("clear()", () => {
466
+ it("Deletes all items", () => {
467
+ const map = new LazyGcMap<string, number>({
468
+ gcInterval: 1000,
469
+ expireTime: 5000,
470
+ });
471
+
472
+ map.set("key1", 100);
473
+ map.set("key2", 200);
474
+ expect(map.size).toBe(2);
475
+
476
+ map.clear();
477
+ expect(map.size).toBe(0);
478
+ expect(map.has("key1")).toBe(false);
479
+ expect(map.has("key2")).toBe(false);
480
+ });
481
+
482
+ it("Can add new items after clear", () => {
483
+ const map = new LazyGcMap<string, number>({
484
+ gcInterval: 1000,
485
+ expireTime: 5000,
486
+ });
487
+
488
+ map.set("key1", 100);
489
+ map.clear();
490
+
491
+ map.set("key2", 200);
492
+ expect(map.has("key2")).toBe(true);
493
+ expect(map.get("key2")).toBe(200);
494
+ });
495
+
496
+ it("clear is safe to call multiple times", () => {
497
+ const map = new LazyGcMap<string, number>({
498
+ gcInterval: 1000,
499
+ expireTime: 5000,
500
+ });
501
+
502
+ map.set("key1", 100);
503
+
504
+ // Safe to call multiple times
505
+ map.clear();
506
+ map.clear();
507
+ map.clear();
508
+
509
+ expect(map.size).toBe(0);
510
+ });
511
+
512
+ it("GC works normally after clear", async () => {
513
+ const map = new LazyGcMap<string, number>({
514
+ gcInterval: 100,
515
+ expireTime: 200,
516
+ });
517
+
518
+ map.set("key1", 100);
519
+ map.clear();
520
+
521
+ // Add new item after clear
522
+ map.set("key2", 200);
523
+
524
+ // Verify GC works normally
525
+ await vi.advanceTimersByTimeAsync(350);
526
+ expect(map.has("key2")).toBe(false);
527
+ });
528
+ });
529
+
530
+ //#endregion
531
+
532
+ //#region SharedArrayBuffer support
533
+
534
+ describe("SharedArrayBuffer support", () => {
535
+ it("Can use SharedArrayBuffer as value", () => {
536
+ // SharedArrayBuffer may be disabled for security in some environments
537
+ if (typeof SharedArrayBuffer === "undefined") {
538
+ expect(true).toBe(true); // Skip if not supported
539
+ return;
540
+ }
541
+
542
+ const map = new LazyGcMap<string, SharedArrayBuffer>({
543
+ gcInterval: 1000,
544
+ expireTime: 5000,
545
+ });
546
+
547
+ const buffer = new SharedArrayBuffer(16);
548
+ map.set("key1", buffer);
549
+
550
+ expect(map.get("key1")).toBe(buffer);
551
+ expect(map.get("key1")?.byteLength).toBe(16);
552
+ });
553
+ });
554
+
555
+ //#endregion
556
+
557
+ //#region Iterator
558
+
559
+ describe("Iterator", () => {
560
+ it("Iterates values with values()", () => {
561
+ const map = new LazyGcMap<string, number>({
562
+ gcInterval: 1000,
563
+ expireTime: 5000,
564
+ });
565
+
566
+ map.set("key1", 100);
567
+ map.set("key2", 200);
568
+ map.set("key3", 300);
569
+
570
+ const values = Array.from(map.values());
571
+ expect(values).toHaveLength(3);
572
+ expect(values).toContain(100);
573
+ expect(values).toContain(200);
574
+ expect(values).toContain(300);
575
+ });
576
+
577
+ it("Iterates keys with keys()", () => {
578
+ const map = new LazyGcMap<string, number>({
579
+ gcInterval: 1000,
580
+ expireTime: 5000,
581
+ });
582
+
583
+ map.set("key1", 100);
584
+ map.set("key2", 200);
585
+
586
+ const keys = Array.from(map.keys());
587
+ expect(keys).toEqual(["key1", "key2"]);
588
+ });
589
+
590
+ it("Iterates entries with entries()", () => {
591
+ const map = new LazyGcMap<string, number>({
592
+ gcInterval: 1000,
593
+ expireTime: 5000,
594
+ });
595
+
596
+ map.set("key1", 100);
597
+ map.set("key2", 200);
598
+
599
+ const entries = Array.from(map.entries());
600
+ expect(entries).toEqual([
601
+ ["key1", 100],
602
+ ["key2", 200],
603
+ ]);
604
+ });
605
+ });
606
+
607
+ //#endregion
608
+
609
+ //#region Safety after dispose
610
+
611
+ describe("Safety after dispose", () => {
612
+ it("Returns undefined on get after dispose", () => {
613
+ const map = new LazyGcMap<string, number>({
614
+ gcInterval: 10000,
615
+ expireTime: 60000,
616
+ });
617
+ map.set("a", 1);
618
+ map.dispose();
619
+ expect(map.get("a")).toBeUndefined();
620
+ });
621
+
622
+ it("Returns false on has after dispose", () => {
623
+ const map = new LazyGcMap<string, number>({
624
+ gcInterval: 10000,
625
+ expireTime: 60000,
626
+ });
627
+ map.set("a", 1);
628
+ map.dispose();
629
+ expect(map.has("a")).toBe(false);
630
+ });
631
+
632
+ it("Returns false on delete after dispose", () => {
633
+ const map = new LazyGcMap<string, number>({
634
+ gcInterval: 10000,
635
+ expireTime: 60000,
636
+ });
637
+ map.set("a", 1);
638
+ map.dispose();
639
+ expect(map.delete("a")).toBe(false);
640
+ });
641
+
642
+ it("Throws error on getOrCreate after dispose", () => {
643
+ const map = new LazyGcMap<string, number>({
644
+ gcInterval: 10000,
645
+ expireTime: 60000,
646
+ });
647
+ map.dispose();
648
+ expect(() => map.getOrCreate("a", () => 1)).toThrow();
649
+ });
650
+
651
+ it("clear is safely ignored after dispose without error", () => {
652
+ const map = new LazyGcMap<string, number>({
653
+ gcInterval: 10000,
654
+ expireTime: 60000,
655
+ });
656
+ map.dispose();
657
+ expect(() => map.clear()).not.toThrow();
658
+ });
659
+
660
+ it("Returns empty iterator on values after dispose", () => {
661
+ const map = new LazyGcMap<string, number>({
662
+ gcInterval: 10000,
663
+ expireTime: 60000,
664
+ });
665
+ map.set("a", 1);
666
+ map.dispose();
667
+ expect([...map.values()]).toEqual([]);
668
+ });
669
+
670
+ it("Returns empty iterator on keys after dispose", () => {
671
+ const map = new LazyGcMap<string, number>({
672
+ gcInterval: 10000,
673
+ expireTime: 60000,
674
+ });
675
+ map.set("a", 1);
676
+ map.dispose();
677
+ expect([...map.keys()]).toEqual([]);
678
+ });
679
+
680
+ it("Returns empty iterator on entries after dispose", () => {
681
+ const map = new LazyGcMap<string, number>({
682
+ gcInterval: 10000,
683
+ expireTime: 60000,
684
+ });
685
+ map.set("a", 1);
686
+ map.dispose();
687
+ expect([...map.entries()]).toEqual([]);
688
+ });
689
+ });
690
+
691
+ //#endregion
692
+ });