@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.
@@ -3,475 +3,488 @@
3
3
  */
4
4
  const { MultipleActionInTable, MultipleActionInDivList } = require('../multiple_action_in_table');
5
5
 
6
- 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
- };
42
- });
43
-
44
- // Mock ROOT_PATH and DOSSIER_IMAGES
45
- global.ROOT_PATH = '/';
46
- global.DOSSIER_IMAGES = 'images/';
47
- });
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 ───────────────────────────────────────────────────
48
71
 
72
+ describe('MultipleActionInTable', () => {
49
73
  afterEach(() => {
50
- jest.clearAllMocks();
51
- delete global.$;
52
- delete global.ROOT_PATH;
53
- delete global.DOSSIER_IMAGES;
74
+ document.body.innerHTML = '';
54
75
  });
55
76
 
56
77
  describe('getDivBtn', () => {
57
- test('should return button div when found as next sibling', () => {
58
- const mockButtonDiv = {
59
- hasClass: jest.fn((className) => className === 'action_multiple_buttons')
60
- };
61
- mockTable = {
62
- parent: jest.fn(() => ({
63
- next: jest.fn(() => mockButtonDiv)
64
- }))
65
- };
66
-
67
- const result = MultipleActionInTable.getDivBtn(mockTable);
68
-
69
- expect(result).toBe(mockButtonDiv);
70
- expect(mockButtonDiv.hasClass).toHaveBeenCalledWith('action_multiple_buttons');
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);
71
83
  });
72
84
 
73
85
  test('should return button div when found in nested structure', () => {
74
- const mockButtonDiv = {
75
- hasClass: jest.fn((className) => className === 'action_multiple_buttons')
76
- };
77
- const notButtonDiv = {
78
- hasClass: jest.fn(() => false)
79
- };
80
- mockTable = {
81
- parent: jest.fn(() => ({
82
- next: jest.fn(() => notButtonDiv),
83
- parent: jest.fn(() => ({
84
- parent: jest.fn(() => ({
85
- parent: jest.fn(() => ({
86
- next: jest.fn(() => mockButtonDiv)
87
- }))
88
- }))
89
- }))
90
- }))
91
- };
92
-
93
- const result = MultipleActionInTable.getDivBtn(mockTable);
94
-
95
- expect(result).toBe(mockButtonDiv);
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);
96
90
  });
97
91
 
98
92
  test('should return null when button div not found', () => {
99
- const notButtonDiv = {
100
- hasClass: jest.fn(() => false)
101
- };
102
- mockTable = {
103
- parent: jest.fn(() => ({
104
- next: jest.fn(() => notButtonDiv),
105
- parent: jest.fn(() => ({
106
- parent: jest.fn(() => ({
107
- parent: jest.fn(() => ({
108
- next: jest.fn(() => notButtonDiv)
109
- }))
110
- }))
111
- }))
112
- }))
113
- };
114
-
115
- const result = MultipleActionInTable.getDivBtn(mockTable);
116
-
93
+ const table = setupTable({ withButtonsDiv: false });
94
+ const result = MultipleActionInTable.getDivBtn(table);
117
95
  expect(result).toBeNull();
118
96
  });
119
97
  });
