@osimatic/helpers-js 1.5.3 → 1.5.5

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,507 @@
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 not throw when table has no <thead>', () => {
143
+ document.body.innerHTML = `
144
+ <div>
145
+ <div>
146
+ <table class="table-action_multiple">
147
+ <tbody>
148
+ <tr data-action_multiple_input_name="ids[]" data-action_multiple_item_id="1">
149
+ <td>Item 1</td>
150
+ </tr>
151
+ </tbody>
152
+ </table>
153
+ </div>
154
+ <div class="action_multiple_buttons hide"></div>
155
+ </div>`;
156
+ const table = document.querySelector('table');
157
+ expect(() => MultipleActionInTable.initCols(table)).not.toThrow();
158
+ expect(table.querySelector('th[data-key="select"]')).toBeNull();
159
+ });
160
+
161
+ test('should skip rows with no_items class', () => {
162
+ document.body.innerHTML = `
163
+ <div>
164
+ <div>
165
+ <table class="table-action_multiple">
166
+ <thead><tr><th>Name</th></tr></thead>
167
+ <tbody>
168
+ <tr class="no_items"><td colspan="1">No items</td></tr>
169
+ </tbody>
170
+ </table>
171
+ </div>
172
+ <div class="action_multiple_buttons hide"></div>
173
+ </div>`;
174
+ const table = document.querySelector('table');
175
+ MultipleActionInTable.initCols(table);
176
+ expect(table.querySelector('tbody tr.no_items td.select')).toBeNull();
177
+ });
49
178
  });
50
179
 
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
- };
180
+ describe('init', () => {
181
+ test('should do nothing if table lacks table-action_multiple class', () => {
182
+ const table = setupTable({ tableClass: 'other' });
183
+ MultipleActionInTable.init(table);
184
+ expect(table.querySelector('input.action_multiple_check_all')).toBeNull();
185
+ });
186
+
187
+ test('should do nothing if no buttons div found', () => {
188
+ const table = setupTable({ withButtonsDiv: false });
189
+ MultipleActionInTable.init(table);
190
+ expect(table.querySelector('input.action_multiple_check_all')).toBeNull();
191
+ });
61
192
 
62
- const result = MultipleActionInTable.getDivBtn(mockTable);
193
+ test('should add check-all input to first th', () => {
194
+ const table = setupTable();
195
+ MultipleActionInTable.init(table);
196
+ expect(table.querySelector('thead tr th input.action_multiple_check_all')).not.toBeNull();
197
+ });
63
198
 
64
- expect(result).toBe(mockButtonDiv);
65
- expect(mockButtonDiv.hasClass).toHaveBeenCalledWith('action_multiple_buttons');
199
+ test('should add arrow image to buttons div once', () => {
200
+ const table = setupTable();
201
+ MultipleActionInTable.init(table);
202
+ const divBtn = MultipleActionInTable.getDivBtn(table);
203
+ expect(divBtn.querySelector('img')).not.toBeNull();
66
204
  });
67
205
 
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);
206
+ test('should not add arrow image twice when called again', () => {
207
+ const table = setupTable();
208
+ MultipleActionInTable.init(table);
209
+ MultipleActionInTable.init(table);
210
+ const divBtn = MultipleActionInTable.getDivBtn(table);
211
+ expect(divBtn.querySelectorAll('img').length).toBe(1);
91
212
  });
92
213
 
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);
214
+ test('check-all toggles all checkboxes to checked when none checked', () => {
215
+ const table = setupTable({ nbRows: 2 });
216
+ MultipleActionInTable.init(table);
111
217
 
112
- expect(result).toBeNull();
218
+ const checkAll = table.querySelector('input.action_multiple_check_all');
219
+ checkAll.click();
220
+
221
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
222
+ checkboxes.forEach(cb => expect(cb.checked).toBe(true));
223
+ });
224
+
225
+ test('check-all unchecks all when all are checked', () => {
226
+ const table = setupTable({ nbRows: 2 });
227
+ MultipleActionInTable.init(table);
228
+
229
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
230
+ checkboxes.forEach(cb => { cb.checked = true; });
231
+
232
+ const checkAll = table.querySelector('input.action_multiple_check_all');
233
+ checkAll.click();
234
+
235
+ checkboxes.forEach(cb => expect(cb.checked).toBe(false));
236
+ });
237
+
238
+ test('checking a row checkbox updates check-all state', () => {
239
+ const table = setupTable({ nbRows: 2 });
240
+ MultipleActionInTable.init(table);
241
+
242
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
243
+ checkboxes.forEach(cb => {
244
+ cb.checked = true;
245
+ cb.dispatchEvent(new Event('change'));
246
+ });
247
+
248
+ const checkAll = table.querySelector('input.action_multiple_check_all');
249
+ expect(checkAll.checked).toBe(true);
113
250
  });
