@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,768 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ const { GoogleCharts } = require('../google_charts');
5
+
6
+ describe('GoogleCharts', () => {
7
+ let mockDiv;
8
+ let mockChart;
9
+ let mockDataTable;
10
+ let consoleErrorSpy;
11
+
12
+ beforeEach(() => {
13
+ // Mock DataTable
14
+ mockDataTable = {
15
+ addColumn: jest.fn(),
16
+ addRows: jest.fn(),
17
+ setCell: jest.fn()
18
+ };
19
+
20
+ // Mock Chart instances
21
+ mockChart = {
22
+ draw: jest.fn()
23
+ };
24
+
25
+ // Mock google.charts
26
+ global.google = {
27
+ charts: {
28
+ load: jest.fn(),
29
+ setOnLoadCallback: jest.fn(),
30
+ Bar: jest.fn(() => mockChart),
31
+ Line: jest.fn(() => mockChart)
32
+ },
33
+ visualization: {
34
+ DataTable: jest.fn(() => mockDataTable),
35
+ ComboChart: jest.fn(() => mockChart),
36
+ PieChart: jest.fn(() => mockChart),
37
+ events: {
38
+ addListener: jest.fn((chart, event, callback) => {
39
+ // Simulate immediate ready event for testing
40
+ if (event === 'ready') {
41
+ setTimeout(() => callback(), 0);
42
+ }
43
+ })
44
+ }
45
+ }
46
+ };
47
+
48
+ // Mock jQuery
49
+ mockDiv = {
50
+ 0: document.createElement('div'),
51
+ length: 1,
52
+ removeClass: jest.fn().mockReturnThis(),
53
+ addClass: jest.fn().mockReturnThis(),
54
+ hasClass: jest.fn(() => false),
55
+ closest: jest.fn().mockReturnThis()
56
+ };
57
+
58
+ global.$ = jest.fn((selector) => {
59
+ if (typeof selector === 'string' && selector.startsWith('#')) {
60
+ return mockDiv;
61
+ }
62
+ return mockDiv;
63
+ });
64
+
65
+ global.$.each = jest.fn((obj, callback) => {
66
+ if (Array.isArray(obj)) {
67
+ obj.forEach((item, idx) => callback(idx, item));
68
+ } else if (typeof obj === 'object') {
69
+ Object.keys(obj).forEach(key => callback(key, obj[key]));
70
+ }
71
+ });
72
+
73
+ // Spy on console.error
74
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
75
+ });
76
+
77
+ afterEach(() => {
78
+ delete global.google;
79
+ delete global.$;
80
+ consoleErrorSpy.mockRestore();
81
+ jest.clearAllMocks();
82
+ });
83
+
84
+ describe('init', () => {
85
+ test('should load Google Charts with correct packages', () => {
86
+ GoogleCharts.init();
87
+
88
+ expect(global.google.charts.load).toHaveBeenCalledWith('current', {
89
+ packages: ['bar', 'line', 'corechart']
90
+ });
91
+ });
92
+
93
+ test('should set onLoad callback when provided', () => {
94
+ const callback = jest.fn();
95
+
96
+ GoogleCharts.init(callback);
97
+
98
+ expect(global.google.charts.setOnLoadCallback).toHaveBeenCalledWith(callback);
99
+ });
100
+
101
+ test('should not set onLoad callback when undefined string', () => {
102
+ GoogleCharts.init('undefined');
103
+
104
+ expect(global.google.charts.setOnLoadCallback).not.toHaveBeenCalled();
105
+ });
106
+
107
+ test('should set onLoad callback with function', () => {
108
+ const callback = jest.fn();
109
+
110
+ GoogleCharts.init(callback);
111
+
112
+ expect(global.google.charts.setOnLoadCallback).toHaveBeenCalledWith(callback);
113
+ });
114
+ });
115
+
116
+ describe('drawCharts', () => {
117
+ test('should filter out charts with missing div_id', () => {
118
+ const chartsList = [
119
+ { div_id: 'chart1', chart_type: 'column_chart', title: 'Test' },
120
+ { chart_type: 'bar_chart', title: 'No ID' },
121
+ { div_id: 'chart2', chart_type: 'line_chart', title: 'Test 2' }
122
+ ];
123
+
124
+ const drawSpy = jest.spyOn(GoogleCharts, 'draw').mockImplementation((div, type, title, abscissa, absData, ordLabel, ordData, colors, format, height, width, callback) => {
125
+ if (callback) callback();
126
+ });
127
+
128
+ GoogleCharts.drawCharts(chartsList);
129
+
130
+ // Should only call draw for charts with div_id
131
+ expect(drawSpy).toHaveBeenCalledTimes(2);
132
+
133
+ drawSpy.mockRestore();
134
+ });
135
+
136
+ test('should filter out charts with non-existent div elements', () => {
137
+ global.$ = jest.fn((selector) => {
138
+ if (selector === '#existing') {
139
+ return { length: 1 };
140
+ }
141
+ return { length: 0 };
142
+ });
143
+
144
+ const chartsList = [
145
+ { div_id: 'existing', chart_type: 'column_chart', title: 'Test' },
146
+ { div_id: 'missing', chart_type: 'bar_chart', title: 'Missing' }
147
+ ];
148
+
149
+ const drawSpy = jest.spyOn(GoogleCharts, 'draw').mockImplementation((div, type, title, abscissa, absData, ordLabel, ordData, colors, format, height, width, callback) => {
150
+ if (callback) callback();
151
+ });
152
+
153
+ GoogleCharts.drawCharts(chartsList);
154
+
155
+ expect(drawSpy).toHaveBeenCalledTimes(1);
156
+
157
+ drawSpy.mockRestore();
158
+ });
159
+
160
+ test('should call onComplete after all charts are drawn', (done) => {
161
+ const chartsList = [
162
+ {
163
+ div_id: 'chart1',
164
+ chart_type: 'column_chart',
165
+ title: 'Chart 1',
166
+ abscissa_label: 'X',
167
+ abscissa_data: ['A', 'B'],
168
+ ordinate_label: ['Y'],
169
+ ordinate_data: [[1, 2]],
170
+ colors: ['#FF0000']
171
+ },
172
+ {
173
+ div_id: 'chart2',
174
+ chart_type: 'bar_chart',
175
+ title: 'Chart 2',
176
+ abscissa_label: 'X',
177
+ abscissa_data: ['C', 'D'],
178
+ ordinate_label: ['Y'],
179
+ ordinate_data: [[3, 4]],
180
+ colors: ['#00FF00']
181
+ }
182
+ ];
183
+
184
+ const onComplete = jest.fn();
185
+ const drawSpy = jest.spyOn(GoogleCharts, 'draw').mockImplementation((div, type, title, abscissa, absData, ordLabel, ordData, colors, format, height, width, callback) => {
186
+ // Simulate async completion
187
+ setTimeout(() => {
188
+ if (callback) callback();
189
+ }, 10);
190
+ });
191
+
192
+ GoogleCharts.drawCharts(chartsList, onComplete);
193
+
194
+ setTimeout(() => {
195
+ expect(onComplete).toHaveBeenCalledTimes(1);
196
+ expect(drawSpy).toHaveBeenCalledTimes(2);
197
+ drawSpy.mockRestore();
198
+ done();
199
+ }, 50);
200
+ });
201
+
202
+ test('should pass all chart data to draw method', () => {
203
+ const chartData = {
204
+ div_id: 'chart1',
205
+ chart_type: 'pie_chart',
206
+ title: 'Test Chart',
207
+ abscissa_label: 'Category',
208
+ abscissa_data: { A: 10, B: 20 },
209
+ ordinate_label: ['Value'],
210
+ ordinate_data: [[10, 20]],
211
+ colors: ['#FF0000', '#00FF00'],
212
+ ordinate_format: '#,##0',
213
+ height: 400,
214
+ width: 600
215
+ };
216
+
217
+ const drawSpy = jest.spyOn(GoogleCharts, 'draw').mockImplementation();
218
+
219
+ GoogleCharts.drawCharts([chartData]);
220
+
221
+ expect(drawSpy).toHaveBeenCalledWith(
222
+ mockDiv,
223
+ 'pie_chart',
224
+ 'Test Chart',
225
+ 'Category',
226
+ { A: 10, B: 20 },
227
+ ['Value'],
228
+ [[10, 20]],
229
+ ['#FF0000', '#00FF00'],
230
+ '#,##0',
231
+ 400,
232
+ 600,
233
+ expect.any(Function)
234
+ );
235
+
236
+ drawSpy.mockRestore();
237
+ });
238
+ });
239
+
240
+ describe('draw', () => {
241
+ test('should return early if div is undefined', () => {
242
+ GoogleCharts.draw(undefined, 'column_chart', 'Title', 'X', [], ['Y'], [[]], []);
243
+
244
+ expect(consoleErrorSpy).toHaveBeenCalledWith('div not found');
245
+ expect(mockDataTable.addColumn).not.toHaveBeenCalled();
246
+ });
247
+
248
+ test('should return early if div has no length', () => {
249
+ const emptyDiv = { length: 0 };
250
+
251
+ GoogleCharts.draw(emptyDiv, 'column_chart', 'Title', 'X', [], ['Y'], [[]], []);
252
+
253
+ expect(consoleErrorSpy).toHaveBeenCalledWith('div not found');
254
+ });
255
+
256
+ test('should create column chart', (done) => {
257
+ GoogleCharts.draw(
258
+ mockDiv,
259
+ 'column_chart',
260
+ 'Test Chart',
261
+ 'Month',
262
+ ['Jan', 'Feb'],
263
+ ['Sales'],
264
+ [[100, 200]],
265
+ ['#FF0000'],
266
+ null,
267
+ 400,
268
+ 600
269
+ );
270
+
271
+ setTimeout(() => {
272
+ expect(global.google.charts.Bar).toHaveBeenCalledWith(mockDiv[0]);
273
+ expect(mockChart.draw).toHaveBeenCalled();
274
+ expect(mockDiv.removeClass).toHaveBeenCalledWith('loading');
275
+ expect(mockDiv.addClass).toHaveBeenCalledWith('chart');
276
+ done();
277
+ }, 10);
278
+ });
279
+
280
+ test('should create bar chart', (done) => {
281
+ GoogleCharts.draw(
282
+ mockDiv,
283
+ 'bar_chart',
284
+ 'Test Chart',
285
+ 'Category',
286
+ ['A', 'B'],
287
+ ['Value'],
288
+ [[10, 20]],
289
+ ['#00FF00']
290
+ );
291
+
292
+ setTimeout(() => {
293
+ expect(global.google.charts.Bar).toHaveBeenCalledWith(mockDiv[0]);
294
+ expect(mockChart.draw).toHaveBeenCalled();
295
+ done();
296
+ }, 10);
297
+ });
298
+
299
+ test('should create line chart', (done) => {
300
+ GoogleCharts.draw(
301
+ mockDiv,
302
+ 'line_chart',
303
+ 'Test Chart',
304
+ 'Time',
305
+ ['1', '2'],
306
+ ['Value'],
307
+ [[10, 20]],
308
+ ['#0000FF']
309
+ );
310
+
311
+ setTimeout(() => {
312
+ expect(global.google.charts.Line).toHaveBeenCalledWith(mockDiv[0]);
313
+ expect(mockChart.draw).toHaveBeenCalled();
314
+ done();
315
+ }, 10);
316
+ });
317
+
318
+ test('should create combo chart', (done) => {
319
+ GoogleCharts.draw(
320
+ mockDiv,
321
+ 'combo_chart',
322
+ 'Test Chart',
323
+ 'Month',
324
+ ['Jan', 'Feb'],
325
+ ['Sales', 'Profit'],
326
+ [[100, 200], [10, 20]],
327
+ ['#FF0000', '#00FF00']
328
+ );
329
+
330
+ setTimeout(() => {
331
+ expect(global.google.visualization.ComboChart).toHaveBeenCalledWith(mockDiv[0]);
332
+ expect(mockChart.draw).toHaveBeenCalled();
333
+ done();
334
+ }, 10);
335
+ });
336
+
337
+ test('should create pie chart', (done) => {
338
+ GoogleCharts.draw(
339
+ mockDiv,
340
+ 'pie_chart',
341
+ 'Test Chart',
342
+ 'Category',
343
+ { A: 30, B: 50, C: 20 },
344
+ [],
345
+ [],
346
+ ['#FF0000', '#00FF00', '#0000FF']
347
+ );
348
+
349
+ setTimeout(() => {
350
+ expect(global.google.visualization.PieChart).toHaveBeenCalledWith(mockDiv[0]);
351
+ expect(mockDataTable.addColumn).toHaveBeenCalledWith('string', 'Category');
352
+ expect(mockDataTable.addColumn).toHaveBeenCalledWith('number', '');
353
+ expect(mockDataTable.addRows).toHaveBeenCalledTimes(3);
354
+ expect(mockChart.draw).toHaveBeenCalled();
355
+ done();
356
+ }, 10);
357
+ });
358
+
359
+ test('should handle stacked_bar_chart type', (done) => {
360
+ GoogleCharts.draw(
361
+ mockDiv,
362
+ 'stacked_bar_chart',
363
+ 'Test Chart',
364
+ 'Category',
365
+ ['A', 'B'],
366
+ ['Val1', 'Val2'],
367
+ [[10, 20], [30, 40]],
368
+ ['#FF0000', '#00FF00']
369
+ );
370
+
371
+ setTimeout(() => {
372
+ expect(global.google.charts.Bar).toHaveBeenCalled();
373
+ const callArgs = mockChart.draw.mock.calls[0];
374
+ expect(callArgs[1].isStacked).toBe(true);
375
+ done();
376
+ }, 10);
377
+ });
378
+
379
+ test('should handle stacked_column_chart type', (done) => {
380
+ GoogleCharts.draw(
381
+ mockDiv,
382
+ 'stacked_column_chart',
383
+ 'Test Chart',
384
+ 'Month',
385
+ ['Jan', 'Feb'],
386
+ ['Sales', 'Costs'],
387
+ [[100, 200], [50, 80]],
388
+ ['#FF0000', '#00FF00']
389
+ );
390
+
391
+ setTimeout(() => {
392
+ expect(global.google.charts.Bar).toHaveBeenCalled();
393
+ const callArgs = mockChart.draw.mock.calls[0];
394
+ expect(callArgs[1].isStacked).toBe(true);
395
+ done();
396
+ }, 10);
397
+ });
398
+
399
+ test('should handle stacked_combo_chart type', (done) => {
400
+ GoogleCharts.draw(
401
+ mockDiv,
402
+ 'stacked_combo_chart',
403
+ 'Test Chart',
404
+ 'Month',
405
+ ['Jan', 'Feb'],
406
+ ['Sales', 'Costs'],
407
+ [[100, 200], [50, 80]],
408
+ ['#FF0000', '#00FF00']
409
+ );
410
+
411
+ setTimeout(() => {
412
+ expect(global.google.visualization.ComboChart).toHaveBeenCalled();
413
+ const callArgs = mockChart.draw.mock.calls[0];
414
+ expect(callArgs[1].isStacked).toBe(true);
415
+ done();
416
+ }, 10);
417
+ });
418
+
419
+ test('should handle dual_column_chart type', (done) => {
420
+ GoogleCharts.draw(
421
+ mockDiv,
422
+ 'dual_column_chart',
423
+ 'Test Chart',
424
+ 'Month',
425
+ ['Jan', 'Feb'],
426
+ ['Sales', 'Profit'],
427
+ [[100, 200], [10, 20]],
428
+ ['#FF0000', '#00FF00']
429
+ );
430
+
431
+ setTimeout(() => {
432
+ expect(global.google.charts.Bar).toHaveBeenCalled();
433
+ const callArgs = mockChart.draw.mock.calls[0];
434
+ expect(callArgs[1].series).toBeDefined();
435
+ expect(callArgs[1].axes).toBeDefined();
436
+ done();
437
+ }, 10);
438
+ });
439
+
440
+ test('should handle dual_bar_chart type', (done) => {
441
+ GoogleCharts.draw(
442
+ mockDiv,
443
+ 'dual_bar_chart',
444
+ 'Test Chart',
445
+ 'Category',
446
+ ['A', 'B'],
447
+ ['Val1', 'Val2'],
448
+ [[10, 20], [30, 40]],
449
+ ['#FF0000', '#00FF00']
450
+ );
451
+
452
+ setTimeout(() => {
453
+ expect(global.google.charts.Bar).toHaveBeenCalled();
454
+ const callArgs = mockChart.draw.mock.calls[0];
455
+ expect(callArgs[1].series).toBeDefined();
456
+ expect(callArgs[1].axes).toBeDefined();
457
+ done();
458
+ }, 10);
459
+ });
460
+
461
+ test('should apply format to vAxis when provided', (done) => {
462
+ GoogleCharts.draw(
463
+ mockDiv,
464
+ 'column_chart',
465
+ 'Test Chart',
466
+ 'Month',
467
+ ['Jan', 'Feb'],
468
+ ['Sales'],
469
+ [[100, 200]],
470
+ ['#FF0000'],
471
+ '#,##0.00'
472
+ );
473
+
474
+ setTimeout(() => {
475
+ const callArgs = mockChart.draw.mock.calls[0];
476
+ expect(callArgs[1].vAxis.format).toBe('#,##0.00');
477
+ done();
478
+ }, 10);
479
+ });
480
+
481
+ test('should set custom height when provided', (done) => {
482
+ GoogleCharts.draw(
483
+ mockDiv,
484
+ 'column_chart',
485
+ 'Test Chart',
486
+ 'Month',
487
+ ['Jan', 'Feb'],
488
+ ['Sales'],
489
+ [[100, 200]],
490
+ ['#FF0000'],
491
+ null,
492
+ 500
493
+ );
494
+
495
+ setTimeout(() => {
496
+ const callArgs = mockChart.draw.mock.calls[0];
497
+ expect(callArgs[1].height).toBe(500);
498
+ done();
499
+ }, 10);
500
+ });
501
+
502
+ test('should handle null chart creation', () => {
503
+ GoogleCharts.draw(
504
+ mockDiv,
505
+ 'unknown_type',
506
+ 'Test Chart',
507
+ 'X',
508
+ ['A'],
509
+ ['Y'],
510
+ [[1]],
511
+ ['#FF0000']
512
+ );
513
+
514
+ expect(consoleErrorSpy).toHaveBeenCalledWith('error during creating chart');
515
+ expect(mockDiv.addClass).toHaveBeenCalledWith('graphique_error');
516
+ expect(mockDiv[0].innerHTML).toContain('erreur');
517
+ });
518
+
519
+ test('should call onComplete callback when chart is ready', (done) => {
520
+ const onComplete = jest.fn();
521
+
522
+ GoogleCharts.draw(
523
+ mockDiv,
524
+ 'column_chart',
525
+ 'Test Chart',
526
+ 'Month',
527
+ ['Jan', 'Feb'],
528
+ ['Sales'],
529
+ [[100, 200]],
530
+ ['#FF0000'],
531
+ null,
532
+ null,
533
+ null,
534
+ onComplete
535
+ );
536
+
537
+ setTimeout(() => {
538
+ expect(onComplete).toHaveBeenCalledWith(mockChart);
539
+ done();
540
+ }, 10);
541
+ });
542
+
543
+ test('should handle tab-pane not active scenario', (done) => {
544
+ const tabPaneDiv = {
545
+ hasClass: jest.fn((className) => className === 'active' ? false : true),
546
+ addClass: jest.fn().mockReturnThis(),
547
+ removeClass: jest.fn().mockReturnThis()
548
+ };
549
+
550
+ mockDiv.hasClass = jest.fn(() => false);
551
+ mockDiv.closest = jest.fn(() => tabPaneDiv);
552
+
553
+ GoogleCharts.draw(
554
+ mockDiv,
555
+ 'column_chart',
556
+ 'Test Chart',
557
+ 'Month',
558
+ ['Jan'],
559
+ ['Sales'],
560
+ [[100]],
561
+ ['#FF0000']
562
+ );
563
+
564
+ setTimeout(() => {
565
+ expect(tabPaneDiv.addClass).toHaveBeenCalledWith('active');
566
+ expect(tabPaneDiv.removeClass).toHaveBeenCalledWith('active');
567
+ done();
568
+ }, 10);
569
+ });
570
+
571
+ test('should not toggle active class if already active', (done) => {
572
+ const tabPaneDiv = {
573
+ hasClass: jest.fn(() => true),
574
+ addClass: jest.fn().mockReturnThis(),
575
+ removeClass: jest.fn().mockReturnThis()
576
+ };
577
+
578
+ mockDiv.hasClass = jest.fn(() => false);
579
+ mockDiv.closest = jest.fn(() => tabPaneDiv);
580
+
581
+ GoogleCharts.draw(
582
+ mockDiv,
583
+ 'column_chart',
584
+ 'Test Chart',
585
+ 'Month',
586
+ ['Jan'],
587
+ ['Sales'],
588
+ [[100]],
589
+ ['#FF0000']
590
+ );
591
+
592
+ setTimeout(() => {
593
+ expect(tabPaneDiv.removeClass).not.toHaveBeenCalledWith('active');
594
+ done();
595
+ }, 10);
596
+ });
597
+
598
+ test('should handle when div itself has tab-pane class', (done) => {
599
+ mockDiv.hasClass = jest.fn((className) => className === 'tab-pane');
600
+
601
+ GoogleCharts.draw(
602
+ mockDiv,
603
+ 'column_chart',
604
+ 'Test Chart',
605
+ 'Month',
606
+ ['Jan'],
607
+ ['Sales'],
608
+ [[100]],
609
+ ['#FF0000']
610
+ );
611
+
612
+ setTimeout(() => {
613
+ expect(mockDiv.closest).not.toHaveBeenCalled();
614
+ done();
615
+ }, 10);
616
+ });
617
+
618
+ test('should set line chart specific options', (done) => {
619
+ GoogleCharts.draw(
620
+ mockDiv,
621
+ 'line_chart',
622
+ 'Test Chart',
623
+ 'Time',
624
+ ['1', '2'],
625
+ ['Value'],
626
+ [[10, 20]],
627
+ ['#0000FF']
628
+ );
629
+
630
+ setTimeout(() => {
631
+ const callArgs = mockChart.draw.mock.calls[0];
632
+ expect(callArgs[1].series).toEqual([{ lineWidth: 3 }, { lineWidth: 1.5 }]);
633
+ expect(callArgs[1].curveType).toBe('function');
634
+ done();
635
+ }, 10);
636
+ });
637
+
638
+ test('should set pie chart specific options', (done) => {
639
+ GoogleCharts.draw(
640
+ mockDiv,
641
+ 'pie_chart',
642
+ 'Test Chart',
643
+ 'Category',
644
+ { A: 30, B: 50 },
645
+ [],
646
+ [],
647
+ ['#FF0000', '#00FF00']
648
+ );
649
+
650
+ setTimeout(() => {
651
+ const callArgs = mockChart.draw.mock.calls[0];
652
+ expect(callArgs[1].is3D).toBe(false);
653
+ expect(callArgs[1].pieResidueSliceLabel).toBe('Autre');
654
+ expect(callArgs[1].legend.position).toBe('right');
655
+ done();
656
+ }, 10);
657
+ });
658
+
659
+ test('should set bar chart specific options', (done) => {
660
+ GoogleCharts.draw(
661
+ mockDiv,
662
+ 'bar_chart',
663
+ 'Test Chart',
664
+ 'Category',
665
+ ['A', 'B'],
666
+ ['Value'],
667
+ [[10, 20]],
668
+ ['#00FF00']
669
+ );
670
+
671
+ setTimeout(() => {
672
+ const callArgs = mockChart.draw.mock.calls[0];
673
+ expect(callArgs[1].bars).toBe('horizontal');
674
+ expect(callArgs[1].legend.position).toBe('bottom');
675
+ expect(callArgs[1].vAxis.title).toBe('Category');
676
+ done();
677
+ }, 10);
678
+ });
679
+
680
+ test('should populate DataTable correctly for non-pie charts', (done) => {
681
+ GoogleCharts.draw(
682
+ mockDiv,
683
+ 'column_chart',
684
+ 'Test Chart',
685
+ 'Month',
686
+ ['Jan', 'Feb', 'Mar'],
687
+ ['Sales', 'Profit'],
688
+ [[100, 200, 300], [10, 20, 30]],
689
+ ['#FF0000', '#00FF00']
690
+ );
691
+
692
+ setTimeout(() => {
693
+ expect(mockDataTable.addColumn).toHaveBeenCalledWith('string', 'Month');
694
+ expect(mockDataTable.addColumn).toHaveBeenCalledWith('number', 'Sales');
695
+ expect(mockDataTable.addColumn).toHaveBeenCalledWith('number', 'Profit');
696
+ expect(mockDataTable.addRows).toHaveBeenCalledTimes(3);
697
+ expect(mockDataTable.setCell).toHaveBeenCalled();
698
+ done();
699
+ }, 10);
700
+ });
701
+
702
+ test('should handle combo chart with multiple series', (done) => {
703
+ GoogleCharts.draw(
704
+ mockDiv,
705
+ 'combo_chart',
706
+ 'Test Chart',
707
+ 'Month',
708
+ ['Jan', 'Feb'],
709
+ ['Sales', 'Profit', 'Trend'],
710
+ [[100, 200], [10, 20], [50, 60]],
711
+ ['#FF0000', '#00FF00', '#0000FF']
712
+ );
713
+
714
+ setTimeout(() => {
715
+ const callArgs = mockChart.draw.mock.calls[0];
716
+ expect(callArgs[1].seriesType).toBe('bars');
717
+ expect(callArgs[1].series).toBeDefined();
718
+ // Last series (index 2 with 3 series) should be line type
719
+ expect(callArgs[1].series[2]).toEqual({ type: 'line' });
720
+ done();
721
+ }, 10);
722
+ });
723
+
724
+ test('should apply dual axis formatting for dual_column_chart', (done) => {
725
+ GoogleCharts.draw(
726
+ mockDiv,
727
+ 'dual_column_chart',
728
+ 'Test Chart',
729
+ 'Month',
730
+ ['Jan', 'Feb'],
731
+ ['Sales', 'Profit'],
732
+ [[100, 200], [10, 20]],
733
+ ['#FF0000', '#00FF00'],
734
+ '$#,##0'
735
+ );
736
+
737
+ setTimeout(() => {
738
+ const callArgs = mockChart.draw.mock.calls[0];
739
+ expect(callArgs[1].vAxes).toBeDefined();
740
+ expect(callArgs[1].vAxes[0]).toEqual({ format: '$#,##0' });
741
+ expect(callArgs[1].axes.y[1].side).toBe('right');
742
+ done();
743
+ }, 10);
744
+ });
745
+
746
+ test('should apply dual axis formatting for dual_bar_chart', (done) => {
747
+ GoogleCharts.draw(
748
+ mockDiv,
749
+ 'dual_bar_chart',
750
+ 'Test Chart',
751
+ 'Category',
752
+ ['A', 'B'],
753
+ ['Val1', 'Val2'],
754
+ [[10, 20], [30, 40]],
755
+ ['#FF0000', '#00FF00'],
756
+ '#,##0.00'
757
+ );
758
+
759
+ setTimeout(() => {
760
+ const callArgs = mockChart.draw.mock.calls[0];
761
+ expect(callArgs[1].hAxes).toBeDefined();
762
+ expect(callArgs[1].hAxes[0]).toEqual({ format: '#,##0.00' });
763
+ expect(callArgs[1].axes.x[1].side).toBe('top');
764
+ done();
765
+ }, 10);
766
+ });
767
+ });
768
+ });