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