@osimatic/helpers-js 1.5.2 → 1.5.4

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.
@@ -2,278 +2,219 @@
2
2
  * @jest-environment jsdom
3
3
  */
4
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
5
  const { HTTPClient } = require('../http_client');
14
6
 
15
- describe('DetailsSubArray', () => {
16
- let mockTable;
17
- let mockLink;
18
- let mockTr;
19
- let mockThead;
7
+ function setupTable(nbLinks = 1) {
8
+ document.body.innerHTML = `
9
+ <table>
10
+ <thead>
11
+ <tr>
12
+ <th>Col1</th><th>Col2</th><th>Col3</th><th>Col4</th><th>Col5</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ ${Array.from({ length: nbLinks }, (_, i) => `
17
+ <tr id="tr${i}">
18
+ <td><a class="details_link hide" data-url_details="http://example.com/details/${i}">Details</a></td>
19
+ </tr>`).join('')}
20
+ </tbody>
21
+ </table>
22
+ `;
23
+ return document.querySelector('table');
24
+ }
20
25
 
26
+ describe('DetailsSubArray', () => {
21
27
  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
- });
28
+ jest.spyOn(HTTPClient, 'request').mockImplementation(() => {});
119
29
  });
120
30
 
121
31
  afterEach(() => {
122
- delete global.$;
123
- delete global.showDetailsLabel;
124
- delete global.hideDetailsLabel;
125
- delete global.labelErrorOccured;
126
- delete global.HTTPClient;
32
+ jest.restoreAllMocks();
33
+ document.body.innerHTML = '';
127
34
  });
128
35
 
