@osimatic/helpers-js 1.4.24 → 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 (68) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/chartjs.js +1 -1
  3. package/date_time.js +25 -16
  4. package/draw.js +3 -2
  5. package/duration.js +176 -130
  6. package/event_bus.js +2 -2
  7. package/file.js +20 -5
  8. package/form_helper.js +1 -1
  9. package/google_charts.js +2 -1
  10. package/http_client.js +2 -0
  11. package/jwt.js +18 -6
  12. package/location.js +5 -1
  13. package/media.js +7 -7
  14. package/multi_files_input.js +3 -1
  15. package/number.js +2 -3
  16. package/package.json +4 -2
  17. package/paging.js +2 -2
  18. package/social_network.js +5 -0
  19. package/string.js +11 -2
  20. package/tests/__mocks__/socket.io-client.js +13 -0
  21. package/tests/chartjs.test.js +273 -0
  22. package/tests/count_down.test.js +580 -0
  23. package/tests/date_time/DatePeriod.test.js +179 -0
  24. package/tests/date_time/DateTime.test.js +492 -0
  25. package/tests/date_time/SqlDate.test.js +205 -0
  26. package/tests/date_time/SqlDateTime.test.js +326 -0
  27. package/tests/date_time/SqlTime.test.js +162 -0
  28. package/tests/date_time/TimestampUnix.test.js +262 -0
  29. package/tests/details_sub_array.test.js +367 -0
  30. package/tests/draw.test.js +271 -0
  31. package/tests/duration.test.js +365 -0
  32. package/tests/event_bus.test.js +268 -0
  33. package/tests/file.test.js +568 -0
  34. package/tests/flash_message.test.js +297 -0
  35. package/tests/form_date.test.js +1559 -0
  36. package/tests/form_helper.test.js +1065 -0
  37. package/tests/google_charts.test.js +768 -0
  38. package/tests/google_maps.test.js +655 -0
  39. package/tests/google_recaptcha.test.js +441 -0
  40. package/tests/http_client.test.js +570 -0
  41. package/tests/import_from_csv.test.js +797 -0
  42. package/tests/jwt.test.js +804 -0
  43. package/tests/list_box.test.js +255 -0
  44. package/tests/location.test.js +86 -0
  45. package/tests/media.test.js +473 -0
  46. package/tests/multi_files_input.test.js +1015 -0
  47. package/tests/multiple_action_in_table.test.js +477 -0
  48. package/tests/network.test.js +489 -0
  49. package/tests/number.test.js +448 -0
  50. package/tests/open_street_map.test.js +388 -0
  51. package/tests/paging.test.js +646 -0
  52. package/tests/select_all.test.js +360 -0
  53. package/tests/shopping_cart.test.js +355 -0
  54. package/tests/social_network.test.js +333 -0
  55. package/tests/sortable_list.test.js +602 -0
  56. package/tests/string.test.js +489 -0
  57. package/tests/user.test.js +204 -0
  58. package/tests/util.test.js +99 -0
  59. package/tests/visitor.test.js +508 -0
  60. package/tests/web_rtc.test.js +458 -0
  61. package/tests/web_socket.test.js +538 -0
  62. package/visitor.js +2 -2
  63. package/tmpclaude-0fa4-cwd +0 -1
  64. package/tmpclaude-104f-cwd +0 -1
  65. package/tmpclaude-1468-cwd +0 -1
  66. package/tmpclaude-324b-cwd +0 -1
  67. package/tmpclaude-35d3-cwd +0 -1
  68. package/tmpclaude-4aa8-cwd +0 -1