120
98
 
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
+ });
159
+ });
160
+
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
+ });
167
+
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
+ });
173
+
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();
178
+ });
179
+
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();
185
+ });
186
+
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
+ });
194
+
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);
231
+ });
232
+ });
233
+
121
234
  describe('updateCheckbox', () => {
122
- test('should hide select-all checkbox when no checkboxes exist', () => {
123
- const mockCheckboxSelectAll = {
124
- addClass: jest.fn(),
125
- removeClass: jest.fn()
126
- };
127
- const mockButtonDiv = {
128
- hasClass: jest.fn(() => false)
129
- };
130
- mockTable = {
131
- find: jest.fn((selector) => {
132
- if (selector === 'input.action_multiple_checkbox') {
133
- return { length: 0 };
134
- }
135
- if (selector === 'input.action_multiple_checkbox:checked') {
136
- return { length: 0 };
137
- }
138
- if (selector === 'thead tr th input.action_multiple_check_all') {
139
- return mockCheckboxSelectAll;
140
- }
141
- return { length: 0 };
142
- }),
143
- parent: jest.fn(() => ({
144
- next: jest.fn(() => mockButtonDiv),
145
- parent: jest.fn(() => ({
146
- parent: jest.fn(() => ({
147
- parent: jest.fn(() => ({
148
- next: jest.fn(() => mockButtonDiv)
149
- }))
150
- }))
151
- }))
152
- }))
153
- };
154
-
155
- MultipleActionInTable.updateCheckbox(mockTable);
156
-
157
- expect(mockCheckboxSelectAll.addClass).toHaveBeenCalledWith('hide');
158
- });
159
-
160
- test('should check select-all checkbox when all checkboxes are checked', () => {
161
- const mockCheckboxSelectAll = {
162
- addClass: jest.fn(),
163
- removeClass: jest.fn(),
164
- prop: jest.fn()
165
- };
166
- const mockButtonDiv = {
167
- hasClass: jest.fn(() => false)
168
- };
169
- mockTable = {
170
- find: jest.fn((selector) => {
171
- if (selector === 'input.action_multiple_checkbox') {
172
- return { length: 3 };
173
- }
174
- if (selector === 'input.action_multiple_checkbox:checked') {
175
- return { length: 3 };
176
- }
177
- if (selector === 'thead tr th input.action_multiple_check_all') {
178
- return mockCheckboxSelectAll;
179
- }
180
- return { length: 0 };
181
- }),
182
- parent: jest.fn(() => ({
183
- next: jest.fn(() => mockButtonDiv),
184
- parent: jest.fn(() => ({
185
- parent: jest.fn(() => ({
186
- parent: jest.fn(() => ({
187
- next: jest.fn(() => mockButtonDiv)
188
- }))
189
- }))
190
- }))
191
- }))
192
- };
193
-
194
- MultipleActionInTable.updateCheckbox(mockTable);
195
-
196
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
197
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', true);
198
- });
199
-
200
- test('should uncheck select-all checkbox when not all checkboxes are checked', () => {
201
- const mockCheckboxSelectAll = {
202
- addClass: jest.fn(),
203
- removeClass: jest.fn(),
204
- prop: jest.fn()
205
- };
206
- const mockButtonDiv = {
207
- hasClass: jest.fn(() => false)
208
- };
209
- mockTable = {
210
- find: jest.fn((selector) => {
211
- if (selector === 'input.action_multiple_checkbox') {
212
- return { length: 5 };
213
- }
214
- if (selector === 'input.action_multiple_checkbox:checked') {
215
- return { length: 2 };
216
- }
217
- if (selector === 'thead tr th input.action_multiple_check_all') {
218
- return mockCheckboxSelectAll;
219
- }
220
- return { length: 0 };
221
- }),
222
- parent: jest.fn(() => ({
223
- next: jest.fn(() => mockButtonDiv),
224
- parent: jest.fn(() => ({
225
- parent: jest.fn(() => ({
226
- parent: jest.fn(() => ({
227
- next: jest.fn(() => mockButtonDiv)
228
- }))
229
- }))
230
- }))
231
- }))
232
- };
233
-
234
- MultipleActionInTable.updateCheckbox(mockTable);
235
-
236
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
237
- 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);
238
268
  });
239
269
  });
240
270
 
