@osimatic/helpers-js 1.4.23 → 1.4.25

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 (129) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/CHANGELOG +2 -1
  3. package/array.js +2 -2
  4. package/bank.js +3 -3
  5. package/chartjs.js +1 -1
  6. package/contact_details.js +2 -2
  7. package/date_time.js +25 -16
  8. package/draw.js +3 -2
  9. package/duration.js +12 -15
  10. package/event_bus.js +2 -2
  11. package/file.js +1 -1
  12. package/form_helper.js +1 -1
  13. package/http_client.js +2 -0
  14. package/jwt.js +18 -6
  15. package/location.js +6 -2
  16. package/media.js +1 -1
  17. package/number.js +2 -3
  18. package/package.json +28 -2
  19. package/social_network.js +5 -0
  20. package/string.js +11 -2
  21. package/tests/array.test.js +458 -0
  22. package/tests/bank.test.js +158 -0
  23. package/tests/chartjs.test.js +273 -0
  24. package/tests/contact_details.test.js +391 -0
  25. package/tests/date_time/DatePeriod.test.js +179 -0
  26. package/tests/date_time/DateTime.test.js +492 -0
  27. package/tests/date_time/SqlDate.test.js +205 -0
  28. package/tests/date_time/SqlDateTime.test.js +326 -0
  29. package/tests/date_time/SqlTime.test.js +162 -0
  30. package/tests/date_time/TimestampUnix.test.js +262 -0
  31. package/tests/draw.test.js +271 -0
  32. package/tests/duration.test.js +365 -0
  33. package/tests/event_bus.test.js +268 -0
  34. package/tests/file.test.js +358 -0
  35. package/tests/form_date.test.js +417 -0
  36. package/tests/form_helper.test.js +415 -0
  37. package/tests/http_client.test.js +570 -0
  38. package/tests/jwt.test.js +804 -0
  39. package/tests/location.test.js +509 -0
  40. package/tests/media.test.js +458 -0
  41. package/tests/network.test.js +489 -0
  42. package/tests/number.test.js +448 -0
  43. package/tests/open_street_map.test.js +388 -0
  44. package/tests/shopping_cart.test.js +355 -0
  45. package/tests/social_network.test.js +333 -0
  46. package/tests/string.test.js +473 -0
  47. package/tests/user.test.js +204 -0
  48. package/tests/util.test.js +99 -0
  49. package/tests/visitor.test.js +508 -0
  50. package/tmpclaude-00a6-cwd +1 -0
  51. package/tmpclaude-0526-cwd +1 -0
  52. package/tmpclaude-0973-cwd +1 -0
  53. package/tmpclaude-0b61-cwd +1 -0
  54. package/tmpclaude-0fa4-cwd +1 -0
  55. package/tmpclaude-104f-cwd +1 -0
  56. package/tmpclaude-1468-cwd +1 -0
  57. package/tmpclaude-146f-cwd +1 -0
  58. package/tmpclaude-223d-cwd +1 -0
  59. package/tmpclaude-2330-cwd +1 -0
  60. package/tmpclaude-282a-cwd +1 -0
  61. package/tmpclaude-2846-cwd +1 -0
  62. package/tmpclaude-28a6-cwd +1 -0
  63. package/tmpclaude-2b5a-cwd +1 -0
  64. package/tmpclaude-2def-cwd +1 -0
  65. package/tmpclaude-324b-cwd +1 -0
  66. package/tmpclaude-35d3-cwd +1 -0
  67. package/tmpclaude-3906-cwd +1 -0
  68. package/tmpclaude-3b32-cwd +1 -0
  69. package/tmpclaude-3da9-cwd +1 -0
  70. package/tmpclaude-3dc3-cwd +1 -0
  71. package/tmpclaude-3e3b-cwd +1 -0
  72. package/tmpclaude-43b6-cwd +1 -0
  73. package/tmpclaude-4495-cwd +1 -0
  74. package/tmpclaude-462f-cwd +1 -0
  75. package/tmpclaude-4aa8-cwd +1 -0
  76. package/tmpclaude-4b29-cwd +1 -0
  77. package/tmpclaude-4db5-cwd +1 -0
  78. package/tmpclaude-4e01-cwd +1 -0
  79. package/tmpclaude-5101-cwd +1 -0
  80. package/tmpclaude-524f-cwd +1 -0
  81. package/tmpclaude-5636-cwd +1 -0
  82. package/tmpclaude-5cdd-cwd +1 -0
  83. package/tmpclaude-5f1f-cwd +1 -0
  84. package/tmpclaude-6078-cwd +1 -0
  85. package/tmpclaude-622e-cwd +1 -0
  86. package/tmpclaude-6802-cwd +1 -0
  87. package/tmpclaude-6e36-cwd +1 -0
  88. package/tmpclaude-7793-cwd +1 -0
  89. package/tmpclaude-7f96-cwd +1 -0
  90. package/tmpclaude-8566-cwd +1 -0
  91. package/tmpclaude-8874-cwd +1 -0
  92. package/tmpclaude-8915-cwd +1 -0
  93. package/tmpclaude-8c8b-cwd +1 -0
  94. package/tmpclaude-94df-cwd +1 -0
  95. package/tmpclaude-9859-cwd +1 -0
  96. package/tmpclaude-9ac5-cwd +1 -0
  97. package/tmpclaude-9f18-cwd +1 -0
  98. package/tmpclaude-a202-cwd +1 -0
  99. package/tmpclaude-a741-cwd +1 -0
  100. package/tmpclaude-ab5f-cwd +1 -0
  101. package/tmpclaude-b008-cwd +1 -0
  102. package/tmpclaude-b0a1-cwd +1 -0
  103. package/tmpclaude-b63d-cwd +1 -0
  104. package/tmpclaude-b681-cwd +1 -0
  105. package/tmpclaude-b72d-cwd +1 -0
  106. package/tmpclaude-b92f-cwd +1 -0
  107. package/tmpclaude-bc49-cwd +1 -0
  108. package/tmpclaude-bc50-cwd +1 -0
  109. package/tmpclaude-bccf-cwd +1 -0
  110. package/tmpclaude-be55-cwd +1 -0
  111. package/tmpclaude-c228-cwd +1 -0
  112. package/tmpclaude-c717-cwd +1 -0
  113. package/tmpclaude-c7ce-cwd +1 -0
  114. package/tmpclaude-cf3e-cwd +1 -0
  115. package/tmpclaude-d142-cwd +1 -0
  116. package/tmpclaude-d5bc-cwd +1 -0
  117. package/tmpclaude-d6ae-cwd +1 -0
  118. package/tmpclaude-d77a-cwd +1 -0
  119. package/tmpclaude-d8da-cwd +1 -0
  120. package/tmpclaude-dbdb-cwd +1 -0
  121. package/tmpclaude-de61-cwd +1 -0
  122. package/tmpclaude-de81-cwd +1 -0
  123. package/tmpclaude-df9d-cwd +1 -0
  124. package/tmpclaude-e786-cwd +1 -0
  125. package/tmpclaude-f01d-cwd +1 -0
  126. package/tmpclaude-f2a9-cwd +1 -0
  127. package/tmpclaude-fc36-cwd +1 -0
  128. package/tmpclaude-ffef-cwd +1 -0
  129. package/visitor.js +2 -2