114
251
  });
115
252
 
116
253
  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);
254
+ test('should hide check-all when no checkboxes exist', () => {
255
+ document.body.innerHTML = `
256
+ <div>
257
+ <div>
258
+ <table class="table-action_multiple">
259
+ <thead><tr><th><input type="checkbox" class="action_multiple_check_all" /></th></tr></thead>
260
+ <tbody></tbody>
261
+ </table>
262
+ </div>
263
+ <div class="action_multiple_buttons hide"></div>
264
+ </div>`;
265
+ const table = document.querySelector('table');
266
+ MultipleActionInTable.updateCheckbox(table);
267
+ const checkAll = table.querySelector('input.action_multiple_check_all');
268
+ expect(checkAll.classList.contains('hide')).toBe(true);
269
+ });
270
+
271
+ test('should check check-all when all checkboxes are checked', () => {
272
+ const table = setupTable({ nbRows: 2 });
273
+ MultipleActionInTable.init(table);
274
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
275
+ checkboxes.forEach(cb => { cb.checked = true; });
276
+ MultipleActionInTable.updateCheckbox(table);
277
+ expect(table.querySelector('input.action_multiple_check_all').checked).toBe(true);
278
+ });
279
+
280
+ test('should uncheck check-all when not all checkboxes are checked', () => {
281
+ const table = setupTable({ nbRows: 2 });
282
+ MultipleActionInTable.init(table);
283
+ const checkboxes = table.querySelectorAll('input.action_multiple_checkbox');
284
+ checkboxes[0].checked = true;
285
+ MultipleActionInTable.updateCheckbox(table);
286
+ expect(table.querySelector('input.action_multiple_check_all').checked).toBe(false);
233
287
  });
234
288
  });
235
289
 
236
290
  describe('showButtonsAction', () => {
237
291
  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');
292
+ const table = setupTable({ withButtonsDiv: false });
293
+ expect(() => MultipleActionInTable.showButtonsAction(table)).not.toThrow();
320
294
  });
321
- });
322
- });
323
295
 
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
- };
296
+ test('should show buttons div when a checkbox is checked', () => {
297
+ const table = setupTable({ nbRows: 1 });
298
+ MultipleActionInTable.init(table);
299
+ const cb = table.querySelector('input.action_multiple_checkbox');
300
+ cb.checked = true;
301
+ MultipleActionInTable.showButtonsAction(table);
302
+ const divBtn = MultipleActionInTable.getDivBtn(table);
303
+ expect(divBtn.classList.contains('hide')).toBe(false);
304
+ });
305
+
306
+ test('should hide buttons div when no checkbox is checked', () => {
307
+ const table = setupTable({ nbRows: 1 });
308
+ MultipleActionInTable.init(table);
309
+ const divBtn = MultipleActionInTable.getDivBtn(table);
310
+ divBtn.classList.remove('hide'); // force visible
311
+ MultipleActionInTable.showButtonsAction(table);
312
+ expect(divBtn.classList.contains('hide')).toBe(true);
342
313
  });
343
314
 
315
+ test('should show "no action" message when div is visible but has no visible buttons', () => {
316
+ document.body.innerHTML = `
317
+ <div>
318
+ <div>
319
+ <table class="table-action_multiple">
320
+ <thead><tr><th>Name</th></tr></thead>
321
+ <tbody>
322
+ <tr data-action_multiple_input_name="ids[]" data-action_multiple_item_id="1">
323
+ <td>Item 1</td>
324
+ </tr>
325
+ </tbody>
326
+ </table>
327
+ </div>
328
+ <div class="action_multiple_buttons">
329
+ <img src="" alt="" />
330
+ </div>
331
+ </div>`;
332
+ const table = document.querySelector('table');
333
+ MultipleActionInTable.init(table);
334
+ const cb = table.querySelector('input.action_multiple_checkbox');
335
+ cb.checked = true;
336
+ MultipleActionInTable.showButtonsAction(table);
337
+ const divBtn = MultipleActionInTable.getDivBtn(table);
338
+ expect(divBtn.querySelector('span.no_button')).not.toBeNull();
339
+ });
344
340
  });