241
271
  describe('showButtonsAction', () => {
242
272
  test('should return early when button div is null', () => {
243
- mockTable = {
244
- parent: jest.fn(() => ({
245
- next: jest.fn(() => ({ hasClass: jest.fn(() => false) })),
246
- parent: jest.fn(() => ({
247
- parent: jest.fn(() => ({
248
- parent: jest.fn(() => ({
249
- next: jest.fn(() => ({ hasClass: jest.fn(() => false) }))
250
- }))
251
- }))
252
- }))
253
- })),
254
- find: jest.fn(() => ({ length: 0 }))
255
- };
256
-
257
- expect(() => {
258
- MultipleActionInTable.showButtonsAction(mockTable);
259
- }).not.toThrow();
260
- });
261
-
262
- test('should show button div when items are checked', () => {
263
- const mockButtonDiv = {
264
- hasClass: jest.fn(() => true),
265
- is: jest.fn((selector) => selector === ':hidden'),
266
- removeClass: jest.fn(),
267
- addClass: jest.fn(),
268
- find: jest.fn(() => ({
269
- length: 1,
270
- remove: jest.fn(),
271
- after: jest.fn()
272
- }))
273
- };
274
- mockTable = {
275
- parent: jest.fn(() => ({
276
- next: jest.fn(() => mockButtonDiv)
277
- })),
278
- find: jest.fn((selector) => {
279
- if (selector === 'input.action_multiple_checkbox:checked') {
280
- return { length: 2 };
281
- }
282
- return { length: 0 };
283
- }),
284
- is: jest.fn(() => false)
285
- };
286
-
287
- MultipleActionInTable.showButtonsAction(mockTable);
288
-
289
- expect(mockButtonDiv.removeClass).toHaveBeenCalledWith('hide');
290
- });
291
-
292
- test('should hide button div when no items are checked', () => {
293
- const mockButtonDiv = {
294
- hasClass: jest.fn(() => true),
295
- is: jest.fn((selector) => selector === ':visible'),
296
- removeClass: jest.fn(),
297
- addClass: jest.fn(),
298
- find: jest.fn((selector) => {
299
- if (selector === 'span.no_button') {
300
- return { remove: jest.fn() };
301
- }
302
- if (selector === 'button:visible, a:visible') {
303
- return { length: 0 };
304
- }
305
- if (selector === 'img') {
306
- return { after: jest.fn() };
307
- }
308
- return {
309
- length: 0,
310
- remove: jest.fn()
311
- };
312
- })
313
- };
314
- mockTable = {
315
- parent: jest.fn(() => ({
316
- next: jest.fn(() => mockButtonDiv)
317
- })),
318
- find: jest.fn(() => ({ length: 0 })),
319
- is: jest.fn(() => false)
320
- };
321
-
322
- MultipleActionInTable.showButtonsAction(mockTable);
323
-
324
- expect(mockButtonDiv.addClass).toHaveBeenCalledWith('hide');
273
+ const table = setupTable({ withButtonsDiv: false });
274
+ expect(() => MultipleActionInTable.showButtonsAction(table)).not.toThrow();
275
+ });
276
+
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);
294
+ });
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();
325
320
  });
326
321
  });
327
322
  });
328
323
 
329
- describe('MultipleActionInDivList', () => {
330
- let mockContentDiv;
331
-
332
- beforeEach(() => {
333
- // Mock jQuery
334
- global.$ = jest.fn((selector) => {
335
- if (typeof selector === 'string' && (selector.includes('<') || selector.includes('img'))) {
336
- return {
337
- addClass: jest.fn().mockReturnThis(),
338
- removeClass: jest.fn().mockReturnThis(),
339
- prepend: jest.fn().mockReturnThis(),
340
- append: jest.fn().mockReturnThis()
341
- };
342
- }
343
- return {
344
- find: jest.fn(() => ({ length: 0 })),
345
- hasClass: jest.fn(() => false)
346
- };
347
- });
348
-
349
- global.ROOT_PATH = '/';
350
- global.DOSSIER_IMAGES = 'images/';
351
- });
324
+ // ─── MultipleActionInDivList ─────────────────────────────────────────────────
352
325
 
326
+ describe('MultipleActionInDivList', () => {
353
327
  afterEach(() => {
354
- jest.clearAllMocks();
355
- delete global.$;
356
- delete global.ROOT_PATH;
357
- delete global.DOSSIER_IMAGES;
328
+ document.body.innerHTML = '';
358
329
  });
359
330
 
360
331
  describe('getButtonsDiv', () => {
361
- test('should return buttons div when found', () => {
362
- const mockButtonsDiv = {
363
- hasClass: jest.fn(() => true)
364
- };
365
- mockContentDiv = {
366
- next: jest.fn(() => mockButtonsDiv)
367
- };
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
+ });
368
338
 
369
- 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
+ });
370
345
 
371
- expect(result).toBe(mockButtonsDiv);
372
- 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();
373
351
  });
374
352
 
375
- test('should return null when buttons div not found', () => {
376
- const notButtonsDiv = {
377
- hasClass: jest.fn(() => false)
378
- };
379
- mockContentDiv = {
380
- next: jest.fn(() => notButtonsDiv)
381
- };
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
+ });
382
361
 
383
- 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
+ });
384
367
 