129
36
  describe('initDetailsLink', () => {
130
- test('should initialize details links', () => {
131
- DetailsSubArray.initDetailsLink(mockTable);
37
+ test('should initialize details links and remove hide class', () => {
38
+ const table = setupTable();
39
+ DetailsSubArray.initDetailsLink(table);
132
40
 
133
- expect(mockTable.find).toHaveBeenCalledWith('a.details_link');
134
- expect(mockLink.removeClass).toHaveBeenCalledWith('hide');
41
+ const link = table.querySelector('a.details_link');
42
+ expect(link.classList.contains('hide')).toBe(false);
135
43
  });
136
44
 
137
- test('should set up click handlers on links', () => {
138
- DetailsSubArray.initDetailsLink(mockTable);
45
+ test('should show plus button initially', () => {
46
+ const table = setupTable();
47
+ DetailsSubArray.initDetailsLink(table);
139
48
 
140
- expect(mockLink.click).toHaveBeenCalled();
49
+ const link = table.querySelector('a.details_link');
50
+ expect(link.innerHTML).toContain('glyphicon-plus');
141
51
  });
142
52
 
143
- test('should show plus button initially', () => {
144
- DetailsSubArray.initDetailsLink(mockTable);
53
+ test('should set showDetailsLabel as title initially', () => {
54
+ const table = setupTable();
55
+ DetailsSubArray.initDetailsLink(table, { showDetailsLabel: 'Show details' });
56
+
57
+ const link = table.querySelector('a.details_link');
58
+ expect(link.title).toBe('Show details');
59
+ });
60
+
61
+ test('should make HTTP request when link is clicked', () => {
62
+ const table = setupTable();
63
+ DetailsSubArray.initDetailsLink(table);
145
64
 
146
- expect(mockLink.html).toHaveBeenCalled();
65
+ const link = table.querySelector('a.details_link');
66
+ link.click();
67
+
68
+ expect(HTTPClient.request).toHaveBeenCalledWith(
69
+ 'GET',
70
+ 'http://example.com/details/0',
71
+ null,
72
+ expect.any(Function),
73
+ expect.any(Function)
74
+ );
147
75
  });
148
76
 
149
- test('should handle callback before send', () => {
150
- const beforeSendCallback = jest.fn(() => '<div>Custom content</div>');
77
+ test('should disable link while loading', () => {
78
+ HTTPClient.request.mockImplementation(() => {
79
+ // Don't call callbacks — simulate loading state
80
+ });
151
81
 
152
- DetailsSubArray.initDetailsLink(mockTable, null, null, beforeSendCallback);
82
+ const table = setupTable();
83
+ DetailsSubArray.initDetailsLink(table);
153
84
 
154
- // Trigger the click
155
- const clickHandler = mockLink.click.mock.calls[0][0];
156
- if (clickHandler) {
157
- clickHandler.call(mockLink);
158
- }
85
+ const link = table.querySelector('a.details_link');
86
+ link.click();
159
87
 
160
- // beforeSendCallback should be called when clicking
161
- // (This tests that the callback is properly wired up)
88
+ expect(link.disabled).toBe(true);
162
89
  });
163
90
 
164
- test('should handle success callback', () => {
165
- const successCallback = jest.fn((jsonObj, link) => '<div>Success</div>');
91
+ test('should show loading row while request is pending', () => {
92
+ HTTPClient.request.mockImplementation(() => {});
93
+
94
+ const table = setupTable();
95
+ DetailsSubArray.initDetailsLink(table);
166
96
 
167
- DetailsSubArray.initDetailsLink(mockTable, successCallback);
97
+ const link = table.querySelector('a.details_link');
98
+ const tr = link.closest('tr');
99
+ link.click();
168
100
 
169
- // Verify the callback is passed through
170
- expect(mockTable.find).toHaveBeenCalled();
101
+ const loadingRow = tr.nextElementSibling;
102
+ expect(loadingRow).not.toBeNull();
103
+ expect(loadingRow.classList.contains('waiting_icon')).toBe(true);
171
104
  });
172
105
 
173
- test('should handle error callback', () => {
174
- const errorCallback = jest.fn();
106
+ test('should call success callback with JSON response', () => {
107
+ const jsonResponse = { items: ['item1', 'item2'] };
108
+ const successCallback = jest.fn(() => '<div>Details</div>');
175
109
 
176
- DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
110
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
111
+ successCb(jsonResponse);
112
+ });
113
+
114
+ const table = setupTable();
115
+ DetailsSubArray.initDetailsLink(table, { onSuccess: successCallback });
116
+
117
+ const link = table.querySelector('a.details_link');
118
+ link.click();
177
119
 
178
- // Verify the callback is passed through
179
- expect(mockTable.find).toHaveBeenCalled();
120
+ expect(successCallback).toHaveBeenCalledWith(jsonResponse, link);
180
121
  });
181
122
 
182
- test('should make HTTP request when link is clicked', () => {
183
- HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
123
+ test('should show details row after successful request', () => {
124
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
184
125
  successCb({ data: 'test' });
185
126
  });
186
127
 
187
- const successCallback = jest.fn(() => '<div>Details</div>');
188
- DetailsSubArray.initDetailsLink(mockTable, successCallback);
128
+ const table = setupTable();
129
+ DetailsSubArray.initDetailsLink(table, { onSuccess: jest.fn(() => '<div class="content">Details</div>') });
189
130
 
190
- // Simulate click
191
- const clickHandler = mockLink.click.mock.calls[0][0];
192
- if (clickHandler) {
193
- clickHandler.call(mockLink);
194
- }
131
+ const link = table.querySelector('a.details_link');
132
+ const tr = link.closest('tr');
133
+ link.click();
195
134
 
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
- );
135
+ const detailsRow = tr.nextElementSibling;
136
+ expect(detailsRow).not.toBeNull();
137
+ expect(detailsRow.classList.contains('participants')).toBe(true);
204
138
  });
205
139
 
206
- test('should handle HTTP request success with null response', () => {
140
+ test('should show minus button after successful request', () => {
207
141
  HTTPClient.request.mockImplementation((method, url, data, successCb) => {
208
- successCb(null);
142
+ successCb({ data: 'test' });
209
143
  });
210
144
 
211
- DetailsSubArray.initDetailsLink(mockTable);
145
+ const table = setupTable();
146
+ DetailsSubArray.initDetailsLink(table, { onSuccess: jest.fn(() => '') });
212
147
 
213
- // Simulate click
214
- const clickHandler = mockLink.click.mock.calls[0][0];
215
- if (clickHandler) {
216
- clickHandler.call(mockLink);
217
- }
148
+ const link = table.querySelector('a.details_link');
149
+ link.click();
218
150
 
219
- // Should display error row
220
- expect(mockTr.after).toHaveBeenCalled();
151
+ expect(link.innerHTML).toContain('glyphicon-minus');
221
152
  });
222
153
 
223
- test('should handle HTTP request error', () => {
224
- HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
225
- errorCb();
154
+ test('should set hideDetailsLabel as title after loading', () => {
155
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
156
+ successCb({ data: 'test' });
226
157
  });
227
158
 
228
- DetailsSubArray.initDetailsLink(mockTable);
159
+ const table = setupTable();
160
+ DetailsSubArray.initDetailsLink(table, {
161
+ onSuccess: jest.fn(() => ''),
162
+ hideDetailsLabel: 'Hide details',
163
+ });
229
164
 
230
- // Simulate click
231
- const clickHandler = mockLink.click.mock.calls[0][0];
232
- if (clickHandler) {
233
- clickHandler.call(mockLink);
234
- }
165
+ const link = table.querySelector('a.details_link');
166
+ link.click();
235
167
 
236
- // Should display error row
237
- expect(mockTr.after).toHaveBeenCalled();
168
+ expect(link.title).toBe('Hide details');
238
169
  });
239
170
 
240
- test('should call success callback with JSON response', () => {
241
- const jsonResponse = { items: ['item1', 'item2'] };
242
- const successCallback = jest.fn(() => '<div>Details</div>');
243
-
171
+ test('should hide details row when clicking minus button', () => {
244
172
  HTTPClient.request.mockImplementation((method, url, data, successCb) => {
245
- successCb(jsonResponse);
173
+ successCb({ data: 'test' });
246
174
  });
247
175
 
248
- DetailsSubArray.initDetailsLink(mockTable, successCallback);
176
+ const table = setupTable();
177
+ DetailsSubArray.initDetailsLink(table, { onSuccess: jest.fn(() => '<div>Details</div>') });
249
178
 
250
- // Simulate click
251
- const clickHandler = mockLink.click.mock.calls[0][0];
252
- if (clickHandler) {
253
- clickHandler.call(mockLink);
254
- }
179
+ const link = table.querySelector('a.details_link');
180
+ const tr = link.closest('tr');
181
+ link.click(); // open
182
+ expect(tr.nextElementSibling?.classList.contains('participants')).toBe(true);
255
183
 
256
- // Success callback should be called with JSON response
257
- expect(successCallback).toHaveBeenCalledWith(jsonResponse, mockLink);
184
+ link.click(); // close
185
+ expect(tr.nextElementSibling?.classList.contains('participants')).toBeFalsy();
258
186
  });
259
187
 
260
- test('should call error callback on HTTP error', () => {
261
- const errorCallback = jest.fn();
188
+ test('should display error row when response is null with no error callback', () => {
189
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
190
+ successCb(null);
191
+ });
262
192
 
193
+ const table = setupTable();
194
+ DetailsSubArray.initDetailsLink(table);
195
+
196
+ const link = table.querySelector('a.details_link');
197
+ const tr = link.closest('tr');
198
+ link.click();
199
+
200
+ expect(tr.nextElementSibling).not.toBeNull();
201
+ expect(tr.nextElementSibling.classList.contains('text-error')).toBe(true);
202
+ });
203
+
204
+ test('should display error row when HTTP request fails with no error callback', () => {
263
205
  HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
264
206
  errorCb();
265
207
  });
266
208
 
267
- DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
209
+ const table = setupTable();
210
+ DetailsSubArray.initDetailsLink(table);
268
211
 
269
- // Simulate click
270
- const clickHandler = mockLink.click.mock.calls[0][0];
271
- if (clickHandler) {
272
- clickHandler.call(mockLink);
273
- }
212
+ const link = table.querySelector('a.details_link');
213
+ const tr = link.closest('tr');
214
+ link.click();
274
215
 
275
- // Error callback should be called
276
- expect(errorCallback).toHaveBeenCalledWith(mockLink);
216
+ expect(tr.nextElementSibling).not.toBeNull();
217
+ expect(tr.nextElementSibling.classList.contains('error')).toBe(true);
277
218
  });
278
219
 
279
220
  test('should call error callback when response is null', () => {
@@ -283,85 +224,99 @@ describe('DetailsSubArray', () => {
283
224
  successCb(null);
284
225
  });
285
226
 
286
- DetailsSubArray.initDetailsLink(mockTable, null, errorCallback);
227
+ const table = setupTable();
228
+ DetailsSubArray.initDetailsLink(table, { onError: errorCallback });
229
+
230
+ const link = table.querySelector('a.details_link');
231
+ link.click();
232
+
233
+ expect(errorCallback).toHaveBeenCalledWith(link);
234
+ });
235
+
236
+ test('should call error callback on HTTP error', () => {
237
+ const errorCallback = jest.fn();
238
+
239
+ HTTPClient.request.mockImplementation((method, url, data, successCb, errorCb) => {
240
+ errorCb();
241
+ });
242
+
243
+ const table = setupTable();
244
+ DetailsSubArray.initDetailsLink(table, { onError: errorCallback });
287
245
 
288
- // Simulate click
289
- const clickHandler = mockLink.click.mock.calls[0][0];
290
- if (clickHandler) {
291
- clickHandler.call(mockLink);
292
- }
246
+ const link = table.querySelector('a.details_link');
247
+ link.click();
293
248
 
294
- // Error callback should be called
295
- expect(errorCallback).toHaveBeenCalledWith(mockLink);
249
+ expect(errorCallback).toHaveBeenCalledWith(link);
296
250
  });
297
251
 
298
252
  test('should use before send callback instead of HTTP request', () => {
299
253
  const beforeSendCallback = jest.fn(() => '<div>Immediate content</div>');
300
254
 
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
- }
255
+ const table = setupTable();
256
+ DetailsSubArray.initDetailsLink(table, { onBeforeSend: beforeSendCallback });
308
257
 
309
- // Before send callback should be called
310
- expect(beforeSendCallback).toHaveBeenCalledWith(mockLink);
258
+ const link = table.querySelector('a.details_link');
259
+ link.click();
311
260
 
312
- // HTTP request should NOT be made
261
+ expect(beforeSendCallback).toHaveBeenCalledWith(link);
313
262
  expect(HTTPClient.request).not.toHaveBeenCalled();
314
263
  });
315
264
 
316
- test('should handle multiple links', () => {
317
- const mockLink2 = { ...mockLink };
265
+ test('should show details row when using before send callback', () => {
266
+ const table = setupTable();
267
+ DetailsSubArray.initDetailsLink(table, { onBeforeSend: jest.fn(() => '<div>Immediate</div>') });
318
268
 
319
- mockTable.find = jest.fn(() => ({
320
- each: jest.fn((callback) => {
321
- callback(0, mockLink);
322
- callback(1, mockLink2);
323
- })
324
- }));
269
+ const link = table.querySelector('a.details_link');
270
+ const tr = link.closest('tr');
271
+ link.click();
325
272
 
326
- DetailsSubArray.initDetailsLink(mockTable);
273
+ const detailsRow = tr.nextElementSibling;
274
+ expect(detailsRow).not.toBeNull();
275
+ expect(detailsRow.classList.contains('participants')).toBe(true);
276
+ });
277
+
278
+ test('should handle multiple links independently', () => {
279
+ const table = setupTable(2);
280
+ DetailsSubArray.initDetailsLink(table);
327
281
 
328
- // Both links should be initialized
329
- expect(mockLink.removeClass).toHaveBeenCalled();
282
+ const links = table.querySelectorAll('a.details_link');
283
+ expect(links.length).toBe(2);
284
+ links.forEach(link => {
285
+ expect(link.innerHTML).toContain('glyphicon-plus');
286
+ expect(link.classList.contains('hide')).toBe(false);
287
+ });
330
288
  });
331
289
 
332
- test('should disable link while loading', () => {
333
- HTTPClient.request.mockImplementation(() => {
334
- // Don't call callbacks, simulate loading state
290
+ test('should use colspan matching number of columns', () => {
291
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
292
+ successCb(null);
335
293
  });
336
294
 
337
- DetailsSubArray.initDetailsLink(mockTable);
295
+ const table = setupTable();
296
+ DetailsSubArray.initDetailsLink(table);
338
297
 
339
- // Simulate click
340
- const clickHandler = mockLink.click.mock.calls[0][0];
341
- if (clickHandler) {
342
- clickHandler.call(mockLink);
343
- }
298
+ const link = table.querySelector('a.details_link');
299
+ const tr = link.closest('tr');
300
+ link.click();
344
301
 
345
- // Link should be disabled during loading
346
- expect(mockLink.attr).toHaveBeenCalledWith('disabled', true);
302
+ const errorRow = tr.nextElementSibling;
303
+ expect(errorRow.querySelector('td').getAttribute('colspan')).toBe('5');
347
304
  });
348
305
 
349
- test('should show loading icon', () => {
350
- HTTPClient.request.mockImplementation(() => {
351
- // Loading state
306
+ test('should use custom error label', () => {
307
+ HTTPClient.request.mockImplementation((method, url, data, successCb) => {
308
+ successCb(null);
352
309
  });
353
310
 
354
- DetailsSubArray.initDetailsLink(mockTable);
311
+ const table = setupTable();
312
+ DetailsSubArray.initDetailsLink(table, { labelErrorOccurred: 'Custom error' });
355
313
 
356
- // Simulate click
357
- const clickHandler = mockLink.click.mock.calls[0][0];
358
- if (clickHandler) {
359
- clickHandler.call(mockLink);
360
- }
314
+ const link = table.querySelector('a.details_link');
315
+ const tr = link.closest('tr');
316
+ link.click();
361
317
 
362
- // Should display loading icon
363
- expect(mockLink.html).toHaveBeenCalled();
364
- expect(mockTr.after).toHaveBeenCalled();
318
+ const errorRow = tr.nextElementSibling;
319
+ expect(errorRow.textContent).toContain('Custom error');
365
320
  });
366
321
  });
367
322
  });