341
+ });
345
342
 
343
+ // ─── MultipleActionInDivList ─────────────────────────────────────────────────
344
+
345
+ describe('MultipleActionInDivList', () => {
346
346
  afterEach(() => {
347
- jest.clearAllMocks();
348
- delete global.$;
347
+ document.body.innerHTML = '';
349
348
  });
350
349
 
351
350
  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
- };
351
+ test('should return buttons div when found as next sibling', () => {
352
+ const contentDiv = setupDivList();
353
+ const result = MultipleActionInDivList.getButtonsDiv(contentDiv);
354
+ expect(result).not.toBeNull();
355
+ expect(result.classList.contains('action_multiple_buttons')).toBe(true);
356
+ });
359
357
 
360
- const result = MultipleActionInDivList.getButtonsDiv(mockContentDiv);
358
+ test('should return null when buttons div not found', () => {
359
+ const contentDiv = setupDivList({ withButtonsDiv: false });
360
+ const result = MultipleActionInDivList.getButtonsDiv(contentDiv);
361
+ expect(result).toBeNull();
362
+ });
363
+ });
361
364
 
362
- expect(result).toBe(mockButtonsDiv);
363
- expect(mockButtonsDiv.hasClass).toHaveBeenCalledWith('action_multiple_buttons');
365
+ describe('init', () => {
366
+ test('should return early when no buttons div found', () => {
367
+ const contentDiv = setupDivList({ withButtonsDiv: false });
368
+ MultipleActionInDivList.init(contentDiv);
369
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).toBeNull();
364
370
  });
365
371
 
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
- };
372
+ test('should return early when no .multiple_action divs found', () => {
373
+ document.body.innerHTML = `
374
+ <div id="content"></div>
375
+ <div class="action_multiple_buttons"></div>`;
376
+ const contentDiv = document.getElementById('content');
377
+ MultipleActionInDivList.init(contentDiv);
378
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).toBeNull();
379
+ });
373
380
 
374
- const result = MultipleActionInDivList.getButtonsDiv(mockContentDiv);
381
+ test('should add checkbox to each .multiple_action div', () => {
382
+ const contentDiv = setupDivList({ nbItems: 2 });
383
+ MultipleActionInDivList.init(contentDiv);
384
+ expect(contentDiv.querySelectorAll('input.action_multiple_checkbox').length).toBe(2);
385
+ });
375
386
 
376
- expect(result).toBeNull();
387
+ test('should set correct name and value on checkboxes', () => {
388
+ const contentDiv = setupDivList({ nbItems: 1 });
389
+ MultipleActionInDivList.init(contentDiv);
390
+ const cb = contentDiv.querySelector('input.action_multiple_checkbox');
391
+ expect(cb.name).toBe('ids[]');
392
+ expect(cb.value).toBe('1');
393
+ });
394
+
395
+ test('should add check-all input', () => {
396
+ const contentDiv = setupDivList();
397
+ MultipleActionInDivList.init(contentDiv);
398
+ expect(contentDiv.querySelector('input.action_multiple_check_all')).not.toBeNull();
399
+ });
400
+
401
+ test('should add arrow image to buttons div once', () => {
402
+ const contentDiv = setupDivList();
403
+ MultipleActionInDivList.init(contentDiv);
404
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
405
+ expect(buttonsDiv.querySelector('img')).not.toBeNull();
406
+ });
407
+
408
+ test('should not add arrow image twice when called again', () => {
409
+ const contentDiv = setupDivList();
410
+ MultipleActionInDivList.init(contentDiv);
411
+ MultipleActionInDivList.init(contentDiv);
412
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
413
+ expect(buttonsDiv.querySelectorAll('img').length).toBe(1);
414
+ });
415
+
416
+ test('check-all toggles all checkboxes to checked when none checked', () => {
417
+ const contentDiv = setupDivList({ nbItems: 2 });
418
+ MultipleActionInDivList.init(contentDiv);
419
+ const checkAll = contentDiv.querySelector('input.action_multiple_check_all');
420
+ checkAll.click();
421
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
422
+ checkboxes.forEach(cb => expect(cb.checked).toBe(true));
423
+ });
424
+
425
+ test('check-all unchecks all when all are checked', () => {
426
+ const contentDiv = setupDivList({ nbItems: 2 });
427
+ MultipleActionInDivList.init(contentDiv);
428
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
429
+ checkboxes.forEach(cb => { cb.checked = true; });
430
+ const checkAll = contentDiv.querySelector('input.action_multiple_check_all');
431
+ checkAll.click();
432
+ checkboxes.forEach(cb => expect(cb.checked).toBe(false));
433
+ });
434
+
435
+ test('checking a checkbox updates check-all state', () => {
436
+ const contentDiv = setupDivList({ nbItems: 2 });
437
+ MultipleActionInDivList.init(contentDiv);
438
+ const checkboxes = contentDiv.querySelectorAll('input.action_multiple_checkbox');
439
+ checkboxes.forEach(cb => {
440
+ cb.checked = true;
441
+ cb.dispatchEvent(new Event('change'));
442
+ });
443
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(true);
444
+ });
445
+
446
+ test('should hide buttons div initially', () => {
447
+ const contentDiv = setupDivList();
448
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
449
+ buttonsDiv.classList.remove('hide');
450
+ MultipleActionInDivList.init(contentDiv);
451
+ expect(buttonsDiv.classList.contains('hide')).toBe(true);
377
452
  });
