@osimatic/helpers-js 1.4.25 → 1.4.26

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 (110) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/duration.js +174 -125
  3. package/file.js +19 -4
  4. package/google_charts.js +2 -1
  5. package/location.js +5 -1
  6. package/media.js +6 -6
  7. package/multi_files_input.js +3 -1
  8. package/package.json +2 -1
  9. package/paging.js +2 -2
  10. package/tests/__mocks__/socket.io-client.js +13 -0
  11. package/tests/count_down.test.js +580 -0
  12. package/tests/details_sub_array.test.js +367 -0
  13. package/tests/file.test.js +210 -0
  14. package/tests/flash_message.test.js +297 -0
  15. package/tests/form_date.test.js +1142 -0
  16. package/tests/form_helper.test.js +780 -130
  17. package/tests/google_charts.test.js +768 -0
  18. package/tests/google_maps.test.js +655 -0
  19. package/tests/google_recaptcha.test.js +441 -0
  20. package/tests/import_from_csv.test.js +797 -0
  21. package/tests/list_box.test.js +255 -0
  22. package/tests/location.test.js +86 -0
  23. package/tests/media.test.js +15 -0
  24. package/tests/multi_files_input.test.js +1015 -0
  25. package/tests/multiple_action_in_table.test.js +477 -0
  26. package/tests/paging.test.js +646 -0
  27. package/tests/select_all.test.js +360 -0
  28. package/tests/sortable_list.test.js +602 -0
  29. package/tests/string.test.js +16 -0
  30. package/tests/web_rtc.test.js +458 -0
  31. package/tests/web_socket.test.js +538 -0
  32. package/tmpclaude-00a6-cwd +0 -1
  33. package/tmpclaude-0526-cwd +0 -1
  34. package/tmpclaude-0973-cwd +0 -1
  35. package/tmpclaude-0b61-cwd +0 -1
  36. package/tmpclaude-0fa4-cwd +0 -1
  37. package/tmpclaude-104f-cwd +0 -1
  38. package/tmpclaude-1468-cwd +0 -1
  39. package/tmpclaude-146f-cwd +0 -1
  40. package/tmpclaude-223d-cwd +0 -1
  41. package/tmpclaude-2330-cwd +0 -1
  42. package/tmpclaude-282a-cwd +0 -1
  43. package/tmpclaude-2846-cwd +0 -1
  44. package/tmpclaude-28a6-cwd +0 -1
  45. package/tmpclaude-2b5a-cwd +0 -1
  46. package/tmpclaude-2def-cwd +0 -1
  47. package/tmpclaude-324b-cwd +0 -1
  48. package/tmpclaude-35d3-cwd +0 -1
  49. package/tmpclaude-3906-cwd +0 -1
  50. package/tmpclaude-3b32-cwd +0 -1
  51. package/tmpclaude-3da9-cwd +0 -1
  52. package/tmpclaude-3dc3-cwd +0 -1
  53. package/tmpclaude-3e3b-cwd +0 -1
  54. package/tmpclaude-43b6-cwd +0 -1
  55. package/tmpclaude-4495-cwd +0 -1
  56. package/tmpclaude-462f-cwd +0 -1
  57. package/tmpclaude-4aa8-cwd +0 -1
  58. package/tmpclaude-4b29-cwd +0 -1
  59. package/tmpclaude-4db5-cwd +0 -1
  60. package/tmpclaude-4e01-cwd +0 -1
  61. package/tmpclaude-5101-cwd +0 -1
  62. package/tmpclaude-524f-cwd +0 -1
  63. package/tmpclaude-5636-cwd +0 -1
  64. package/tmpclaude-5cdd-cwd +0 -1
  65. package/tmpclaude-5f1f-cwd +0 -1
  66. package/tmpclaude-6078-cwd +0 -1
  67. package/tmpclaude-622e-cwd +0 -1
  68. package/tmpclaude-6802-cwd +0 -1
  69. package/tmpclaude-6e36-cwd +0 -1
  70. package/tmpclaude-7793-cwd +0 -1
  71. package/tmpclaude-7f96-cwd +0 -1
  72. package/tmpclaude-8566-cwd +0 -1
  73. package/tmpclaude-8874-cwd +0 -1
  74. package/tmpclaude-8915-cwd +0 -1
  75. package/tmpclaude-8c8b-cwd +0 -1
  76. package/tmpclaude-94df-cwd +0 -1
  77. package/tmpclaude-9859-cwd +0 -1
  78. package/tmpclaude-9ac5-cwd +0 -1
  79. package/tmpclaude-9f18-cwd +0 -1
  80. package/tmpclaude-a202-cwd +0 -1
  81. package/tmpclaude-a741-cwd +0 -1
  82. package/tmpclaude-ab5f-cwd +0 -1
  83. package/tmpclaude-b008-cwd +0 -1
  84. package/tmpclaude-b0a1-cwd +0 -1
  85. package/tmpclaude-b63d-cwd +0 -1
  86. package/tmpclaude-b681-cwd +0 -1
  87. package/tmpclaude-b72d-cwd +0 -1
  88. package/tmpclaude-b92f-cwd +0 -1
  89. package/tmpclaude-bc49-cwd +0 -1
  90. package/tmpclaude-bc50-cwd +0 -1
  91. package/tmpclaude-bccf-cwd +0 -1
  92. package/tmpclaude-be55-cwd +0 -1
  93. package/tmpclaude-c228-cwd +0 -1
  94. package/tmpclaude-c717-cwd +0 -1
  95. package/tmpclaude-c7ce-cwd +0 -1
  96. package/tmpclaude-cf3e-cwd +0 -1
  97. package/tmpclaude-d142-cwd +0 -1
  98. package/tmpclaude-d5bc-cwd +0 -1
  99. package/tmpclaude-d6ae-cwd +0 -1
  100. package/tmpclaude-d77a-cwd +0 -1
  101. package/tmpclaude-d8da-cwd +0 -1
  102. package/tmpclaude-dbdb-cwd +0 -1
  103. package/tmpclaude-de61-cwd +0 -1
  104. package/tmpclaude-de81-cwd +0 -1
  105. package/tmpclaude-df9d-cwd +0 -1
  106. package/tmpclaude-e786-cwd +0 -1
  107. package/tmpclaude-f01d-cwd +0 -1
  108. package/tmpclaude-f2a9-cwd +0 -1
  109. package/tmpclaude-fc36-cwd +0 -1
  110. package/tmpclaude-ffef-cwd +0 -1