@@ -0,0 +1,458 @@
1
+ require('../array');
2
+
3
+ describe('Array.prototype extensions', () => {
4
+ describe('unset', () => {
5
+ test('should remove element at given index', () => {
6
+ const arr = [1, 2, 3, 4, 5];
7
+ arr.unset(2);
8
+ expect(arr).toEqual([1, 2, 4, 5]);
9
+ });
10
+
11
+ test('should not modify array if index is -1', () => {
12
+ const arr = [1, 2, 3];
13
+ arr.unset(-1);
14
+ expect(arr).toEqual([1, 2, 3]);
15
+ });
16
+
17
+ test('should not modify array if index is out of bounds', () => {
18
+ const arr = [1, 2, 3];
19
+ arr.unset(10);
20
+ expect(arr).toEqual([1, 2, 3]);
21
+ });
22
+
23
+ test('should remove first element', () => {
24
+ const arr = [1, 2, 3];
25
+ arr.unset(0);
26
+ expect(arr).toEqual([2, 3]);
27
+ });
28
+ });
29
+
30
+ describe('unsetVal', () => {
31
+ test('should remove first occurrence of value', () => {
32
+ const arr = [1, 2, 3, 2, 4];
33
+ arr.unsetVal(2);
34
+ expect(arr).toEqual([1, 3, 2, 4]);
35
+ });
36
+
37
+ test('should not modify array if value not found', () => {
38
+ const arr = [1, 2, 3];
39
+ arr.unsetVal(5);
40
+ expect(arr).toEqual([1, 2, 3]);
41
+ });
42
+
43
+ test('should work with strings', () => {
44
+ const arr = ['a', 'b', 'c'];
45
+ arr.unsetVal('b');
46
+ expect(arr).toEqual(['a', 'c']);
47
+ });
48
+
49
+ test('should work with objects', () => {
50
+ const obj = { id: 1 };
51
+ const arr = [obj, { id: 2 }];
52
+ arr.unsetVal(obj);
53
+ expect(arr).toEqual([{ id: 2 }]);
54
+ });
55
+ });
56
+
57
+ describe('unsetValues', () => {
58
+ test('should remove multiple values', () => {
59
+ const arr = [1, 2, 3, 4, 5];
60
+ arr.unsetValues([2, 4]);
61
+ expect(arr).toEqual([1, 3, 5]);
62
+ });
63
+
64
+ test('should work with empty values array', () => {
65
+ const arr = [1, 2, 3];
66
+ arr.unsetValues([]);
67
+ expect(arr).toEqual([1, 2, 3]);
68
+ });
69
+
70
+ test('should remove only first occurrence of duplicates', () => {
71
+ const arr = [1, 2, 3, 2, 4, 3];
72
+ arr.unsetValues([2, 3]);
73
+ expect(arr).toEqual([1, 2, 4, 3]);
74
+ });
75
+ });
76
+
77
+ describe('pushVal', () => {
78
+ test('should add value if not present', () => {
79
+ const arr = [1, 2, 3];
80
+ arr.pushVal(4);
81
+ expect(arr).toEqual([1, 2, 3, 4]);
82
+ });
83
+
84
+ test('should not add value if already present', () => {
85
+ const arr = [1, 2, 3];
86
+ arr.pushVal(2);
87
+ expect(arr).toEqual([1, 2, 3]);
88
+ });
89
+
90
+ test('should work with strings', () => {
91
+ const arr = ['a', 'b'];
92
+ arr.pushVal('c');
93
+ arr.pushVal('a');
94
+ expect(arr).toEqual(['a', 'b', 'c']);
95
+ });
96
+ });
97
+
98
+ describe('inArray', () => {
99
+ test('should return true if value exists', () => {
100
+ const arr = [1, 2, 3];
101
+ expect(arr.inArray(2)).toBe(true);
102
+ });
103
+
104
+ test('should return false if value does not exist', () => {
105
+ const arr = [1, 2, 3];
106
+ expect(arr.inArray(5)).toBe(false);
107
+ });
108
+
109
+ test('should work with strings', () => {
110
+ const arr = ['a', 'b', 'c'];
111
+ expect(arr.inArray('b')).toBe(true);
112
+ expect(arr.inArray('d')).toBe(false);
113
+ });
114
+
115
+ test('should return false for empty array', () => {
116
+ const arr = [];
117
+ expect(arr.inArray(1)).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe('unique', () => {
122
+ test('should remove duplicate values', () => {
123
+ const arr = [1, 2, 2, 3, 3, 4];
124
+ expect(arr.unique()).toEqual([1, 2, 3, 4]);
125
+ });
126
+
127
+ test('should work with strings', () => {
128
+ const arr = ['a', 'b', 'a', 'c', 'b'];
129
+ expect(arr.unique()).toEqual(['a', 'b', 'c']);
130
+ });
131
+
132
+ test('should work with empty array', () => {
133
+ const arr = [];
134
+ expect(arr.unique()).toEqual([]);
135
+ });
136
+
137
+ test('should not modify original array', () => {
138
+ const arr = [1, 2, 2, 3];
139
+ arr.unique();
140
+ expect(arr).toEqual([1, 2, 2, 3]);
141
+ });
142
+ });
143
+
144
+ describe('removeEmptyValues', () => {
145
+ test('should remove empty strings', () => {
146
+ const arr = ['a', '', 'b', '', 'c'];
147
+ expect(arr.removeEmptyValues()).toEqual(['a', 'b', 'c']);
148
+ });
149
+
150
+ test('should keep other falsy values', () => {
151
+ const arr = ['a', '', 0, false, null, 'b'];
152
+ expect(arr.removeEmptyValues()).toEqual(['a', 0, false, null, 'b']);
153
+ });
154
+
155
+ test('should work with empty array', () => {
156
+ const arr = [];
157
+ expect(arr.removeEmptyValues()).toEqual([]);
158
+ });
159
+ });
160
+
161
+ describe('hasOwnIndex', () => {
162
+ test('should return true for valid array indices', () => {
163
+ const arr = ['a', 'b', 'c'];
164
+ expect(arr.hasOwnIndex('0')).toBe(true);
165
+ expect(arr.hasOwnIndex('1')).toBe(true);
166
+ expect(arr.hasOwnIndex('2')).toBe(true);
167
+ });
168
+
169
+ test('should return false for out of bounds indices', () => {
170
+ const arr = ['a', 'b', 'c'];
171
+ expect(arr.hasOwnIndex('5')).toBe(false);
172
+ });
173
+
174
+ test('should return false for negative indices', () => {
175
+ const arr = ['a', 'b', 'c'];
176
+ expect(arr.hasOwnIndex('-1')).toBe(false);
177
+ });
178
+
179
+ test('should return false for non-numeric strings', () => {
180
+ const arr = ['a', 'b', 'c'];
181
+ expect(arr.hasOwnIndex('abc')).toBe(false);
182
+ });
183
+ });
184
+
185
+ describe('cumulativeSum', () => {
186
+ test('should calculate cumulative sum', () => {
187
+ const arr = [1, 2, 3, 4];
188
+ expect(arr.cumulativeSum()).toEqual([1, 3, 6, 10]);
189
+ });
190
+
191
+ test('should work with negative numbers', () => {
192
+ const arr = [1, -2, 3, -4];
193
+ expect(arr.cumulativeSum()).toEqual([1, -1, 2, -2]);
194
+ });
195
+
196
+ test('should work with decimals', () => {
197
+ const arr = [1.5, 2.5, 3];
198
+ expect(arr.cumulativeSum()).toEqual([1.5, 4, 7]);
199
+ });
200
+
201
+ test('should work with empty array', () => {
202
+ const arr = [];
203
+ expect(arr.cumulativeSum()).toEqual([]);
204
+ });
205
+ });
206
+
207
+ describe('intersect', () => {
208
+ test('should return common elements', () => {
209
+ const arr = [];
210
+ const result = arr.intersect([1, 2, 3, 4], [3, 4, 5, 6]);
211
+ expect(result).toEqual([3, 4]);
212
+ });
213
+
214
+ test('should return empty array if no common elements', () => {
215
+ const arr = [];
216
+ const result = arr.intersect([1, 2], [3, 4]);
217
+ expect(result).toEqual([]);
218
+ });
219
+
220
+ test('should work with strings', () => {
221
+ const arr = [];
222
+ const result = arr.intersect(['a', 'b', 'c'], ['b', 'c', 'd']);
223
+ expect(result).toEqual(['b', 'c']);
224
+ });
225
+
226
+ test('should work with empty arrays', () => {
227
+ const arr = [];
228
+ expect(arr.intersect([], [1, 2])).toEqual([]);
229
+ expect(arr.intersect([1, 2], [])).toEqual([]);
230
+ });
231
+ });
232
+
233
+ describe('diff', () => {
234
+ test('should return symmetric difference', () => {
235
+ const arr = [];
236
+ const result = arr.diff([1, 2, 3], [2, 3, 4]);
237
+ expect(result).toEqual([1, 4]);
238
+ });
239
+
240
+ test('should return all elements if no common elements', () => {
241
+ const arr = [];
242
+ const result = arr.diff([1, 2], [3, 4]);
243
+ expect(result).toEqual([1, 2, 3, 4]);
244
+ });
245
+
246
+ test('should work with strings', () => {
247
+ const arr = [];
248
+ const result = arr.diff(['a', 'b', 'c'], ['b', 'c', 'd']);
249
+ expect(result).toEqual(['a', 'd']);
250
+ });
251
+
252
+ test('should work with empty arrays', () => {
253
+ const arr = [];
254
+ expect(arr.diff([], [1, 2])).toEqual([1, 2]);
255
+ expect(arr.diff([1, 2], [])).toEqual([1, 2]);
256
+ });
257
+ });
258
+
259
+ describe('filterUnique', () => {
260
+ test('should filter duplicate values', () => {
261
+ const arr = [1, 2, 2, 3, 3, 4];
262
+ expect(arr.filterUnique()).toEqual([1, 2, 3, 4]);
263
+ });
264
+
265
+ test('should work with strings', () => {
266
+ const arr = ['a', 'b', 'a', 'c'];
267
+ expect(arr.filterUnique()).toEqual(['a', 'b', 'c']);
268
+ });
269
+
270
+ test('should work with empty array', () => {
271
+ const arr = [];
272
+ expect(arr.filterUnique()).toEqual([]);
273
+ });
274
+ });
275
+ });
276
+
277
+ describe('Array static methods', () => {
278
+ describe('generate', () => {
279
+ test('should generate array from 0 to given number', () => {
280
+ const result = Array.generate({ to: 5 });
281
+ expect(result).toEqual([0, 1, 2, 3, 4]);
282
+ });
283
+
284
+ test('should generate array with custom start', () => {
285
+ const result = Array.generate({ from: 5, to: 10 });
286
+ expect(result).toEqual([5, 6, 7, 8, 9]);
287
+ });
288
+
289
+ test('should generate array with custom step', () => {
290
+ const result = Array.generate({ from: 0, to: 10, step: 2 });
291
+ expect(result).toEqual([0, 2, 4, 6, 8]);
292
+ });
293
+
294
+ test('should generate array with negative step', () => {
295
+ const result = Array.generate({ from: 10, to: 0, step: -2 });
296
+ expect(result).toEqual([10, 8, 6, 4, 2]);
297
+ });
298
+
299
+ test('should generate array with decimal step', () => {
300
+ const result = Array.generate({ from: 0, to: 2, step: 0.5 });
301
+ expect(result).toEqual([0, 0.5, 1, 1.5]);
302
+ });
303
+
304
+ test('should work with custom length', () => {
305
+ const result = Array.generate({ from: 0, step: 3, length: 4 });
306
+ expect(result).toEqual([0, 3, 6, 9]);
307
+ });
308
+ });
309
+
310
+ describe('getValuesByKeyInArrayOfArrays', () => {
311
+ test('should extract values by key', () => {
312
+ const arr = [
313
+ { id: 1, name: 'Alice' },
314
+ { id: 2, name: 'Bob' },
315
+ { id: 3, name: 'Charlie' }
316
+ ];
317
+ const result = Array.getValuesByKeyInArrayOfArrays(arr, 'name');
318
+ expect(result).toEqual(['Alice', 'Bob', 'Charlie']);
319
+ });
320
+
321
+ test('should skip elements without the key', () => {
322
+ const arr = [
323
+ { id: 1, name: 'Alice' },
324
+ { id: 2 },
325
+ { id: 3, name: 'Charlie' }
326
+ ];
327
+ const result = Array.getValuesByKeyInArrayOfArrays(arr, 'name');
328
+ expect(result).toEqual(['Alice', 'Charlie']);
329
+ });
330
+
331
+ test('should work with empty array', () => {
332
+ const result = Array.getValuesByKeyInArrayOfArrays([], 'name');
333
+ expect(result).toEqual([]);
334
+ });
335
+
336
+ test('should work with numeric keys', () => {
337
+ const arr = [
338
+ { 0: 'a', 1: 'b' },
339
+ { 0: 'c', 1: 'd' }
340
+ ];
341
+ const result = Array.getValuesByKeyInArrayOfArrays(arr, 0);
342
+ expect(result).toEqual(['a', 'c']);
343
+ });
344
+ });
345
+ });
346
+
347
+ describe('Object extensions', () => {
348
+ describe('toArray', () => {
349
+ test('should convert object values to array', () => {
350
+ const obj = { a: 1, b: 2, c: 3 };
351
+ expect(Object.toArray(obj)).toEqual([1, 2, 3]);
352
+ });
353
+
354
+ test('should work with empty object', () => {
355
+ expect(Object.toArray({})).toEqual([]);
356
+ });
357
+
358
+ test('should preserve order for numeric keys', () => {
359
+ const obj = { 0: 'a', 1: 'b', 2: 'c' };
360
+ expect(Object.toArray(obj)).toEqual(['a', 'b', 'c']);
361
+ });
362
+ });
363
+
364
+ describe('filter', () => {
365
+ test('should filter object by value predicate', () => {
366
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
367
+ const result = Object.filter(obj, val => val > 2);
368
+ expect(result).toEqual({ c: 3, d: 4 });
369
+ });
370
+
371
+ test('should work with string values', () => {
372
+ const obj = { a: 'apple', b: 'banana', c: 'cherry' };
373
+ const result = Object.filter(obj, val => val.startsWith('a'));
374
+ expect(result).toEqual({ a: 'apple' });
375
+ });
376
+
377
+ test('should return empty object if no match', () => {
378
+ const obj = { a: 1, b: 2 };
379
+ const result = Object.filter(obj, val => val > 10);
380
+ expect(result).toEqual({});
381
+ });
382
+
383
+ test('should work with empty object', () => {
384
+ const result = Object.filter({}, val => val > 0);
385
+ expect(result).toEqual({});
386
+ });
387
+ });
388
+
389
+ describe('filterKeys', () => {
390
+ test('should filter object by key predicate', () => {
391
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
392
+ const result = Object.filterKeys(obj, key => key === 'a' || key === 'c');
393
+ expect(result).toEqual({ a: 1, c: 3 });
394
+ });
395
+
396
+ test('should work with pattern matching', () => {
397
+ const obj = { name: 'John', age: 30, name2: 'Doe' };
398
+ const result = Object.filterKeys(obj, key => key.includes('name'));
399
+ expect(result).toEqual({ name: 'John', name2: 'Doe' });
400
+ });
401
+
402
+ test('should return empty object if no match', () => {
403
+ const obj = { a: 1, b: 2 };
404
+ const result = Object.filterKeys(obj, key => key === 'z');
405
+ expect(result).toEqual({});
406
+ });
407
+ });
408
+
409
+ describe('renameKeys', () => {
410
+ test('should rename keys according to map', () => {
411
+ const obj = { a: 1, b: 2, c: 3 };
412
+ const result = Object.renameKeys(obj, { a: 'x', b: 'y' });
413
+ expect(result).toEqual({ x: 1, y: 2, c: 3 });
414
+ });
415
+
416
+ test('should keep keys not in map', () => {
417
+ const obj = { firstName: 'John', lastName: 'Doe', age: 30 };
418
+ const result = Object.renameKeys(obj, { firstName: 'first', lastName: 'last' });
419
+ expect(result).toEqual({ first: 'John', last: 'Doe', age: 30 });
420
+ });
421
+
422
+ test('should work with empty map', () => {
423
+ const obj = { a: 1, b: 2 };
424
+ const result = Object.renameKeys(obj, {});
425
+ expect(result).toEqual({ a: 1, b: 2 });
426
+ });
427
+
428
+ test('should work with empty object', () => {
429
+ const result = Object.renameKeys({}, { a: 'x' });
430
+ expect(result).toEqual({});
431
+ });
432
+ });
433
+
434
+ describe('renameKeysByCallback', () => {
435
+ test('should rename keys using callback', () => {
436
+ const obj = { a: 1, b: 2, c: 3 };
437
+ const result = Object.renameKeysByCallback(obj, key => key.toUpperCase());
438
+ expect(result).toEqual({ A: 1, B: 2, C: 3 });
439
+ });
440
+
441
+ test('should add prefix to keys', () => {
442
+ const obj = { name: 'John', age: 30 };
443
+ const result = Object.renameKeysByCallback(obj, key => 'user_' + key);
444
+ expect(result).toEqual({ user_name: 'John', user_age: 30 });
445
+ });
446
+
447
+ test('should work with complex transformations', () => {
448
+ const obj = { firstName: 'John', lastName: 'Doe' };
449
+ const result = Object.renameKeysByCallback(obj, key => key.replace(/([A-Z])/g, '_$1').toLowerCase());
450
+ expect(result).toEqual({ first_name: 'John', last_name: 'Doe' });
451
+ });
452
+
453
+ test('should work with empty object', () => {
454
+ const result = Object.renameKeysByCallback({}, key => key.toUpperCase());
455
+ expect(result).toEqual({});
456
+ });
457
+ });
458
+ });
@@ -0,0 +1,158 @@
1
+ const { IBAN, BankCard } = require('../bank');
2
+
3
+ describe('IBAN', () => {
4
+ describe('format', () => {
5
+ test('should format IBAN with spaces every 4 characters', () => {
6
+ const iban = 'FR7630006000011234567890189';
7
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
8
+ });
9
+
10
+ test('should remove non-alphanumeric characters', () => {
11
+ const iban = 'FR76-3000-6000-0112-3456-7890-189';
12
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
13
+ });
14
+
15
+ test('should handle IBAN with spaces', () => {
16
+ const iban = 'FR76 3000 6000 0112 3456 7890 189';
17
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
18
+ });
19
+
20
+ test('should handle lowercase letters', () => {
21
+ const iban = 'fr7630006000011234567890189';
22
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
23
+ });
24
+
25
+ test('should remove special characters and spaces', () => {
26
+ const iban = 'FR76 3000.6000/0112-3456_7890*189';
27
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
28
+ });
29
+
30
+ test('should handle German IBAN', () => {
31
+ const iban = 'DE89370400440532013000';
32
+ expect(IBAN.format(iban)).toBe('DE89 3704 0044 0532 0130 00');
33
+ });
34
+
35
+ test('should handle Belgian IBAN', () => {
36
+ const iban = 'BE68539007547034';
37
+ expect(IBAN.format(iban)).toBe('BE68 5390 0754 7034');
38
+ });
39
+
40
+ test('should handle empty string', () => {
41
+ expect(IBAN.format('')).toBe('');
42
+ });
43
+
44
+ test('should handle IBAN with mixed case and special chars', () => {
45
+ const iban = 'fR76@3000#6000$0112%3456^7890&189';
46
+ expect(IBAN.format(iban)).toBe('FR76 3000 6000 0112 3456 7890 189');
47
+ });
48
+ });
49
+ });
50
+
51
+ describe('BankCard', () => {
52
+ describe('formatCardNumber', () => {
53
+ test('should format 16-digit card number with dashes', () => {
54
+ const cardNumber = '1234567890123456';
55
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('1234-5678-9012-3456');
56
+ });
57
+
58
+ test('should replace asterisks with X', () => {
59
+ const cardNumber = '1234********3456';
60
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('1234-****-****-3456');
61
+ });
62
+
63
+ test('should replace asterisks with custom hiddenChar', () => {
64
+ const cardNumber = '1234********3456';
65
+ expect(BankCard.formatCardNumber(cardNumber, 'X')).toBe('1234-XXXX-XXXX-3456');
66
+ });
67
+
68
+ test('should replace X with custom hiddenChar', () => {
69
+ const cardNumber = '1234XXXXXXXX3456';
70
+ expect(BankCard.formatCardNumber(cardNumber, '#')).toBe('1234-####-####-3456');
71
+ });
72
+
73
+ test('should replace both * and X with hiddenChar', () => {
74
+ const cardNumber = '1234**XX**XX3456';
75
+ // After formatting: 1234-**XX-**XX-3456
76
+ // After replacing * and X with -: 1234-----------3456 (11 dashes: 3 from formatting + 8 from replacement)
77
+ expect(BankCard.formatCardNumber(cardNumber, '-')).toBe('1234-----------3456');
78
+ });
79
+
80
+ test('should handle lowercase x', () => {
81
+ const cardNumber = '1234xxxxxxxx3456';
82
+ expect(BankCard.formatCardNumber(cardNumber, '•')).toBe('1234-••••-••••-3456');
83
+ });
84
+
85
+ test('should not format if not 16 digits', () => {
86
+ const cardNumber = '12345678901234';
87
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('12345678901234');
88
+ });
89
+
90
+ test('should not format if more than 16 digits', () => {
91
+ const cardNumber = '12345678901234567';
92
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('12345678901234567');
93
+ });
94
+
95
+ test('should handle card number with all zeros', () => {
96
+ const cardNumber = '0000000000000000';
97
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('0000-0000-0000-0000');
98
+ });
99
+
100
+ test('should handle card number with letters (treated as 16 chars)', () => {
101
+ const cardNumber = '123456789012ABCD';
102
+ expect(BankCard.formatCardNumber(cardNumber)).toBe('1234-5678-9012-ABCD');
103
+ });
104
+
105
+ test('should handle empty string', () => {
106
+ expect(BankCard.formatCardNumber('')).toBe('');
107
+ });
108
+ });
109
+
110
+ describe('formatExpirationDate', () => {
111
+ // Mock SqlDateTime since it's imported from another module
112
+ beforeAll(() => {
113
+ global.SqlDateTime = {
114
+ getMonthName: jest.fn((date, locale) => {
115
+ // Mock implementation
116
+ const d = new Date(date);
117
+ const month = d.toLocaleString(locale, { month: 'long' });
118
+ return month.charAt(0).toUpperCase() + month.slice(1);
119
+ }),
120
+ getYear: jest.fn((date) => {
121
+ const d = new Date(date);
122
+ return d.getFullYear();
123
+ })
124
+ };
125
+ });
126
+
127
+ afterAll(() => {
128
+ delete global.SqlDateTime;
129
+ });
130
+
131
+ test('should format expiration date in French', () => {
132
+ const date = '2025-12-31';
133
+ const result = BankCard.formatExpirationDate(date, 'fr-FR');
134
+ expect(result).toContain('2025');
135
+ expect(SqlDateTime.getMonthName).toHaveBeenCalledWith(date, 'fr-FR');
136
+ expect(SqlDateTime.getYear).toHaveBeenCalledWith(date);
137
+ });
138
+
139
+ test('should use default locale fr-FR', () => {
140
+ const date = '2025-06-15';
141
+ BankCard.formatExpirationDate(date);
142
+ expect(SqlDateTime.getMonthName).toHaveBeenCalledWith(date, 'fr-FR');
143
+ });
144
+
145
+ test('should format expiration date in English', () => {
146
+ const date = '2025-03-20';
147
+ const result = BankCard.formatExpirationDate(date, 'en-US');
148
+ expect(result).toContain('2025');
149
+ expect(SqlDateTime.getMonthName).toHaveBeenCalledWith(date, 'en-US');
150
+ });
151
+
152
+ test('should handle different date formats', () => {
153
+ const date = '2026-01-01';
154
+ const result = BankCard.formatExpirationDate(date);
155
+ expect(result).toContain('2026');
156
+ });
157
+ });
158
+ });