@@ -0,0 +1,262 @@
1
+ const { TimestampUnix } = require('../../date_time');
2
+
3
+ describe('TimestampUnix', () => {
4
+ describe('parse', () => {
5
+ test('should parse Unix timestamp to Date', () => {
6
+ const timestamp = 1705314600; // 2024-01-15 10:30:00 UTC
7
+ const result = TimestampUnix.parse(timestamp);
8
+ expect(result instanceof Date).toBe(true);
9
+ expect(result.getTime()).toBe(1705314600000);
10
+ });
11
+
12
+ test('should return null for null input', () => {
13
+ expect(TimestampUnix.parse(null)).toBeNull();
14
+ });
15
+
16
+ test('should handle timestamp 0', () => {
17
+ const result = TimestampUnix.parse(0);
18
+ expect(result.getTime()).toBe(0);
19
+ });
20
+ });
21
+
22
+ describe('getCurrent', () => {
23
+ test('should return current Unix timestamp', () => {
24
+ const result = TimestampUnix.getCurrent();
25
+ const now = Math.trunc(Date.now() / 1000);
26
+ expect(result).toBeCloseTo(now, 0);
27
+ });
28
+
29
+ test('should return an integer', () => {
30
+ const result = TimestampUnix.getCurrent();
31
+ expect(Number.isInteger(result)).toBe(true);
32
+ });
33
+ });
34
+
35
+ describe('getDateDigitalDisplay', () => {
36
+ test('should format timestamp date in digital format', () => {
37
+ const timestamp = 1705314600;
38
+ const result = TimestampUnix.getDateDigitalDisplay(timestamp, 'fr-FR', 'UTC');
39
+ expect(result).toContain('15');
40
+ expect(result).toContain('01');
41
+ expect(result).toContain('2024');
42
+ });
43
+ });
44
+
45
+ describe('getDateTextDisplay', () => {
46
+ test('should format timestamp date in text format', () => {
47
+ const timestamp = 1705314600;
48
+ const result = TimestampUnix.getDateTextDisplay(timestamp, 'fr-FR', 'UTC');
49
+ expect(typeof result).toBe('string');
50
+ expect(result.length).toBeGreaterThan(10);
51
+ });
52
+ });
53
+
54
+ describe('getTimeDisplay', () => {
55
+ test('should format timestamp time', () => {
56
+ const timestamp = 1705314600;
57
+ const result = TimestampUnix.getTimeDisplay(timestamp, 'fr-FR', 'UTC');
58
+ expect(result).toContain('10');
59
+ expect(result).toContain('30');
60
+ });
61
+ });
62
+
63
+ describe('getTimeDisplayWithNbDays', () => {
64
+ test('should display time without days difference', () => {
65
+ const timestamp = 1705314600;
66
+ const result = TimestampUnix.getTimeDisplayWithNbDays(timestamp, 0, 'fr-FR', 'UTC');
67
+ expect(result).toContain('10');
68
+ expect(result).toContain('30');
69
+ });
70
+
71
+ test('should display time with days difference', () => {
72
+ const timestamp1 = 1705315800; // 2024-01-15 10:30:00
73
+ const timestamp2 = 1705574400; // 2024-01-18 10:00:00
74
+ const result = TimestampUnix.getTimeDisplayWithNbDays(timestamp2, timestamp1, 'fr-FR', 'UTC');
75
+ expect(result).toContain('J+');
76
+ });
77
+ });
78
+
79
+ describe('getTimeDigitalDisplay', () => {
80
+ test('should format timestamp time in digital format', () => {
81
+ const timestamp = 1705314600;
82
+ const result = TimestampUnix.getTimeDigitalDisplay(timestamp, 'fr-FR', 'UTC');
83
+ expect(result).toMatch(/\d{2}:\d{2}:\d{2}/);
84
+ });
85
+ });
86
+
87
+ describe('getYear', () => {
88
+ test('should return year from timestamp', () => {
89
+ const timestamp = 1705314600;
90
+ const result = TimestampUnix.getYear(timestamp, 'UTC');
91
+ expect(result).toBe(2024);
92
+ });
93
+ });
94
+
95
+ describe('getMonth', () => {
96
+ test('should return month from timestamp', () => {
97
+ const timestamp = 1705314600;
98
+ const result = TimestampUnix.getMonth(timestamp, 'UTC');
99
+ expect(result).toBe(1);
100
+ });
101
+ });
102
+
103
+ describe('getDayOfMonth', () => {
104
+ test('should return day of month from timestamp', () => {
105
+ const timestamp = 1705314600;
106
+ const result = TimestampUnix.getDayOfMonth(timestamp, 'UTC');
107
+ expect(result).toBe(15);
108
+ });
109
+ });
110
+
111
+ describe('getHour', () => {
112
+ test('should return hour from timestamp', () => {
113
+ const timestamp = 1705314600;
114
+ const result = TimestampUnix.getHour(timestamp, 'UTC');
115
+ expect(result).toBe(10);
116
+ });
117
+ });
118
+
119
+ describe('getMinute', () => {
120
+ test('should return minute from timestamp', () => {
121
+ const timestamp = 1705314600;
122
+ const result = TimestampUnix.getMinute(timestamp, 'UTC');
123
+ expect(result).toBe(30);
124
+ });
125
+ });
126
+
127
+ describe('getSecond', () => {
128
+ test('should return second from timestamp', () => {
129
+ const timestamp = 1705314600;
130
+ const result = TimestampUnix.getSecond(timestamp, 'UTC');
131
+ expect(result).toBe(0);
132
+ });
133
+
134
+ test('should return 45 seconds', () => {
135
+ const timestamp = 1705315845;
136
+ const result = TimestampUnix.getSecond(timestamp, 'UTC');
137
+ expect(result).toBe(45);
138
+ });
139
+ });
140
+
141
+ describe('getSqlDateTime', () => {
142
+ test('should convert timestamp to SQL datetime', () => {
143
+ const timestamp = 1705314600;
144
+ const result = TimestampUnix.getSqlDateTime(timestamp, 'UTC');
145
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
146
+ });
147
+ });
148
+
149
+ describe('getSqlDate', () => {
150
+ test('should convert timestamp to SQL date', () => {
151
+ const timestamp = 1705314600;
152
+ const result = TimestampUnix.getSqlDate(timestamp, 'UTC');
153
+ expect(result).toBe('2024-01-15');
154
+ });
155
+ });
156
+
157
+ describe('getSqlTime', () => {
158
+ test('should convert timestamp to SQL time', () => {
159
+ const timestamp = 1705314600;
160
+ const result = TimestampUnix.getSqlTime(timestamp, 'UTC');
161
+ expect(result).toBe('10:30:00');
162
+ });
163
+ });
164
+
165
+ describe('getDateForInputDate', () => {
166
+ test('should return date for input field', () => {
167
+ const timestamp = 1705314600;
168
+ const result = TimestampUnix.getDateForInputDate(timestamp, 'UTC');
169
+ expect(result).toBe('2024-01-15');
170
+ });
171
+ });
172
+
173
+ describe('getTimeForInputTime', () => {
174
+ test('should return time for input field without seconds', () => {
175
+ const timestamp = 1705314600;
176
+ const result = TimestampUnix.getTimeForInputTime(timestamp, 'UTC', false);
177
+ expect(result).toMatch(/^\d{2}:\d{2}$/);
178
+ });
179
+
180
+ test('should return time for input field with seconds', () => {
181
+ const timestamp = 1705314600;
182
+ const result = TimestampUnix.getTimeForInputTime(timestamp, 'UTC', true);
183
+ expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/);
184
+ });
185
+ });
186
+
187
+ describe('isDateEqual', () => {
188
+ test('should return true for timestamps on same date', () => {
189
+ const timestamp1 = 1705315800; // 2024-01-15 10:30:00
190
+ const timestamp2 = 1705329600; // 2024-01-15 14:20:00
191
+ expect(TimestampUnix.isDateEqual(timestamp1, timestamp2)).toBe(true);
192
+ });
193
+
194
+ test('should return false for timestamps on different dates', () => {
195
+ const timestamp1 = 1705315800; // 2024-01-15
196
+ const timestamp2 = 1705402200; // 2024-01-16
197
+ expect(TimestampUnix.isDateEqual(timestamp1, timestamp2)).toBe(false);
198
+ });
199
+ });
200
+
201
+ describe('getNbDayBetweenTwo', () => {
202
+ test('should calculate days between two timestamps', () => {
203
+ const timestamp1 = 1705315800; // 2024-01-15 10:30:00
204
+ const timestamp2 = 1705747800; // 2024-01-20 10:30:00
205
+ expect(TimestampUnix.getNbDayBetweenTwo(timestamp1, timestamp2, true, 'UTC')).toBe(5);
206
+ });
207
+
208
+ test('should return 0 for same day', () => {
209
+ const timestamp1 = 1705315800; // 2024-01-15 10:30:00
210
+ const timestamp2 = 1705329600; // 2024-01-15 14:20:00
211
+ expect(TimestampUnix.getNbDayBetweenTwo(timestamp1, timestamp2, false, 'UTC')).toBe(0);
212
+ });
213
+ });
214
+
215
+ describe('isDateInThePast', () => {
216
+ test('should return true for past timestamp', () => {
217
+ const timestamp = 1577836800; // 2020-01-01
218
+ expect(TimestampUnix.isDateInThePast(timestamp)).toBe(true);
219
+ });
220
+
221
+ test('should return false for future timestamp', () => {
222
+ const timestamp = 1893456000; // 2030-01-01
223
+ expect(TimestampUnix.isDateInThePast(timestamp)).toBe(false);
224
+ });
225
+ });
226
+
227
+ describe('isDateTimeInThePast', () => {
228
+ test('should return true for past timestamp', () => {
229
+ const timestamp = 1577836800; // 2020-01-01
230
+ expect(TimestampUnix.isDateTimeInThePast(timestamp)).toBe(true);
231
+ });
232
+
233
+ test('should return false for future timestamp', () => {
234
+ const timestamp = 1893456000; // 2030-01-01
235
+ expect(TimestampUnix.isDateTimeInThePast(timestamp)).toBe(false);
236
+ });
237
+ });
238
+
239
+ describe('isDateInTheFuture', () => {
240
+ test('should return true for future timestamp', () => {
241
+ const timestamp = 1893456000; // 2030-01-01
242
+ expect(TimestampUnix.isDateInTheFuture(timestamp)).toBe(true);
243
+ });
244
+
245
+ test('should return false for past timestamp', () => {
246
+ const timestamp = 1577836800; // 2020-01-01
247
+ expect(TimestampUnix.isDateInTheFuture(timestamp)).toBe(false);
248
+ });
249
+ });
250
+
251
+ describe('isDateTimeInTheFuture', () => {
252
+ test('should return true for future timestamp', () => {
253
+ const timestamp = 1893456000; // 2030-01-01
254
+ expect(TimestampUnix.isDateTimeInTheFuture(timestamp)).toBe(true);
255
+ });
256
+
257
+ test('should return false for past timestamp', () => {
258
+ const timestamp = 1577836800; // 2020-01-01
259
+ expect(TimestampUnix.isDateTimeInTheFuture(timestamp)).toBe(false);
260
+ });
261
+ });
262
+ });
@@ -0,0 +1,367 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ const { DetailsSubArray } = require('../details_sub_array');
5
+
6
+ // Mock HTTPClient
7
+ jest.mock('../http_client', () => ({
8
+ HTTPClient: {
9
+ request: jest.fn()
10
+ }
11
+ }));
12
+
13
+ const { HTTPClient } = require('../http_client');
14
+
15
+ describe('DetailsSubArray', () => {
16
+ let mockTable;
17
+ let mockLink;
18
+ let mockTr;
19
+ let mockThead;
20
+
21
+ beforeEach(() => {
22
+ // Clear all mocks
23
+ jest.clearAllMocks();
24
+
25
+ // Define global labels used by the code
26
+ global.showDetailsLabel = 'Show details';
27
+ global.hideDetailsLabel = 'Hide details';
28
+ global.labelErrorOccured = 'An error occurred';
29
+
30
+ // Make HTTPClient available globally
31
+ global.HTTPClient = HTTPClient;
32
+
33
+ // Create mock DOM structure
34
+ mockThead = {
35
+ find: jest.fn((selector) => {
36
+ if (selector === 'thead tr') {
37
+ return {
38
+ children: jest.fn(() => ({ length: 5 }))
39
+ };
40
+ }
41
+ return {
42
+ children: jest.fn(() => ({ length: 5 }))
43
+ };
44
+ })
45
+ };
46
+
47
+ mockTr = {
48
+ closest: jest.fn((selector) => {
49
+ if (selector === 'table') {
50
+ return {
51
+ find: jest.fn((sel) => {
52
+ if (sel === 'thead tr') {
53
+ return {
54
+ children: jest.fn(() => ({ length: 5 }))
55
+ };
56
+ }
57
+ return mockThead;
58
+ })
59
+ };
60
+ }
61
+ return mockTr;
62
+ }),
63
+ after: jest.fn(),
64
+ addClass: jest.fn().mockReturnThis(),
65
+ next: jest.fn(() => ({
66
+ hasClass: jest.fn(() => false),
67
+ remove: jest.fn()
68
+ }))
69
+ };
70
+
71
+ mockLink = {
72
+ data: jest.fn((key) => {
73
+ if (key === 'url_details') return 'http://example.com/details';
74
+ return null;
75
+ }),
76
+ closest: jest.fn(() => mockTr),
77
+ prop: jest.fn().mockReturnThis(),
78
+ attr: jest.fn().mockReturnThis(),
79
+ html: jest.fn().mockReturnThis(),
80
+ click: jest.fn().mockReturnThis(),
81
+ stop: jest.fn().mockReturnThis(),
82
+ off: jest.fn().mockReturnThis(),
83
+ removeClass: jest.fn().mockReturnThis()
84
+ };
85
+
86
+ mockTable = {
87
+ find: jest.fn(() => ({
88
+ each: jest.fn((callback) => {
89
+ // Simulate one link found
90
+ callback(0, mockLink);
91
+ })
92
+ }))
93
+ };
94
+
95
+ // Mock jQuery
96
+ global.$ = jest.fn((selector) => {
97
+ if (typeof selector === 'string') {
98
+ if (selector.includes('<tr')) {
99
+ return {
100
+ find: jest.fn().mockReturnThis(),
101
+ append: jest.fn().mockReturnThis(),
102
+ after: jest.fn().mockReturnThis()
103
+ };
104
+ }
105
+ if (selector.includes('<span')) {
106
+ return selector; // Return the HTML string for glyphicon
107
+ }
108
+ if (selector.includes('<i')) {
109
+ return selector; // Return the HTML string for fa icons
110
+ }
111
+ return {
112
+ length: 0,
113
+ remove: jest.fn()
114
+ };
115
+ }
116
+ // Wrap element
117
+ return selector;
118
+ });
119
+ });
120
+
121
+ afterEach(() => {
122
+ delete global.$;
123
+ delete global.showDetailsLabel;
124
+ delete global.hideDetailsLabel;
125
+ delete global.labelErrorOccured;
126
+ delete global.HTTPClient;
127
+ });
128
+
129
+ describe('initDetailsLink', () => {
130
+ test('should initialize details links', () => {
131
+ DetailsSubArray.initDetailsLink(mockTable);
132
+
133
+ expect(mockTable.find).toHaveBeenCalledWith('a.details_link');
134
+ expect(mockLink.removeClass).toHaveBeenCalledWith('hide');
135
+ });
136
+
137
+ test('should set up click handlers on links', () => {
138
+ DetailsSubArray.initDetailsLink(mockTable);
139
+
140
+ expect(mockLink.click).toHaveBeenCalled();
141
+ });
142
+
143
+ test('should show plus button initially', () => {
144
+ DetailsSubArray.initDetailsLink(mockTable);
145
+
146
+ expect(mockLink.html).toHaveBeenCalled();
147
+ });
148
+
149
+ test('should handle callback before send', () => {
150
+ const beforeSendCallback = jest.fn(() => '<div>Custom content</div>');
151
+
152
+ DetailsSubArray.initDetailsLink(mockTable, null, null, beforeSendCallback);
153
+
154
+ // Trigger the click
155
+ const clickHandler = mockLink.click.mock.calls[0][0];
156
+ if (clickHandler) {
157
+ clickHandler.call(mockLink);
158
+ }
159
+
160
+ // beforeSendCallback should be called when clicking
161
+ // (This tests that the callback is properly wired up)
162
+ });
163
+
164
+ test('should handle success callback', () => {
165
+ const successCallback = jest.fn((jsonObj, link) => '<div>Success</div>');
166
+
167
+ DetailsSubArray.initDetailsLink(mockTable, successCallback);
168
+
169
+ // Verify the callback is passed through
170
+ expect(mockTable.find).toHaveBeenCalled();
171
+ });
172
+
173
+ test('should handle error callback', () => {
174
+ const errorCallback = jest.fn();
175
+
176
+ DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
177
+
178
+ // Verify the callback is passed through
179
+ expect(mockTable.find).toHaveBeenCalled();
180
+ });
181
+
182
+ test('should make HTTP request when link is clicked', () => {
183
+ HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
184
+ successCb({ data: 'test' });
185
+ });
186
+
187
+ const successCallback = jest.fn(() => '<div>Details</div>');
188
+ DetailsSubArray.initDetailsLink(mockTable, successCallback);
189
+
190
+ // Simulate click
191
+ const clickHandler = mockLink.click.mock.calls[0][0];
192
+ if (clickHandler) {
193
+ clickHandler.call(mockLink);
194
+ }
195
+
196
+ // HTTPClient.request should be called
197
+ expect(HTTPClient.request).toHaveBeenCalledWith(
198
+ 'GET',
199
+ 'http://example.com/details',
200
+ null,
201
+ expect.any(Function),
202
+ expect.any(Function)
203
+ );
204
+ });
205
+
206
+ test('should handle HTTP request success with null response', () => {
207
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
208
+ successCb(null);
209
+ });
210
+
211
+ DetailsSubArray.initDetailsLink(mockTable);
212
+
213
+ // Simulate click
214
+ const clickHandler = mockLink.click.mock.calls[0][0];
215
+ if (clickHandler) {
216
+ clickHandler.call(mockLink);
217
+ }
218
+
219
+ // Should display error row
220
+ expect(mockTr.after).toHaveBeenCalled();
221
+ });
222
+
223
+ test('should handle HTTP request error', () => {
224
+ HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
225
+ errorCb();
226
+ });
227
+
228
+ DetailsSubArray.initDetailsLink(mockTable);
229
+
230
+ // Simulate click
231
+ const clickHandler = mockLink.click.mock.calls[0][0];
232
+ if (clickHandler) {
233
+ clickHandler.call(mockLink);
234
+ }
235
+
236
+ // Should display error row
237
+ expect(mockTr.after).toHaveBeenCalled();
238
+ });
239
+
240
+ test('should call success callback with JSON response', () => {
241
+ const jsonResponse = { items: ['item1', 'item2'] };
242
+ const successCallback = jest.fn(() => '<div>Details</div>');
243
+
244
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
245
+ successCb(jsonResponse);
246
+ });
247
+
248
+ DetailsSubArray.initDetailsLink(mockTable, successCallback);
249
+
250
+ // Simulate click
251
+ const clickHandler = mockLink.click.mock.calls[0][0];
252
+ if (clickHandler) {
253
+ clickHandler.call(mockLink);
254
+ }
255
+
256
+ // Success callback should be called with JSON response
257
+ expect(successCallback).toHaveBeenCalledWith(jsonResponse, mockLink);
258
+ });
259
+
260
+ test('should call error callback on HTTP error', () => {
261
+ const errorCallback = jest.fn();
262
+
263
+ HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
264
+ errorCb();
265
+ });
266
+
267
+ DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
268
+
269
+ // Simulate click
270
+ const clickHandler = mockLink.click.mock.calls[0][0];
271
+ if (clickHandler) {
272
+ clickHandler.call(mockLink);
273
+ }
274
+
275
+ // Error callback should be called
276
+ expect(errorCallback).toHaveBeenCalledWith(mockLink);
277
+ });
278
+
279
+ test('should call error callback when response is null', () => {
280
+ const errorCallback = jest.fn();
281
+
282
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
283
+ successCb(null);
284
+ });
285
+
286
+ DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
287
+
288
+ // Simulate click
289
+ const clickHandler = mockLink.click.mock.calls[0][0];
290
+ if (clickHandler) {
291
+ clickHandler.call(mockLink);
292
+ }
293
+
294
+ // Error callback should be called
295
+ expect(errorCallback).toHaveBeenCalledWith(mockLink);
296
+ });
297
+
298
+ test('should use before send callback instead of HTTP request', () => {
299
+ const beforeSendCallback = jest.fn(() => '<div>Immediate content</div>');
300
+
301
+ DetailsSubArray.initDetailsLink(mockTable, null, null, beforeSendCallback);
302
+
303
+ // Simulate click
304
+ const clickHandler = mockLink.click.mock.calls[0][0];
305
+ if (clickHandler) {
306
+ clickHandler.call(mockLink);
307
+ }
308
+
309
+ // Before send callback should be called
310
+ expect(beforeSendCallback).toHaveBeenCalledWith(mockLink);
311
+
312
+ // HTTP request should NOT be made
313
+ expect(HTTPClient.request).not.toHaveBeenCalled();
314
+ });
315
+
316
+ test('should handle multiple links', () => {
317
+ const mockLink2 = { ...mockLink };
318
+
319
+ mockTable.find = jest.fn(() => ({
320
+ each: jest.fn((callback) => {
321
+ callback(0, mockLink);
322
+ callback(1, mockLink2);
323
+ })
324
+ }));
325
+
326
+ DetailsSubArray.initDetailsLink(mockTable);
327
+
328
+ // Both links should be initialized
329
+ expect(mockLink.removeClass).toHaveBeenCalled();
330
+ });
331
+
332
+ test('should disable link while loading', () => {
333
+ HTTPClient.request.mockImplementation(() => {
334
+ // Don't call callbacks, simulate loading state
335
+ });
336
+
337
+ DetailsSubArray.initDetailsLink(mockTable);
338
+
339
+ // Simulate click
340
+ const clickHandler = mockLink.click.mock.calls[0][0];
341
+ if (clickHandler) {
342
+ clickHandler.call(mockLink);
343
+ }
344
+
345
+ // Link should be disabled during loading
346
+ expect(mockLink.attr).toHaveBeenCalledWith('disabled', true);
347
+ });
348
+
349
+ test('should show loading icon', () => {
350
+ HTTPClient.request.mockImplementation(() => {
351
+ // Loading state
352
+ });
353
+
354
+ DetailsSubArray.initDetailsLink(mockTable);
355
+
356
+ // Simulate click
357
+ const clickHandler = mockLink.click.mock.calls[0][0];
358
+ if (clickHandler) {
359
+ clickHandler.call(mockLink);
360
+ }
361
+
362
+ // Should display loading icon
363
+ expect(mockLink.html).toHaveBeenCalled();
364
+ expect(mockTr.after).toHaveBeenCalled();
365
+ });
366
+ });
367
+ });