385
- 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);
386
433
  });
387
434
  });
388
435
 
389
436
  describe('updateCheckbox', () => {
390
- test('should hide select-all checkbox when no checkboxes exist', () => {
391
- const mockCheckboxSelectAll = {
392
- addClass: jest.fn(),
393
- removeClass: jest.fn()
394
- };
395
- const mockButtonsDiv = {
396
- hasClass: jest.fn(() => false)
397
- };
398
- mockContentDiv = {
399
- find: jest.fn((selector) => {
400
- if (selector === 'input.action_multiple_checkbox') {
401
- return { length: 0 };
402
- }
403
- if (selector === 'input.action_multiple_check_all') {
404
- return mockCheckboxSelectAll;
405
- }
406
- return { length: 0 };
407
- }),
408
- next: jest.fn(() => mockButtonsDiv)
409
- };
410
-
411
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
412
-
413
- expect(mockCheckboxSelectAll.addClass).toHaveBeenCalledWith('hide');
414
- });
415
-
416
- test('should check select-all checkbox when all checkboxes are checked', () => {
417
- const mockCheckboxSelectAll = {
418
- addClass: jest.fn(),
419
- removeClass: jest.fn(),
420
- prop: jest.fn()
421
- };
422
- const mockButtonsDiv = {
423
- hasClass: jest.fn(() => false)
424
- };
425
- mockContentDiv = {
426
- find: jest.fn((selector) => {
427
- if (selector === 'input.action_multiple_checkbox') {
428
- return { length: 4 };
429
- }
430
- if (selector === 'input.action_multiple_checkbox:checked') {
431
- return { length: 4 };
432
- }
433
- if (selector === 'input.action_multiple_check_all') {
434
- return mockCheckboxSelectAll;
435
- }
436
- return { length: 0 };
437
- }),
438
- next: jest.fn(() => mockButtonsDiv)
439
- };
440
-
441
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
442
-
443
- expect(mockCheckboxSelectAll.removeClass).toHaveBeenCalledWith('hide');
444
- expect(mockCheckboxSelectAll.prop).toHaveBeenCalledWith('checked', true);
445
- });
446
-
447
- test('should uncheck select-all checkbox when not all checkboxes are checked', () => {
448
- const mockCheckboxSelectAll = {
449
- addClass: jest.fn(),
450
- removeClass: jest.fn(),
451
- prop: jest.fn()
452
- };
453
- const mockButtonsDiv = {
454
- hasClass: jest.fn(() => false)
455
- };
456
- mockContentDiv = {
457
- find: jest.fn((selector) => {
458
- if (selector === 'input.action_multiple_checkbox') {
459
- return { length: 4 };
460
- }
461
- if (selector === 'input.action_multiple_checkbox:checked') {
462
- return { length: 1 };
463
- }
464
- if (selector === 'input.action_multiple_check_all') {
465
- return mockCheckboxSelectAll;
466
- }
467
- return { length: 0 };
468
- }),
469
- next: jest.fn(() => mockButtonsDiv)
470
- };
471
-
472
- MultipleActionInDivList.updateCheckbox(mockContentDiv);
473
-
474
- 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);
475
488
  });
476
489
  });
477
490
  });