@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.
@@ -3,466 +3,488 @@
3
3
  */
4
4
  const { MultipleActionInTable, MultipleActionInDivList } = require('../multiple_action_in_table');
5
5
 
6
+ // ─── helpers ────────────────────────────────────────────────────────────────
7
+
8
+ function setupTable({ nbRows = 1, withButtonsDiv = true, nested = false, tableClass = 'table-action_multiple' } = {}) {
9
+ const rows = Array.from({ length: nbRows }, (_, i) => `
10
+ <tr data-action_multiple_input_name="ids[]" data-action_multiple_item_id="${i + 1}">
11
+ <td>Item ${i + 1}</td>
12
+ </tr>`).join('');
13
+
14
+ const buttonsDiv = withButtonsDiv
15
+ ? `<div class="action_multiple_buttons hide"><button>Action</button></div>`
16
+ : '';
17
+
18
+ if (nested) {
19
+ // table.parentElement.parentElement.parentElement.nextElementSibling = buttons div
20
+ document.body.innerHTML = `
21
+ <div>
22
+ <div>
23
+ <div>
24
+ <div>
25
+ <div><table class="${tableClass}">
26
+ <thead><tr><th>Name</th></tr></thead>
27
+ <tbody>${rows}</tbody>
28
+ </table></div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ ${buttonsDiv}
33
+ </div>`;
34
+ } else {
35
+ // table.parentElement.nextElementSibling = buttons div
36
+ document.body.innerHTML = `
37
+ <div>
38
+ <div>
39
+ <table class="${tableClass}">
40
+ <thead><tr><th>Name</th></tr></thead>
41
+ <tbody>${rows}</tbody>
42
+ </table>
43
+ </div>
44
+ ${buttonsDiv}
45
+ </div>`;
46
+ }
47
+
48
+ return document.querySelector('table');
49
+ }
50
+
51
+ function setupDivList({ nbItems = 1, withButtonsDiv = true } = {}) {
52
+ const items = Array.from({ length: nbItems }, (_, i) => `
53
+ <div class="multiple_action" data-action_multiple_input_name="ids[]" data-action_multiple_item_id="${i + 1}">
54
+ Item ${i + 1}
55
+ </div>`).join('');
56
+
57
+ const buttonsDiv = withButtonsDiv
58
+ ? `<div class="action_multiple_buttons"><button>Action</button></div>`
59
+ : '';
60
+
61
+ document.body.innerHTML = `
62
+ <div id="content">
63
+ ${items}
64
+ </div>
65
+ ${buttonsDiv}`;
66
+
67
+ return document.getElementById('content');
68
+ }
69
+
70
+ // ─── MultipleActionInTable ───────────────────────────────────────────────────
71
+
6
72
  describe('MultipleActionInTable', () => {
7
- let mockTable;
8
-
9
- beforeEach(() => {
10
- // Mock jQuery
11
- global.$ = jest.fn((selector) => {
12
- if (typeof selector === 'string') {
13
- if (selector.includes('<th') || selector.includes('<td') || selector.includes('<p>') || selector.includes('<img') || selector.includes('<span') || selector.includes('<div')) {
14
- // Creating new element
15
- return {
16
- addClass: jest.fn().mockReturnThis(),
17
- removeClass: jest.fn().mockReturnThis(),
18
- hasClass: jest.fn(() => false),
19
- find: jest.fn(() => ({ length: 0 })),
20
- prepend: jest.fn().mockReturnThis(),
21
- append: jest.fn().mockReturnThis(),
22
- after: jest.fn().mockReturnThis(),
23
- remove: jest.fn().mockReturnThis()
24
- };
25
- }
26
- return {
27
- find: jest.fn(() => ({ length: 0 })),
28
- hasClass: jest.fn(() => false),
29
- addClass: jest.fn().mockReturnThis(),
30
- removeClass: jest.fn().mockReturnThis()
31
- };
32
- }
33
- // Handle element wrapping
34
- return {
35
- find: jest.fn(() => ({ length: 0 })),
36
- hasClass: jest.fn(() => false),
37
- closest: jest.fn(() => mockTable),
38
- addClass: jest.fn().mockReturnThis(),
39
- removeClass: jest.fn().mockReturnThis(),
40
- data: jest.fn()
41
- };
73
+ afterEach(() => {
74
+ document.body.innerHTML = '';
75
+ });
76
+
77
+ describe('getDivBtn', () => {
78
+ test('should return button div when found as direct next sibling', () => {
79
+ const table = setupTable();
80
+ const result = MultipleActionInTable.getDivBtn(table);
81
+ expect(result).not.toBeNull();
82
+ expect(result.classList.contains('action_multiple_buttons')).toBe(true);
83
+ });
84
+
85
+ test('should return button div when found in nested structure', () => {
86
+ const table = setupTable({ nested: true });
87
+ const result = MultipleActionInTable.getDivBtn(table);
88
+ expect(result).not.toBeNull();
89
+ expect(result.classList.contains('action_multiple_buttons')).toBe(true);
42
90
  });
43
91
 
92
+ test('should return null when button div not found', () => {
93
+ const table = setupTable({ withButtonsDiv: false });
94
+ const result = MultipleActionInTable.getDivBtn(table);
95
+ expect(result).toBeNull();
96
+ });
44
97
  });
45
98
 
46
- afterEach(() => {
47
- jest.clearAllMocks();
48
- delete global.$;
99
+ describe('initCols', () => {
100
+ test('should do nothing if table lacks table-action_multiple class', () => {
101
+ const table = setupTable({ tableClass: 'other-class' });
102
+ MultipleActionInTable.initCols(table);
103
+ expect(table.querySelector('th[data-key="select"]')).toBeNull();
104
+ });
105
+
106
+ test('should do nothing if no buttons div found', () => {
107
+ const table = setupTable({ withButtonsDiv: false, tableClass: 'table-action_multiple' });
108
+ MultipleActionInTable.initCols(table);
109
+ expect(table.querySelector('th[data-key="select"]')).toBeNull();
110
+ });
111
+
112
+ test('should add select th to thead', () => {
113
+ const table = setupTable();
114
+ MultipleActionInTable.initCols(table);
115
+ const th = table.querySelector('thead tr th[data-key="select"]');
116
+ expect(th).not.toBeNull();
117
+ });
118
+
119
+ test('should add checkbox td to each tbody row', () => {
120
+ const table = setupTable({ nbRows: 2 });
121
+ MultipleActionInTable.initCols(table);
122
+ const tds = table.querySelectorAll('tbody tr td.select');
123
+ expect(tds.length).toBe(2);
124
+ });
125
+
126
+ test('should set correct name and value on checkboxes', () => {
127
+ const table = setupTable({ nbRows: 1 });
128
+ MultipleActionInTable.initCols(table);
129
+ const cb = table.querySelector('input.action_multiple_checkbox');
130
+ expect(cb.name).toBe('ids[]');
131
+ expect(cb.value).toBe('1');
132
+ });
133
+
134
+ test('should be idempotent (no duplicate cols on second call)', () => {
135
+ const table = setupTable({ nbRows: 2 });
136
+ MultipleActionInTable.initCols(table);
137
+ MultipleActionInTable.initCols(table);
138
+ expect(table.querySelectorAll('th[data-key="select"]').length).toBe(1);
139
+ expect(table.querySelectorAll('tbody tr td.select').length).toBe(2);
140
+ });
141
+
142
+ test('should skip rows with no_items class', () => {
143
+ document.body.innerHTML = `
144
+ <div>
145
+ <div>
146
+ <table class="table-action_multiple">
147
+ <thead><tr><th>Name</th></tr></thead>
148
+ <tbody>
149
+ <tr class="no_items"><td colspan="1">No items</td></tr>
150
+ </tbody>
151
+ </table>
152
+ </div>
153
+ <div class="action_multiple_buttons hide"></div>
154
+ </div>`;
155
+ const table = document.querySelector('table');
156
+ MultipleActionInTable.initCols(table);
157
+ expect(table.querySelector('tbody tr.no_items td.select')).toBeNull();
158
+ });
49
159
  });
50
160
 
51
- describe('getDivBtn', () => {
52
- test('should return button div when found as next sibling', () => {
53
- const mockButtonDiv = {
54
- hasClass: jest.fn((className) => className === 'action_multiple_buttons')
55
- };
56
- mockTable = {
57
- parent: jest.fn(() => ({
58
- next: jest.fn(() => mockButtonDiv)
59
- }))
60
- };
161
+ describe('init', () => {
162
+ test('should do nothing if table lacks table-action_multiple class', () => {
163
+ const table = setupTable({ tableClass: 'other' });
164
+ MultipleActionInTable.init(table);
165
+ expect(table.querySelector('input.action_multiple_check_all')).toBeNull();
166
+ });
61
167
 
62
- const result = MultipleActionInTable.getDivBtn(mockTable);
168
+ test('should do nothing if no buttons div found', () => {
169
+ const table = setupTable({ withButtonsDiv: false });
170
+ MultipleActionInTable.init(table);
171
+ expect(table.querySelector('input.action_multiple_check_all')).toBeNull();
172
+ });
63
173
 
64
- expect(result).toBe(mockButtonDiv);
65
- expect(mockButtonDiv.hasClass).toHaveBeenCalledWith('action_multiple_buttons');
174
+ test('should add check-all input to first th', () => {
175
+ const table = setupTable();
176
+ MultipleActionInTable.init(table);
177
+ expect(table.querySelector('thead tr th input.action_multiple_check_all')).not.toBeNull();
66
178
  });
67
179
 
68
- test('should return button div when found in nested structure', () => {
69
- const mockButtonDiv = {
70
- hasClass: jest.fn((className) => className === 'action_multiple_buttons')
71
- };
72
- const notButtonDiv = {
73
- hasClass: jest.fn(() => false)
74
- };
75
- mockTable = {
76
- parent: jest.fn(() => ({
77
- next: jest.fn(() => notButtonDiv),
78
- parent: jest.fn(() => ({
79
- parent: jest.fn(() => ({
80
- parent: jest.fn(() => ({
81
- next: jest.fn(() => mockButtonDiv)
82
- }))
83
- }))
84
- }))
85
- }))
86
- };
87
-
88
- const result = MultipleActionInTable.getDivBtn(mockTable);
89
-
90
- expect(result).toBe(mockButtonDiv);
180
+ test('should add arrow image to buttons div once', () => {
181
+ const table = setupTable();
182
+ MultipleActionInTable.init(table);
183
+ const divBtn = MultipleActionInTable.getDivBtn(table);
184
+ expect(divBtn.querySelector('img')).not.toBeNull();
91
185
  });
92
186
 
93
- test('should return null when button div not found', () => {
94
- const notButtonDiv = {
95
- hasClass: jest.fn(() => false)
96
- };
97
- mockTable = {
98
- parent: jest.fn(() => ({
99
- next: jest.fn(() => notButtonDiv),
100
- parent: jest.fn(() => ({
101
- parent: jest.fn(() => ({
102
- parent: jest.fn(() => ({
103
- next: jest.fn(() => notButtonDiv)
104
- }))
105
- }))
106
- }))
107
- }))
108
- };
109
-
110
- const result = MultipleActionInTable.getDivBtn(mockTable);
187
+ test('should not add arrow image twice when called again', () => {
188
+ const table = setupTable();
189
+ MultipleActionInTable.init(table);
190
+ MultipleActionInTable.init(table);
191
+ const divBtn = MultipleActionInTable.getDivBtn(table);
192
+ expect(divBtn.querySelectorAll('img').length).toBe(1);
193
+ });
111
194
 
112
- expect(result).toBeNull();
195
+ test('check-all toggles all checkboxes to checked when none checked', () => {
196
+ const table = setupTable({ nbRows: 2 });
197
+ MultipleActionInTable.init(table);
198
+
199
+ const checkAll = table.querySelector('input.action_multiple_check_all');
200
+ checkAll.click();
201
+
202
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
203
+ checkboxes.forEach(cb => expect(cb.checked).toBe(true));
204
+ });
205
+
206
+ test('check-all unchecks all when all are checked', () => {
207
+ const table = setupTable({ nbRows: 2 });
208
+ MultipleActionInTable.init(table);
209
+
210
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
211
+ checkboxes.forEach(cb => { cb.checked = true; });
212
+
213
+ const checkAll = table.querySelector('input.action_multiple_check_all');
214
+ checkAll.click();
215
+
216
+ checkboxes.forEach(cb => expect(cb.checked).toBe(false));
217
+ });
218
+
219
+ test('checking a row checkbox updates check-all state', () => {
220
+ const table = setupTable({ nbRows: 2 });
221
+ MultipleActionInTable.init(table);
222
+
223
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
224
+ checkboxes.forEach(cb => {
225
+ cb.checked = true;
226
+ cb.dispatchEvent(new Event('change'));
227
+ });
228
+
229
+ const checkAll = table.querySelector('input.action_multiple_check_all');
230
+ expect(checkAll.checked).toBe(true);
113
231
  });
114
232
  });
115
233
 
116
234
  describe('updateCheckbox', () => {
117
- test('should hide select-all checkbox when no checkboxes exist', () => {
118
- const mockCheckboxSelectAll = {
119
- addClass: jest.fn(),
120
- removeClass: jest.fn()
121
- };
122
- const mockButtonDiv = {
123
- hasClass: jest.fn(() => false)
124
- };
125
- mockTable = {
126
- find: jest.fn((selector) => {
127
- if (selector === 'input.action_multiple_checkbox') {
128
- return { length: 0 };
129
- }
130
- if (selector === 'input.action_multiple_checkbox:checked') {
131
- return { length: 0 };
132
- }
133
- if (selector === 'thead tr th input.action_multiple_check_all') {
134
- return mockCheckboxSelectAll;
135
- }
136
- return { length: 0 };
137
- }),
138
- parent: jest.fn(() => ({
139
- next: jest.fn(() => mockButtonDiv),
140
- parent: jest.fn(() => ({
141
- parent: jest.fn(() => ({
142
- parent: jest.fn(() => ({
143
- next: jest.fn(() => mockButtonDiv)
144
- }))
145
- }))
146
- }))
147
- }))
148
- };
149
-
150
- MultipleActionInTable.updateCheckbox(mockTable);
151
-
152
- expect(mockCheckboxSelectAll.addClass).toHaveBeenCalledWith('hide');
153
- });
154
-
155
- test('should check select-all checkbox when all checkboxes are checked', () => {
156
- const mockCheckboxSelectAll = {
157
- addClass: jest.fn(),
158
- removeClass: jest.fn(),
159
- prop: jest.fn()
160
- };
161
- const mockButtonDiv = {
162
- hasClass: jest.fn(() => false)
163
- };
164
- mockTable = {
165
- find: jest.fn((selector) => {
166
- if (selector === 'input.action_multiple_checkbox') {
167
- return { length: 3 };
168
- }
169
- if (selector === 'input.action_multiple_checkbox:checked') {
170
- return { length: 3 };
171
- }
172
- if (selector === 'thead tr th input.action_multiple_check_all') {
173
- return mockCheckboxSelectAll;
174
- }
175
- return { length: 0 };
176
- }),
177
- parent: jest.fn(() => ({
178
- next: jest.fn(() => mockButtonDiv),
179
- parent: jest.fn(() => ({
180
- parent: jest.fn(() => ({
181
- parent: jest.fn(() => ({
182
- next: jest.fn(() => mockButtonDiv)
183
- }))
184
- }))
185
- }))
186
- }))
187
- };
188
-
189
- MultipleActionInTable.updateCheckbox(mockTable);
190
-
191
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
192
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', true);
193
- });
194
-
195
- test('should uncheck select-all checkbox when not all checkboxes are checked', () => {
196
- const mockCheckboxSelectAll = {
197
- addClass: jest.fn(),
198
- removeClass: jest.fn(),
199
- prop: jest.fn()
200
- };
201
- const mockButtonDiv = {
202
- hasClass: jest.fn(() => false)
203
- };
204
- mockTable = {
205
- find: jest.fn((selector) => {
206
- if (selector === 'input.action_multiple_checkbox') {
207
- return { length: 5 };
208
- }
209
- if (selector === 'input.action_multiple_checkbox:checked') {
210
- return { length: 2 };
211
- }
212
- if (selector === 'thead tr th input.action_multiple_check_all') {
213
- return mockCheckboxSelectAll;
214
- }
215
- return { length: 0 };
216
- }),
217
- parent: jest.fn(() => ({
218
- next: jest.fn(() => mockButtonDiv),
219
- parent: jest.fn(() => ({
220
- parent: jest.fn(() => ({
221
- parent: jest.fn(() => ({
222
- next: jest.fn(() => mockButtonDiv)
223
- }))
224
- }))
225
- }))
226
- }))
227
- };
228
-
229
- MultipleActionInTable.updateCheckbox(mockTable);
230
-
231
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
232
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', false);
235
+ test('should hide check-all when no checkboxes exist', () => {
236
+ document.body.innerHTML = `
237
+ <div>
238
+ <div>
239
+ <table class="table-action_multiple">
240
+ <thead><tr><th><input type="checkbox" class="action_multiple_check_all" /></th></tr></thead>
241
+ <tbody></tbody>
242
+ </table>
243
+ </div>
244
+ <div class="action_multiple_buttons hide"></div>
245
+ </div>`;
246
+ const table = document.querySelector('table');
247
+ MultipleActionInTable.updateCheckbox(table);
248
+ const checkAll = table.querySelector('input.action_multiple_check_all');
249
+ expect(checkAll.classList.contains('hide')).toBe(true);
250
+ });
251
+
252
+ test('should check check-all when all checkboxes are checked', () => {
253
+ const table = setupTable({ nbRows: 2 });
254
+ MultipleActionInTable.init(table);
255
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
256
+ checkboxes.forEach(cb => { cb.checked = true; });
257
+ MultipleActionInTable.updateCheckbox(table);
258
+ expect(table.querySelector('input.action_multiple_check_all').checked).toBe(true);
259
+ });
260
+
261
+ test('should uncheck check-all when not all checkboxes are checked', () => {
262
+ const table = setupTable({ nbRows: 2 });
263
+ MultipleActionInTable.init(table);
264
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
265
+ checkboxes[0].checked = true;
266
+ MultipleActionInTable.updateCheckbox(table);
267
+ expect(table.querySelector('input.action_multiple_check_all').checked).toBe(false);
233
268
  });
234
269
  });
235
270
 
236
271
  describe('showButtonsAction', () => {
237
272
  test('should return early when button div is null', () => {
238
- mockTable = {
239
- parent: jest.fn(() => ({
240
- next: jest.fn(() => ({ hasClass: jest.fn(() => false) })),
241
- parent: jest.fn(() => ({
242
- parent: jest.fn(() => ({
243
- parent: jest.fn(() => ({
244
- next: jest.fn(() => ({ hasClass: jest.fn(() => false) }))
245
- }))
246
- }))
247
- }))
248
- })),
249
- find: jest.fn(() => ({ length: 0 }))
250
- };
251
-
252
- expect(() => {
253
- MultipleActionInTable.showButtonsAction(mockTable);
254
- }).not.toThrow();
255
- });
256
-
257
- test('should show button div when items are checked', () => {
258
- const mockButtonDiv = {
259
- hasClass: jest.fn(() => true),
260
- is: jest.fn((selector) => selector === ':hidden'),
261
- removeClass: jest.fn(),
262
- addClass: jest.fn(),
263
- find: jest.fn(() => ({
264
- length: 1,
265
- remove: jest.fn(),
266
- after: jest.fn()
267
- }))
268
- };
269
- mockTable = {
270
- parent: jest.fn(() => ({
271
- next: jest.fn(() => mockButtonDiv)
272
- })),
273
- find: jest.fn((selector) => {
274
- if (selector === 'input.action_multiple_checkbox:checked') {
275
- return { length: 2 };
276
- }
277
- return { length: 0 };
278
- }),
279
- is: jest.fn(() => false)
280
- };
281
-
282
- MultipleActionInTable.showButtonsAction(mockTable);
283
-
284
- expect(mockButtonDiv.removeClass).toHaveBeenCalledWith('hide');
285
- });
286
-
287
- test('should hide button div when no items are checked', () => {
288
- const mockButtonDiv = {
289
- hasClass: jest.fn(() => true),
290
- is: jest.fn((selector) => selector === ':visible'),
291
- removeClass: jest.fn(),
292
- addClass: jest.fn(),
293
- find: jest.fn((selector) => {
294
- if (selector === 'span.no_button') {
295
- return { remove: jest.fn() };
296
- }
297
- if (selector === 'button:visible, a:visible') {
298
- return { length: 0 };
299
- }
300
- if (selector === 'img') {
301
- return { after: jest.fn() };
302
- }
303
- return {
304
- length: 0,
305
- remove: jest.fn()
306
- };
307
- })
308
- };
309
- mockTable = {
310
- parent: jest.fn(() => ({
311
- next: jest.fn(() => mockButtonDiv)
312
- })),
313
- find: jest.fn(() => ({ length: 0 })),
314
- is: jest.fn(() => false)
315
- };
316
-
317
- MultipleActionInTable.showButtonsAction(mockTable);
318
-
319
- expect(mockButtonDiv.addClass).toHaveBeenCalledWith('hide');
273
+ const table = setupTable({ withButtonsDiv: false });
274
+ expect(() => MultipleActionInTable.showButtonsAction(table)).not.toThrow();
320
275
  });
321
- });
322
- });
323
276
 
324
- describe('MultipleActionInDivList', () => {
325
- let mockContentDiv;
326
-
327
- beforeEach(() => {
328
- // Mock jQuery
329
- global.$ = jest.fn((selector) => {
330
- if (typeof selector === 'string' && (selector.includes('<') || selector.includes('img'))) {
331
- return {
332
- addClass: jest.fn().mockReturnThis(),
333
- removeClass: jest.fn().mockReturnThis(),
334
- prepend: jest.fn().mockReturnThis(),
335
- append: jest.fn().mockReturnThis()
336
- };
337
- }
338
- return {
339
- find: jest.fn(() => ({ length: 0 })),
340
- hasClass: jest.fn(() => false)
341
- };
277
+ test('should show buttons div when a checkbox is checked', () => {
278
+ const table = setupTable({ nbRows: 1 });
279
+ MultipleActionInTable.init(table);
280
+ const cb = table.querySelector('input.action_multiple_checkbox');
281
+ cb.checked = true;
282
+ MultipleActionInTable.showButtonsAction(table);
283
+ const divBtn = MultipleActionInTable.getDivBtn(table);
284
+ expect(divBtn.classList.contains('hide')).toBe(false);
285
+ });
286
+
287
+ test('should hide buttons div when no checkbox is checked', () => {
288
+ const table = setupTable({ nbRows: 1 });
289
+ MultipleActionInTable.init(table);
290
+ const divBtn = MultipleActionInTable.getDivBtn(table);
291
+ divBtn.classList.remove('hide'); // force visible
292
+ MultipleActionInTable.showButtonsAction(table);
293
+ expect(divBtn.classList.contains('hide')).toBe(true);
342
294
  });
343
295
 
296
+ test('should show "no action" message when div is visible but has no visible buttons', () => {
297
+ document.body.innerHTML = `
298
+ <div>
299
+ <div>
300
+ <table class="table-action_multiple">
301
+ <thead><tr><th>Name</th></tr></thead>
302
+ <tbody>
303
+ <tr data-action_multiple_input_name="ids[]" data-action_multiple_item_id="1">
304
+ <td>Item 1</td>
305
+ </tr>
306
+ </tbody>
307
+ </table>
308
+ </div>
309
+ <div class="action_multiple_buttons">
310
+ <img src="" alt="" />
311
+ </div>
312
+ </div>`;
313
+ const table = document.querySelector('table');
314
+ MultipleActionInTable.init(table);
315
+ const cb = table.querySelector('input.action_multiple_checkbox');
316
+ cb.checked = true;
317
+ MultipleActionInTable.showButtonsAction(table);
318
+ const divBtn = MultipleActionInTable.getDivBtn(table);
319
+ expect(divBtn.querySelector('span.no_button')).not.toBeNull();
320
+ });
344
321
  });
322
+ });
323
+
324
+ // ─── MultipleActionInDivList ─────────────────────────────────────────────────
345
325
 
326
+ describe('MultipleActionInDivList', () => {
346
327
  afterEach(() => {
347
- jest.clearAllMocks();
348
- delete global.$;
328
+ document.body.innerHTML = '';
349
329
  });
350
330
 
351
331
  describe('getButtonsDiv', () => {
352
- test('should return buttons div when found', () => {
353
- const mockButtonsDiv = {
354
- hasClass: jest.fn(() => true)
355
- };
356
- mockContentDiv = {
357
- next: jest.fn(() => mockButtonsDiv)
358
- };
332
+ test('should return buttons div when found as next sibling', () => {
333
+ const contentDiv = setupDivList();
334
+ const result = MultipleActionInDivList.getButtonsDiv(contentDiv);
335
+ expect(result).not.toBeNull();
336
+ expect(result.classList.contains('action_multiple_buttons')).toBe(true);
337
+ });
359
338
 
360
- const result = MultipleActionInDivList.getButtonsDiv(mockContentDiv);
339
+ test('should return null when buttons div not found', () => {
340
+ const contentDiv = setupDivList({ withButtonsDiv: false });
341
+ const result = MultipleActionInDivList.getButtonsDiv(contentDiv);
342
+ expect(result).toBeNull();
343
+ });
344
+ });
361
345
 
362
- expect(result).toBe(mockButtonsDiv);
363
- expect(mockButtonsDiv.hasClass).toHaveBeenCalledWith('action_multiple_buttons');
346
+ describe('init', () => {
347
+ test('should return early when no buttons div found', () => {
348
+ const contentDiv = setupDivList({ withButtonsDiv: false });
349
+ MultipleActionInDivList.init(contentDiv);
350
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).toBeNull();
364
351
  });
365
352
 
366
- test('should return null when buttons div not found', () => {
367
- const notButtonsDiv = {
368
- hasClass: jest.fn(() => false)
369
- };
370
- mockContentDiv = {
371
- next: jest.fn(() => notButtonsDiv)
372
- };
353
+ test('should return early when no .multiple_action divs found', () => {
354
+ document.body.innerHTML = `
355
+ <div id="content"></div>
356
+ <div class="action_multiple_buttons"></div>`;
357
+ const contentDiv = document.getElementById('content');
358
+ MultipleActionInDivList.init(contentDiv);
359
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).toBeNull();
360
+ });
373
361
 
374
- const result = MultipleActionInDivList.getButtonsDiv(mockContentDiv);
362
+ test('should add checkbox to each .multiple_action div', () => {
363
+ const contentDiv = setupDivList({ nbItems: 2 });
364
+ MultipleActionInDivList.init(contentDiv);
365
+ expect(contentDiv.querySelectorAll('input.action_multiple_checkbox').length).toBe(2);
366
+ });
375
367
 
376
- expect(result).toBeNull();
368
+ test('should set correct name and value on checkboxes', () => {
369
+ const contentDiv = setupDivList({ nbItems: 1 });
370
+ MultipleActionInDivList.init(contentDiv);
371
+ const cb = contentDiv.querySelector('input.action_multiple_checkbox');
372
+ expect(cb.name).toBe('ids[]');
373
+ expect(cb.value).toBe('1');
374
+ });
375
+
376
+ test('should add check-all input', () => {
377
+ const contentDiv = setupDivList();
378
+ MultipleActionInDivList.init(contentDiv);
379
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).not.toBeNull();
380
+ });
381
+
382
+ test('should add arrow image to buttons div once', () => {
383
+ const contentDiv = setupDivList();
384
+ MultipleActionInDivList.init(contentDiv);
385
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
386
+ expect(buttonsDiv.querySelector('img')).not.toBeNull();
387
+ });
388
+
389
+ test('should not add arrow image twice when called again', () => {
390
+ const contentDiv = setupDivList();
391
+ MultipleActionInDivList.init(contentDiv);
392
+ MultipleActionInDivList.init(contentDiv);
393
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
394
+ expect(buttonsDiv.querySelectorAll('img').length).toBe(1);
395
+ });
396
+
397
+ test('check-all toggles all checkboxes to checked when none checked', () => {
398
+ const contentDiv = setupDivList({ nbItems: 2 });
399
+ MultipleActionInDivList.init(contentDiv);
400
+ const checkAll = contentDiv.querySelector('input.action_multiple_check_all');
401
+ checkAll.click();
402
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
403
+ checkboxes.forEach(cb => expect(cb.checked).toBe(true));
404
+ });
405
+
406
+ test('check-all unchecks all when all are checked', () => {
407
+ const contentDiv = setupDivList({ nbItems: 2 });
408
+ MultipleActionInDivList.init(contentDiv);
409
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
410
+ checkboxes.forEach(cb => { cb.checked = true; });
411
+ const checkAll = contentDiv.querySelector('input.action_multiple_check_all');
412
+ checkAll.click();
413
+ checkboxes.forEach(cb => expect(cb.checked).toBe(false));
414
+ });
415
+
416
+ test('checking a checkbox updates check-all state', () => {
417
+ const contentDiv = setupDivList({ nbItems: 2 });
418
+ MultipleActionInDivList.init(contentDiv);
419
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
420
+ checkboxes.forEach(cb => {
421
+ cb.checked = true;
422
+ cb.dispatchEvent(new Event('change'));
423
+ });
424
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(true);
425
+ });
426
+
427
+ test('should hide buttons div initially', () => {
428
+ const contentDiv = setupDivList();
429
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
430
+ buttonsDiv.classList.remove('hide');
431
+ MultipleActionInDivList.init(contentDiv);
432
+ expect(buttonsDiv.classList.contains('hide')).toBe(true);
377
433
  });
378
434
  });
379
435
 
380
436
  describe('updateCheckbox', () => {
381
- test('should hide select-all checkbox when no checkboxes exist', () => {
382
- const mockCheckboxSelectAll = {
383
- addClass: jest.fn(),
384
- removeClass: jest.fn()
385
- };
386
- const mockButtonsDiv = {
387
- hasClass: jest.fn(() => false)
388
- };
389
- mockContentDiv = {
390
- find: jest.fn((selector) => {
391
- if (selector === 'input.action_multiple_checkbox') {
392
- return { length: 0 };
393
- }
394
- if (selector === 'input.action_multiple_check_all') {
395
- return mockCheckboxSelectAll;
396
- }
397
- return { length: 0 };
398
- }),
399
- next: jest.fn(() => mockButtonsDiv)
400
- };
401
-
402
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
403
-
404
- expect(mockCheckboxSelectAll.addClass).toHaveBeenCalledWith('hide');
405
- });
406
-
407
- test('should check select-all checkbox when all checkboxes are checked', () => {
408
- const mockCheckboxSelectAll = {
409
- addClass: jest.fn(),
410
- removeClass: jest.fn(),
411
- prop: jest.fn()
412
- };
413
- const mockButtonsDiv = {
414
- hasClass: jest.fn(() => false)
415
- };
416
- mockContentDiv = {
417
- find: jest.fn((selector) => {
418
- if (selector === 'input.action_multiple_checkbox') {
419
- return { length: 4 };
420
- }
421
- if (selector === 'input.action_multiple_checkbox:checked') {
422
- return { length: 4 };
423
- }
424
- if (selector === 'input.action_multiple_check_all') {
425
- return mockCheckboxSelectAll;
426
- }
427
- return { length: 0 };
428
- }),
429
- next: jest.fn(() => mockButtonsDiv)
430
- };
431
-
432
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
433
-
434
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
435
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', true);
436
- });
437
-
438
- test('should uncheck select-all checkbox when not all checkboxes are checked', () => {
439
- const mockCheckboxSelectAll = {
440
- addClass: jest.fn(),
441
- removeClass: jest.fn(),
442
- prop: jest.fn()
443
- };
444
- const mockButtonsDiv = {
445
- hasClass: jest.fn(() => false)
446
- };
447
- mockContentDiv = {
448
- find: jest.fn((selector) => {
449
- if (selector === 'input.action_multiple_checkbox') {
450
- return { length: 4 };
451
- }
452
- if (selector === 'input.action_multiple_checkbox:checked') {
453
- return { length: 1 };
454
- }
455
- if (selector === 'input.action_multiple_check_all') {
456
- return mockCheckboxSelectAll;
457
- }
458
- return { length: 0 };
459
- }),
460
- next: jest.fn(() => mockButtonsDiv)
461
- };
462
-
463
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
464
-
465
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', false);
437
+ test('should hide check-all when no checkboxes exist', () => {
438
+ document.body.innerHTML = `
439
+ <div id="content">
440
+ <p class="mb-2"><input type="checkbox" class="action_multiple_check_all" /> Tout sélectionner</p>
441
+ </div>
442
+ <div class="action_multiple_buttons hide"></div>`;
443
+ const contentDiv = document.getElementById('content');
444
+ MultipleActionInDivList.updateCheckbox(contentDiv);
445
+ expect(contentDiv.querySelector('input.action_multiple_check_all').classList.contains('hide')).toBe(true);
446
+ });
447
+
448
+ test('should check check-all when all checkboxes are checked', () => {
449
+ const contentDiv = setupDivList({ nbItems: 2 });
450
+ MultipleActionInDivList.init(contentDiv);
451
+ contentDiv.querySelectorAll('input.action_multiple_checkbox').forEach(cb => { cb.checked = true; });
452
+ MultipleActionInDivList.updateCheckbox(contentDiv);
453
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(true);
454
+ });
455
+
456
+ test('should uncheck check-all when not all checkboxes are checked', () => {
457
+ const contentDiv = setupDivList({ nbItems: 2 });
458
+ MultipleActionInDivList.init(contentDiv);
459
+ contentDiv.querySelectorAll('input.action_multiple_checkbox')[0].checked = true;
460
+ MultipleActionInDivList.updateCheckbox(contentDiv);
461
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(false);
462
+ });
463
+ });
464
+
465
+ describe('showButtonsAction', () => {
466
+ test('should return early when buttons div is null', () => {
467
+ const contentDiv = setupDivList({ withButtonsDiv: false });
468
+ expect(() => MultipleActionInDivList.showButtonsAction(contentDiv)).not.toThrow();
469
+ });
470
+
471
+ test('should show buttons div when a checkbox is checked', () => {
472
+ const contentDiv = setupDivList({ nbItems: 1 });
473
+ MultipleActionInDivList.init(contentDiv);
474
+ const cb = contentDiv.querySelector('input.action_multiple_checkbox');
475
+ cb.checked = true;
476
+ MultipleActionInDivList.showButtonsAction(contentDiv);
477
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
478
+ expect(buttonsDiv.classList.contains('hide')).toBe(false);
479
+ });
480
+
481
+ test('should hide buttons div when no checkbox is checked', () => {
482
+ const contentDiv = setupDivList({ nbItems: 1 });
483
+ MultipleActionInDivList.init(contentDiv);
484
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
485
+ buttonsDiv.classList.remove('hide');
486
+ MultipleActionInDivList.showButtonsAction(contentDiv);
487
+ expect(buttonsDiv.classList.contains('hide')).toBe(true);
466
488
  });
467
489
  });
468
490
  });