378
453
  });
379
454
 
380
455
  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);
456
+ test('should hide check-all when no checkboxes exist', () => {
457
+ document.body.innerHTML = `
458
+ <div id="content">
459
+ <p class="mb-2"><input type="checkbox" class="action_multiple_check_all" /> Tout sélectionner</p>
460
+ </div>
461
+ <div class="action_multiple_buttons hide"></div>`;
462
+ const contentDiv = document.getElementById('content');
463
+ MultipleActionInDivList.updateCheckbox(contentDiv);
464
+ expect(contentDiv.querySelector('input.action_multiple_check_all').classList.contains('hide')).toBe(true);
465
+ });
466
+
467
+ test('should check check-all when all checkboxes are checked', () => {
468
+ const contentDiv = setupDivList({ nbItems: 2 });
469
+ MultipleActionInDivList.init(contentDiv);
470
+ contentDiv.querySelectorAll('input.action_multiple_checkbox').forEach(cb => { cb.checked = true; });
471
+ MultipleActionInDivList.updateCheckbox(contentDiv);
472
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(true);
473
+ });
474
+
475
+ test('should uncheck check-all when not all checkboxes are checked', () => {
476
+ const contentDiv = setupDivList({ nbItems: 2 });
477
+ MultipleActionInDivList.init(contentDiv);
478
+ contentDiv.querySelectorAll('input.action_multiple_checkbox')[0].checked = true;
479
+ MultipleActionInDivList.updateCheckbox(contentDiv);
480
+ expect(contentDiv.querySelector('input.action_multiple_check_all').checked).toBe(false);
481
+ });
482
+ });
483
+
484
+ describe('showButtonsAction', () => {
485
+ test('should return early when buttons div is null', () => {
486
+ const contentDiv = setupDivList({ withButtonsDiv: false });
487
+ expect(() => MultipleActionInDivList.showButtonsAction(contentDiv)).not.toThrow();
488
+ });
489
+
490
+ test('should show buttons div when a checkbox is checked', () => {
491
+ const contentDiv = setupDivList({ nbItems: 1 });
492
+ MultipleActionInDivList.init(contentDiv);
493
+ const cb = contentDiv.querySelector('input.action_multiple_checkbox');
494
+ cb.checked = true;
495
+ MultipleActionInDivList.showButtonsAction(contentDiv);
496
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
497
+ expect(buttonsDiv.classList.contains('hide')).toBe(false);
498
+ });
499
+
500
+ test('should hide buttons div when no checkbox is checked', () => {
501
+ const contentDiv = setupDivList({ nbItems: 1 });
502
+ MultipleActionInDivList.init(contentDiv);
503
+ const buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
504
+ buttonsDiv.classList.remove('hide');
505
+ MultipleActionInDivList.showButtonsAction(contentDiv);
506
+ expect(buttonsDiv.classList.contains('hide')).toBe(true);
466
507
  });
467
508
  });
468
509
  });