@@ -0,0 +1,441 @@
1
+ const { GoogleRecaptcha } = require('../google_recaptcha');
2
+
3
+ describe('GoogleRecaptcha', () => {
4
+ let mockGrecaptcha;
5
+ let consoleLogSpy;
6
+ let consoleErrorSpy;
7
+
8
+ beforeEach(() => {
9
+ // Mock grecaptcha
10
+ mockGrecaptcha = {
11
+ render: jest.fn((id, config) => {
12
+ return `widget-${id}`;
13
+ }),
14
+ reset: jest.fn()
15
+ };
16
+
17
+ global.grecaptcha = mockGrecaptcha;
18
+
19
+ // Spy on console methods
20
+ consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
21
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
22
+
23
+ // Clear static properties
24
+ delete GoogleRecaptcha.config;
25
+ delete GoogleRecaptcha.googleCaptchaRendersCallback;
26
+ delete GoogleRecaptcha.grecaptchaWidgets;
27
+ });
28
+
29
+ afterEach(() => {
30
+ jest.clearAllMocks();
31
+ consoleLogSpy.mockRestore();
32
+ consoleErrorSpy.mockRestore();
33
+ delete global.grecaptcha;
34
+ delete GoogleRecaptcha.config;
35
+ delete GoogleRecaptcha.googleCaptchaRendersCallback;
36
+ delete GoogleRecaptcha.grecaptchaWidgets;
37
+ });
38
+
39
+ describe('setConfig', () => {
40
+ test('should set config', () => {
41
+ const config = {
42
+ sitekey: 'test-site-key',
43
+ theme: 'light'
44
+ };
45
+
46
+ GoogleRecaptcha.setConfig(config);
47
+
48
+ expect(GoogleRecaptcha.config).toBe(config);
49
+ });
50
+
51
+ test('should overwrite existing config', () => {
52
+ const config1 = { sitekey: 'key1' };
53
+ const config2 = { sitekey: 'key2' };
54
+
55
+ GoogleRecaptcha.setConfig(config1);
56
+ expect(GoogleRecaptcha.config).toBe(config1);
57
+
58
+ GoogleRecaptcha.setConfig(config2);
59
+ expect(GoogleRecaptcha.config).toBe(config2);
60
+ });
61
+ });
62
+
63
+ describe('onLoad', () => {
64
+ test('should log onLoad message', () => {
65
+ GoogleRecaptcha.onLoad();
66
+
67
+ expect(consoleLogSpy).toHaveBeenCalledWith('GoogleRecaptcha.onLoad');
68
+ });
69
+
70
+ test('should return early when grecaptcha is undefined', () => {
71
+ delete global.grecaptcha;
72
+
73
+ GoogleRecaptcha.onLoad();
74
+
75
+ expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha undefined');
76
+ });
77
+
78
+ test('should return early when grecaptcha.render is not a function', () => {
79
+ global.grecaptcha = { render: null };
80
+
81
+ GoogleRecaptcha.onLoad();
82
+
83
+ expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha undefined');
84
+ });
85
+
86
+ test('should initialize googleCaptchaRendersCallback if undefined', () => {
87
+ GoogleRecaptcha.onLoad();
88
+
89
+ expect(Array.isArray(GoogleRecaptcha.googleCaptchaRendersCallback)).toBe(true);
90
+ });
91
+
92
+ test('should execute all callbacks in googleCaptchaRendersCallback', () => {
93
+ const callback1 = jest.fn();
94
+ const callback2 = jest.fn();
95
+ const callback3 = jest.fn();
96
+
97
+ GoogleRecaptcha.googleCaptchaRendersCallback = [callback1, callback2, callback3];
98
+
99
+ GoogleRecaptcha.onLoad();
100
+
101
+ expect(callback1).toHaveBeenCalled();
102
+ expect(callback2).toHaveBeenCalled();
103
+ expect(callback3).toHaveBeenCalled();
104
+ });
105
+
106
+ test('should clear googleCaptchaRendersCallback after execution', () => {
107
+ const callback = jest.fn();
108
+ GoogleRecaptcha.googleCaptchaRendersCallback = [callback];
109
+
110
+ GoogleRecaptcha.onLoad();
111
+
112
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
113
+ });
114
+
115
+ test('should handle empty callback array', () => {
116
+ GoogleRecaptcha.googleCaptchaRendersCallback = [];
117
+
118
+ expect(() => {
119
+ GoogleRecaptcha.onLoad();
120
+ }).not.toThrow();
121
+
122
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
123
+ });
124
+
125
+ test('should execute callbacks with correct context', () => {
126
+ let contextThis;
127
+ const callback = jest.fn(function() {
128
+ contextThis = this;
129
+ });
130
+ GoogleRecaptcha.googleCaptchaRendersCallback = [callback];
131
+
132
+ GoogleRecaptcha.onLoad();
133
+
134
+ expect(callback).toHaveBeenCalled();
135
+ });
136
+ });
137
+
138
+ describe('render', () => {
139
+ test('should add render callback and execute it', () => {
140
+ const id = 'recaptcha-1';
141
+ GoogleRecaptcha.setConfig({ sitekey: 'test' });
142
+
143
+ GoogleRecaptcha.render(id);
144
+
145
+ // Callback should have been executed and array cleared
146
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
147
+ // The callback should have called reset which renders the captcha
148
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test' });
149
+ });
150
+
151
+ test('should call reset through callback', () => {
152
+ const id = 'recaptcha-1';
153
+ GoogleRecaptcha.setConfig({ sitekey: 'test' });
154
+
155
+ GoogleRecaptcha.render(id);
156
+
157
+ // The render method should have triggered reset which calls grecaptcha.render
158
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test' });
159
+ });
160
+
161
+ test('should trigger onLoad', () => {
162
+ const onLoadSpy = jest.spyOn(GoogleRecaptcha, 'onLoad');
163
+
164
+ GoogleRecaptcha.render('recaptcha-1');
165
+
166
+ expect(onLoadSpy).toHaveBeenCalled();
167
+
168
+ onLoadSpy.mockRestore();
169
+ });
170
+ });
171
+
172
+ describe('reset', () => {
173
+ beforeEach(() => {
174
+ GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
175
+ });
176
+
177
+ test('should return early when grecaptcha is undefined', () => {
178
+ delete global.grecaptcha;
179
+
180
+ GoogleRecaptcha.reset('recaptcha-1');
181
+
182
+ expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha.render undefined');
183
+ });
184
+
185
+ test('should return early when grecaptcha.render is not a function', () => {
186
+ global.grecaptcha = { render: null };
187
+
188
+ GoogleRecaptcha.reset('recaptcha-1');
189
+
190
+ expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha.render undefined');
191
+ });
192
+
193
+ test('should initialize grecaptchaWidgets if undefined', () => {
194
+ GoogleRecaptcha.reset('recaptcha-1');
195
+
196
+ expect(Array.isArray(GoogleRecaptcha.grecaptchaWidgets)).toBe(true);
197
+ });
198
+
199
+ test('should render new widget when widget does not exist', () => {
200
+ const id = 'recaptcha-1';
201
+
202
+ GoogleRecaptcha.reset(id);
203
+
204
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test-key' });
205
+ });
206
+
207
+ test('should store widget id after rendering', () => {
208
+ const id = 'recaptcha-1';
209
+
210
+ GoogleRecaptcha.reset(id);
211
+
212
+ expect(GoogleRecaptcha.grecaptchaWidgets[id]).toBe('widget-recaptcha-1');
213
+ });
214
+
215
+ test('should reset existing widget instead of creating new one', () => {
216
+ const id = 'recaptcha-1';
217
+
218
+ // First call - creates widget
219
+ GoogleRecaptcha.reset(id);
220
+ expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
221
+
222
+ jest.clearAllMocks();
223
+
224
+ // Second call - resets widget
225
+ GoogleRecaptcha.reset(id);
226
+ expect(mockGrecaptcha.render).not.toHaveBeenCalled();
227
+ expect(mockGrecaptcha.reset).toHaveBeenCalledWith('widget-recaptcha-1');
228
+ });
229
+
230
+ test('should handle multiple widgets independently', () => {
231
+ GoogleRecaptcha.reset('recaptcha-1');
232
+ GoogleRecaptcha.reset('recaptcha-2');
233
+
234
+ expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-1']).toBe('widget-recaptcha-1');
235
+ expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-2']).toBe('widget-recaptcha-2');
236
+ expect(mockGrecaptcha.render).toHaveBeenCalledTimes(2);
237
+ });
238
+
239
+ test('should catch and log error during grecaptcha.render', () => {
240
+ const error = new Error('Render failed');
241
+ mockGrecaptcha.render.mockImplementation(() => {
242
+ throw error;
243
+ });
244
+
245
+ GoogleRecaptcha.reset('recaptcha-1');
246
+
247
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Exception during grecaptcha.render', error);
248
+ });
249
+
250
+ test('should not store widget id when render throws error', () => {
251
+ mockGrecaptcha.render.mockImplementation(() => {
252
+ throw new Error('Render failed');
253
+ });
254
+
255
+ GoogleRecaptcha.reset('recaptcha-1');
256
+
257
+ expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-1']).toBeUndefined();
258
+ });
259
+
260
+ test('should use config passed to setConfig', () => {
261
+ const config = {
262
+ sitekey: 'my-site-key',
263
+ theme: 'dark',
264
+ size: 'compact'
265
+ };
266
+ GoogleRecaptcha.setConfig(config);
267
+
268
+ GoogleRecaptcha.reset('recaptcha-1');
269
+
270
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('recaptcha-1', config);
271
+ });
272
+ });
273
+
274
+ describe('addRenderCallback', () => {
275
+ test('should initialize googleCaptchaRendersCallback if undefined', () => {
276
+ const callback = jest.fn();
277
+
278
+ GoogleRecaptcha.addRenderCallback(callback);
279
+
280
+ expect(Array.isArray(GoogleRecaptcha.googleCaptchaRendersCallback)).toBe(true);
281
+ });
282
+
283
+ test('should add callback to array and execute immediately', () => {
284
+ const callback1 = jest.fn();
285
+ const callback2 = jest.fn();
286
+
287
+ GoogleRecaptcha.addRenderCallback(callback1);
288
+ // Callback is executed immediately and array is cleared
289
+ expect(callback1).toHaveBeenCalled();
290
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
291
+
292
+ GoogleRecaptcha.addRenderCallback(callback2);
293
+ expect(callback2).toHaveBeenCalled();
294
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
295
+ });
296
+
297
+ test('should log addRenderCallback message', () => {
298
+ GoogleRecaptcha.addRenderCallback(jest.fn());
299
+
300
+ expect(consoleLogSpy).toHaveBeenCalledWith('GoogleRecaptcha.addRenderCallback');
301
+ });
302
+
303
+ test('should call onLoad after adding callback', () => {
304
+ const onLoadSpy = jest.spyOn(GoogleRecaptcha, 'onLoad');
305
+
306
+ GoogleRecaptcha.addRenderCallback(jest.fn());
307
+
308
+ expect(onLoadSpy).toHaveBeenCalled();
309
+
310
+ onLoadSpy.mockRestore();
311
+ });
312
+
313
+ test('should execute callback immediately if grecaptcha is loaded', () => {
314
+ const callback = jest.fn();
315
+
316
+ GoogleRecaptcha.addRenderCallback(callback);
317
+
318
+ expect(callback).toHaveBeenCalled();
319
+ });
320
+
321
+ test('should not execute callback if grecaptcha is not loaded', () => {
322
+ delete global.grecaptcha;
323
+ const callback = jest.fn();
324
+
325
+ GoogleRecaptcha.addRenderCallback(callback);
326
+
327
+ expect(callback).not.toHaveBeenCalled();
328
+ });
329
+
330
+ test('should keep callback in array if grecaptcha not loaded', () => {
331
+ delete global.grecaptcha;
332
+ const callback = jest.fn();
333
+
334
+ GoogleRecaptcha.addRenderCallback(callback);
335
+
336
+ expect(GoogleRecaptcha.googleCaptchaRendersCallback).toContain(callback);
337
+ });
338
+ });
339
+
340
+ describe('integration scenarios', () => {
341
+ test('should render multiple captchas sequentially', () => {
342
+ GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
343
+
344
+ GoogleRecaptcha.render('captcha-1');
345
+ GoogleRecaptcha.render('captcha-2');
346
+ GoogleRecaptcha.render('captcha-3');
347
+
348
+ // Execute callbacks
349
+ GoogleRecaptcha.googleCaptchaRendersCallback.forEach(cb => cb());
350
+
351
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-1', { sitekey: 'test-key' });
352
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-2', { sitekey: 'test-key' });
353
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-3', { sitekey: 'test-key' });
354
+ });
355
+
356
+ test('should handle render and reset lifecycle', () => {
357
+ GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
358
+
359
+ // Render
360
+ GoogleRecaptcha.render('captcha-1');
361
+ GoogleRecaptcha.googleCaptchaRendersCallback.forEach(cb => cb());
362
+
363
+ expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
364
+
365
+ jest.clearAllMocks();
366
+
367
+ // Reset should reuse widget
368
+ GoogleRecaptcha.reset('captcha-1');
369
+ expect(mockGrecaptcha.render).not.toHaveBeenCalled();
370
+ expect(mockGrecaptcha.reset).toHaveBeenCalled();
371
+ });
372
+
373
+ test('should queue callbacks when grecaptcha not loaded yet', () => {
374
+ delete global.grecaptcha;
375
+
376
+ const callback1 = jest.fn();
377
+ const callback2 = jest.fn();
378
+
379
+ GoogleRecaptcha.addRenderCallback(callback1);
380
+ GoogleRecaptcha.addRenderCallback(callback2);
381
+
382
+ expect(callback1).not.toHaveBeenCalled();
383
+ expect(callback2).not.toHaveBeenCalled();
384
+
385
+ // Simulate grecaptcha loading
386
+ global.grecaptcha = mockGrecaptcha;
387
+ GoogleRecaptcha.onLoad();
388
+
389
+ expect(callback1).toHaveBeenCalled();
390
+ expect(callback2).toHaveBeenCalled();
391
+ });
392
+
393
+ test('should handle config changes between renders', () => {
394
+ GoogleRecaptcha.setConfig({ sitekey: 'key1' });
395
+ GoogleRecaptcha.reset('captcha-1');
396
+
397
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-1', { sitekey: 'key1' });
398
+
399
+ jest.clearAllMocks();
400
+
401
+ GoogleRecaptcha.setConfig({ sitekey: 'key2' });
402
+ GoogleRecaptcha.reset('captcha-2');
403
+
404
+ expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-2', { sitekey: 'key2' });
405
+ });
406
+
407
+ test('should handle errors gracefully and continue working', () => {
408
+ GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
409
+
410
+ // First render fails
411
+ mockGrecaptcha.render.mockImplementationOnce(() => {
412
+ throw new Error('Render failed');
413
+ });
414
+
415
+ GoogleRecaptcha.reset('captcha-1');
416
+ expect(consoleErrorSpy).toHaveBeenCalled();
417
+
418
+ // Second render succeeds
419
+ GoogleRecaptcha.reset('captcha-2');
420
+ expect(GoogleRecaptcha.grecaptchaWidgets['captcha-2']).toBe('widget-captcha-2');
421
+ });
422
+
423
+ test('should reset same captcha multiple times', () => {
424
+ GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
425
+
426
+ // First reset - renders
427
+ GoogleRecaptcha.reset('captcha-1');
428
+ expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
429
+
430
+ jest.clearAllMocks();
431
+
432
+ // Second reset - resets
433
+ GoogleRecaptcha.reset('captcha-1');
434
+ expect(mockGrecaptcha.reset).toHaveBeenCalledTimes(1);
435
+
436
+ // Third reset - resets again
437
+ GoogleRecaptcha.reset('captcha-1');
438
+ expect(mockGrecaptcha.reset).toHaveBeenCalledTimes(2);
439
+ });
440
+ });
441
+ });