@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,81 +3,71 @@
3
3
  */
4
4
  const { ImportFromCsv } = require('../import_from_csv');
5
5
 
6
- describe('ImportFromCsv', () => {
7
- let mockFormMatching;
8
- let mockDivResult;
9
-
10
- beforeEach(() => {
11
- // Mock jQuery
12
- global.$ = jest.fn((selector) => {
13
- if (typeof selector === 'string') {
14
- if (selector.includes('<')) {
15
- // Creating HTML element
16
- return {
17
- html: jest.fn().mockReturnThis(),
18
- append: jest.fn().mockReturnThis(),
19
- find: jest.fn().mockReturnThis(),
20
- addClass: jest.fn().mockReturnThis(),
21
- removeClass: jest.fn().mockReturnThis(),
22
- prop: jest.fn().mockReturnThis(),
23
- click: jest.fn().mockReturnThis()
24
- };
25
- }
26
- return {
27
- find: jest.fn().mockReturnThis(),
28
- each: jest.fn(),
29
- addClass: jest.fn().mockReturnThis(),
30
- removeClass: jest.fn().mockReturnThis(),
31
- empty: jest.fn().mockReturnThis(),
32
- append: jest.fn().mockReturnThis()
33
- };
34
- }
35
- // Wrap element
36
- return {
37
- val: jest.fn(),
38
- prop: jest.fn(),
39
- find: jest.fn().mockReturnThis(),
40
- text: jest.fn(),
41
- length: 0,
42
- hasClass: jest.fn(() => false),
43
- addClass: jest.fn().mockReturnThis(),
44
- parent: jest.fn().mockReturnThis(),
45
- closest: jest.fn().mockReturnThis()
46
- };
47
- });
48
-
49
- global.$.each = jest.fn((collection, callback) => {
50
- if (Array.isArray(collection)) {
51
- collection.forEach((item, index) => callback(index, item));
52
- } else if (typeof collection === 'object') {
53
- Object.keys(collection).forEach((key) => callback(key, collection[key]));
54
- }
55
- });
56
-
57
- global.$.isEmptyObject = jest.fn((obj) => {
58
- return Object.keys(obj).length === 0;
59
- });
60
-
61
- mockFormMatching = {
62
- find: jest.fn(() => ({
63
- each: jest.fn(),
64
- prop: jest.fn().mockReturnThis()
65
- }))
66
- };
67
-
68
- mockDivResult = {
69
- find: jest.fn(() => ({
70
- each: jest.fn(),
71
- addClass: jest.fn().mockReturnThis(),
72
- length: 0
73
- }))
74
- };
6
+ const originalDefaults = { ...ImportFromCsv._defaults };
7
+
8
+ function setupFormMatching() {
9
+ const form = document.createElement('div');
10
+ form.classList.add('form_matching');
11
+ form.innerHTML = `
12
+ <div class="import_matching_select_content"></div>
13
+ <div class="errors hide"></div>
14
+ <button type="submit">Import</button>`;
15
+ document.body.appendChild(form);
16
+ return form;
17
+ }
18
+
19
+ function setupDivResult(rows = []) {
20
+ const div = document.createElement('div');
21
+ div.classList.add('csv_result', 'hide');
22
+ const tbodyRows = rows.map((row, rowIndex) => {
23
+ const entries = Array.isArray(row) ? row.map((v, i) => [i, v]) : Object.entries(row);
24
+ const tds = entries.map(([key, val]) => `<td data-key="${key}">${val ?? ''}</td>`).join('');
25
+ return `<tr data-line="${rowIndex + 1}">
26
+ <td class="select_line_checkbox"><input type="checkbox" class="import_line_checkbox" checked="checked" /></td>
27
+ ${tds}
28
+ <td class="edit_line_button"></td>
29
+ </tr>`;
30
+ }).join('');
31
+ div.innerHTML = `<table><tbody>${tbodyRows}</tbody></table>`;
32
+ document.body.appendChild(div);
33
+ return div;
34
+ }
35
+
36
+ function setupTableWithRow(cells = ['John', 'john@example.com'], formMatching = null) {
37
+ const fm = formMatching ?? setupFormMatching();
38
+ const table = document.createElement('table');
39
+ const tbody = document.createElement('tbody');
40
+ const tr = document.createElement('tr');
41
+
42
+ const checkboxTd = document.createElement('td');
43
+ checkboxTd.className = 'select_line_checkbox';
44
+ checkboxTd.innerHTML = '<input type="checkbox" class="import_line_checkbox" checked="checked" />';
45
+ tr.appendChild(checkboxTd);
46
+
47
+ cells.forEach((val, i) => {
48
+ const td = document.createElement('td');
49
+ td.dataset.key = String(i);
50
+ td.textContent = val ?? '';
51
+ tr.appendChild(td);
75
52
  });
76
53
 
77
- afterEach(() => {
78
- delete global.$;
79
- ImportFromCsv._defaults = {};
80
- });
54
+ const editTd = document.createElement('td');
55
+ editTd.className = 'edit_line_button';
56
+ tr.appendChild(editTd);
57
+
58
+ tbody.appendChild(tr);
59
+ table.appendChild(tbody);
60
+ document.body.appendChild(table);
61
+
62
+ return { table, tr, editTd, fm };
63
+ }
64
+
65
+ afterEach(() => {
66
+ document.body.innerHTML = '';
67
+ ImportFromCsv._defaults = { ...originalDefaults };
68
+ });
69
+
70
+ describe('ImportFromCsv', () => {
81
71
 
82
72
  describe('setDefault', () => {
83
73
  test('should set default options', () => {
@@ -95,10 +85,7 @@ describe('ImportFromCsv', () => {
95
85
  });
96
86
 
97
87
  test('should use defaults in getErrorsHtmlOfImportData when no params passed', () => {
98
- ImportFromCsv.setDefault({
99
- errorMessageImportFailed: 'Default error',
100
- lineLabel: 'Row {0}',
101
- });
88
+ ImportFromCsv.setDefault({ errorMessageImportFailed: 'Default error', lineLabel: 'Row {0}' });
102
89
 
103
90
  const html = ImportFromCsv.getErrorsHtmlOfImportData([{ line: 1, errors: ['Err'] }]);
104
91
 
@@ -108,23 +95,12 @@ describe('ImportFromCsv', () => {
108
95
 
109
96
  test('should use defaults in displayFormMatching when no selectDefaultOptionLabel passed', () => {
110
97
  ImportFromCsv.setDefault({ selectDefaultOptionLabel: 'Pick a column' });
98
+ const formMatching = setupFormMatching();
111
99
 
112
- const mockSelectContent = {
113
- addClass: jest.fn().mockReturnThis(),
114
- empty: jest.fn().mockReturnThis(),
115
- append: jest.fn().mockReturnThis()
116
- };
117
- mockFormMatching.find = jest.fn((selector) => {
118
- if (selector === '.import_matching_select_content') return mockSelectContent;
119
- return { addClass: jest.fn().mockReturnThis(), removeClass: jest.fn().mockReturnThis() };
120
- });
121
- mockFormMatching.removeClass = jest.fn().mockReturnThis();
122
-
123
- ImportFromCsv.displayFormMatching(mockFormMatching, { col: 'Col' }, ['Col'], true);
100
+ ImportFromCsv.displayFormMatching(formMatching, { col: 'Col' }, ['Col'], true);
124
101
 
125
- // $ est appelé avec la string HTML contenant le label
126
- const calls = global.$.mock.calls.map(([arg]) => arg).filter(a => typeof a === 'string');
127
- expect(calls.some(s => s.includes('Pick a column'))).toBe(true);
102
+ const select = formMatching.querySelector('select');
103
+ expect(select.options[0].textContent).toBe('Pick a column');
128
104
  });
129
105
  });
130
106
 
@@ -151,8 +127,7 @@ describe('ImportFromCsv', () => {
151
127
  });
152
128
 
153
129
  test('should return false for empty array', () => {
154
- const json = [];
155
- expect(ImportFromCsv.isImportErrors(json)).toBe(false);
130
+ expect(ImportFromCsv.isImportErrors([])).toBe(false);
156
131
  });
157
132
 
158
133
  test('should return true when at least one item has line and errors', () => {
@@ -166,46 +141,31 @@ describe('ImportFromCsv', () => {
166
141
 
167
142
  describe('getTabLink', () => {
168
143
  test('should extract selected values from selects', () => {
169
- const mockSelects = [
170
- { val: jest.fn(() => '0'), prop: jest.fn((name) => name === 'name' ? 'name' : undefined) },
171
- { val: jest.fn(() => '1'), prop: jest.fn((name) => name === 'name' ? 'email' : undefined) },
172
- { val: jest.fn(() => '-1'), prop: jest.fn((name) => name === 'name' ? 'phone' : undefined) }
173
- ];
144
+ const formMatching = setupFormMatching();
145
+ formMatching.querySelector('.import_matching_select_content').innerHTML = `
146
+ <select name="name"><option value="0" selected>Col0</option></select>
147
+ <select name="email"><option value="1" selected>Col1</option></select>
148
+ <select name="phone"><option value="-1" selected>--</option></select>`;
174
149
 
175
- global.$ = jest.fn((el) => ({
176
- val: el.val,
177
- prop: el.prop
178
- }));
150
+ const result = ImportFromCsv.getTabLink(formMatching);
179
151
 
180
- mockFormMatching.find = jest.fn((selector) => {
181
- if (selector === 'select') {
182
- return {
183
- each: jest.fn((callback) => {
184
- mockSelects.forEach((select, idx) => {
185
- callback(idx, select);
186
- });
187
- })
188
- };
189
- }
190
- return { each: jest.fn() };
191
- });
152
+ expect(result).toEqual({ name: '0', email: '1' });
153
+ });
192
154
 
193
- const result = ImportFromCsv.getTabLink(mockFormMatching);
155
+ test('should return empty object when no selects with valid values', () => {
156
+ const formMatching = setupFormMatching();
157
+ formMatching.querySelector('.import_matching_select_content').innerHTML = `
158
+ <select name="name"><option value="-1" selected>--</option></select>`;
194
159
 
195
- expect(result).toEqual({
196
- name: '0',
197
- email: '1'
198
- });
160
+ const result = ImportFromCsv.getTabLink(formMatching);
161
+
162
+ expect(result).toEqual({});
199
163
  });
200
164
 
201
- test('should return empty object when no selects with valid values', () => {
202
- mockFormMatching.find = jest.fn(() => ({
203
- each: jest.fn((callback) => {
204
- // No selects or all have -1
205
- })
206
- }));
165
+ test('should return empty object when no selects', () => {
166
+ const formMatching = setupFormMatching();
207
167
 
208
- const result = ImportFromCsv.getTabLink(mockFormMatching);
168
+ const result = ImportFromCsv.getTabLink(formMatching);
209
169
 
210
170
  expect(result).toEqual({});
211
171
  });
@@ -230,28 +190,20 @@ describe('ImportFromCsv', () => {
230
190
  });
231
191
 
232
192
  test('should mark error lines in divResult when provided', () => {
233
- const json = [{ line: 2, errors: ['Error'] }];
234
-
235
- const mockTr = {
236
- addClass: jest.fn().mockReturnThis()
237
- };
193
+ const divResult = setupDivResult([['a'], ['b']]);
238
194
 
239
- mockDivResult.find = jest.fn((selector) => {
240
- if (selector.includes('tr[data-line="2"]')) {
241
- return mockTr;
242
- }
243
- return { addClass: jest.fn().mockReturnThis() };
244
- });
195
+ const json = [{ line: 2, errors: ['Error'] }];
196
+ ImportFromCsv.getErrorsHtmlOfImportData(json, divResult, 'Import failed', 'Line {0}');
245
197
 
246
- ImportFromCsv.getErrorsHtmlOfImportData(json, mockDivResult, 'Import failed', 'Line {0}');
198
+ const tr2 = divResult.querySelector('table tr[data-line="2"]');
199
+ expect(tr2.classList.contains('danger')).toBe(true);
247
200
 
248
- expect(mockTr.addClass).toHaveBeenCalledWith('danger');
201
+ const tr1 = divResult.querySelector('table tr[data-line="1"]');
202
+ expect(tr1.classList.contains('danger')).toBe(false);
249
203
  });
250
204
 
251
205
  test('should handle empty errors array', () => {
252
- const json = [];
253
-
254
- const html = ImportFromCsv.getErrorsHtmlOfImportData(json, null, 'Import failed', 'Line {0}');
206
+ const html = ImportFromCsv.getErrorsHtmlOfImportData([], null, 'Import failed', 'Line {0}');
255
207
 
256
208
  expect(html).toContain('Import failed');
257
209
  expect(html).toContain('<ul>');
@@ -261,579 +213,366 @@ describe('ImportFromCsv', () => {
261
213
 
262
214
  describe('getDataToImport', () => {
263
215
  test('should extract data from checked rows', () => {
264
- const tabLink = {
265
- name: '0',
266
- email: '1'
267
- };
268
-
269
- const mockTr1 = {
270
- find: jest.fn((selector) => {
271
- if (selector === 'input.import_line_checkbox:checked') {
272
- return { length: 1 };
273
- }
274
- if (selector === 'td[data-key="0"]') {
275
- return { length: 1, text: jest.fn(() => 'John Doe') };
276
- }
277
- if (selector === 'td[data-key="1"]') {
278
- return { length: 1, text: jest.fn(() => 'john@example.com') };
279
- }
280
- return { length: 0 };
281
- })
282
- };
283
-
284
- const mockTr2 = {
285
- find: jest.fn((selector) => {
286
- if (selector === 'input.import_line_checkbox:checked') {
287
- return { length: 0 }; // Not checked
288
- }
289
- return { length: 0 };
290
- })
291
- };
292
-
293
- global.$ = jest.fn((el) => el);
294
- global.$.each = jest.fn((collection, callback) => {
295
- if (Array.isArray(collection)) {
296
- collection.forEach((item, index) => callback(index, item));
297
- } else {
298
- Object.keys(collection).forEach((key) => callback(key, collection[key]));
299
- }
300
- });
301
-
302
- mockDivResult.find = jest.fn((selector) => {
303
- if (selector === 'table tbody tr') {
304
- return [mockTr1, mockTr2];
305
- }
306
- return { length: 0 };
307
- });
308
-
309
- const result = ImportFromCsv.getDataToImport(mockDivResult, tabLink);
216
+ const divResult = setupDivResult([
217
+ ['John Doe', 'john@example.com'],
218
+ ['Jane', 'jane@example.com']
219
+ ]);
220
+ // Uncheck second row
221
+ divResult.querySelectorAll('input.import_line_checkbox')[1].checked = false;
222
+
223
+ const result = ImportFromCsv.getDataToImport(divResult, { name: '0', email: '1' });
310
224
 
311
225
  expect(result).toHaveLength(1);
312
- expect(result[0]).toEqual({
313
- line: 1,
314
- name: 'John Doe',
315
- email: 'john@example.com'
316
- });
226
+ expect(result[0]).toEqual({ line: 1, name: 'John Doe', email: 'john@example.com' });
317
227
  });
318
228
 
319
229
  test('should skip rows without checked checkbox', () => {
320
- const tabLink = { name: '0' };
321
-
322
- const mockTr = {
323
- find: jest.fn(() => ({ length: 0 })) // No checked checkbox
324
- };
230
+ const divResult = setupDivResult([['John']]);
231
+ divResult.querySelector('input.import_line_checkbox').checked = false;
325
232
 
326
- global.$ = jest.fn((el) => el);
327
- global.$.each = jest.fn((collection, callback) => {
328
- if (Array.isArray(collection)) {
329
- collection.forEach((item, index) => callback(index, item));
330
- }
331
- });
332
-
333
- mockDivResult.find = jest.fn(() => [mockTr]);
334
-
335
- const result = ImportFromCsv.getDataToImport(mockDivResult, tabLink);
233
+ const result = ImportFromCsv.getDataToImport(divResult, { name: '0' });
336
234
 
337
235
  expect(result).toEqual([]);
338
236
  });
339
237
 
340
238
  test('should handle missing columns gracefully', () => {
341
- const tabLink = {
342
- name: '0',
343
- email: '1',
344
- phone: '2' // This column doesn't exist
345
- };
346
-
347
- const mockTr = {
348
- find: jest.fn((selector) => {
349
- if (selector === 'input.import_line_checkbox:checked') {
350
- return { length: 1 };
351
- }
352
- if (selector === 'td[data-key="0"]') {
353
- return { length: 1, text: jest.fn(() => 'John') };
354
- }
355
- if (selector === 'td[data-key="1"]') {
356
- return { length: 1, text: jest.fn(() => 'john@test.com') };
357
- }
358
- if (selector === 'td[data-key="2"]') {
359
- return { length: 0 }; // Missing
360
- }
361
- return { length: 0 };
362
- })
363
- };
364
-
365
- global.$ = jest.fn((el) => el);
366
- global.$.each = jest.fn((collection, callback) => {
367
- if (Array.isArray(collection)) {
368
- collection.forEach((item, index) => callback(index, item));
369
- } else {
370
- Object.keys(collection).forEach((key) => callback(key, collection[key]));
371
- }
372
- });
373
-
374
- mockDivResult.find = jest.fn(() => [mockTr]);
375
-
376
- const result = ImportFromCsv.getDataToImport(mockDivResult, tabLink);
239
+ const divResult = setupDivResult([['John', 'john@test.com']]);
240
+ // tabLink references key "2" which has no td
241
+
242
+ const result = ImportFromCsv.getDataToImport(divResult, { name: '0', email: '1', phone: '2' });
377
243
 
378
244
  expect(result).toHaveLength(1);
379
- expect(result[0]).toEqual({
380
- line: 1,
381
- name: 'John',
382
- email: 'john@test.com'
383
- // phone is not included because td was not found
384
- });
245
+ expect(result[0]).toEqual({ line: 1, name: 'John', email: 'john@test.com' });
246
+ expect(result[0].phone).toBeUndefined();
247
+ });
248
+
249
+ test('should include line number in each result', () => {
250
+ const divResult = setupDivResult([['A'], ['B'], ['C']]);
251
+
252
+ const result = ImportFromCsv.getDataToImport(divResult, { val: '0' });
253
+
254
+ expect(result[0].line).toBe(1);
255
+ expect(result[1].line).toBe(2);
256
+ expect(result[2].line).toBe(3);
257
+ });
258
+
259
+ test('should return empty array for empty table', () => {
260
+ const divResult = setupDivResult([]);
261
+
262
+ const result = ImportFromCsv.getDataToImport(divResult, { name: '0' });
263
+
264
+ expect(result).toEqual([]);
385
265
  });
386
266
  });
387
267
 
388
268
  describe('displayFormMatching', () => {
389
269
  test('should create select elements for each import column', () => {
390
- const importColumns = {
391
- name: 'Name',
392
- email: 'Email',
393
- phone: 'Phone'
394
- };
270
+ const formMatching = setupFormMatching();
271
+ const importColumns = { name: 'Name', email: 'Email', phone: 'Phone' };
395
272
  const header = ['Name', 'Email', 'Phone', 'Address'];
396
- const hasHeader = true;
397
-
398
- const mockSelectContent = {
399
- addClass: jest.fn().mockReturnThis(),
400
- empty: jest.fn().mockReturnThis(),
401
- append: jest.fn()
402
- };
403
273
 
404
- const mockDiv = {
405
- addClass: jest.fn().mockReturnThis()
406
- };
274
+ ImportFromCsv.displayFormMatching(formMatching, importColumns, header, true);
407
275
 
408
- mockFormMatching.find = jest.fn((selector) => {
409
- if (selector === '.import_matching_select_content') {
410
- return mockSelectContent;
411
- }
412
- if (selector === 'div.errors') {
413
- return mockDiv;
414
- }
415
- return { addClass: jest.fn().mockReturnThis(), removeClass: jest.fn().mockReturnThis() };
416
- });
276
+ const selects = formMatching.querySelectorAll('select');
277
+ expect(selects).toHaveLength(3);
278
+ expect(selects[0].name).toBe('name');
279
+ expect(selects[1].name).toBe('email');
280
+ expect(selects[2].name).toBe('phone');
281
+ });
417
282
 
418
- mockFormMatching.removeClass = jest.fn().mockReturnThis();
283
+ test('should populate options from header', () => {
284
+ const formMatching = setupFormMatching();
285
+ const header = ['Col A', 'Col B'];
419
286
 
420
- ImportFromCsv.displayFormMatching(mockFormMatching, importColumns, header, hasHeader);
287
+ ImportFromCsv.displayFormMatching(formMatching, { field: 'Field' }, header, true);
421
288
 
422
- expect(mockSelectContent.empty).toHaveBeenCalled();
423
- expect(mockSelectContent.append).toHaveBeenCalledTimes(3); // name, email, phone
424
- expect(mockFormMatching.removeClass).toHaveBeenCalledWith('hide');
289
+ const select = formMatching.querySelector('select');
290
+ const optionValues = [...select.options].map(o => o.value);
291
+ expect(optionValues).toContain('Col A');
292
+ expect(optionValues).toContain('Col B');
293
+ expect(optionValues).toContain('-1');
425
294
  });
426
295
 
427
- test('should use index as value when hasHeader is false', () => {
428
- const importColumns = { field1: 'Field 1' };
296
+ test('should use index as option value when hasHeader is false', () => {
297
+ const formMatching = setupFormMatching();
429
298
  const header = ['Col1', 'Col2'];
430
- const hasHeader = false;
431
-
432
- const mockSelectContent = {
433
- addClass: jest.fn().mockReturnThis(),
434
- empty: jest.fn().mockReturnThis(),
435
- append: jest.fn()
436
- };
437
-
438
- mockFormMatching.find = jest.fn((selector) => {
439
- if (selector === '.import_matching_select_content') {
440
- return mockSelectContent;
441
- }
442
- return {
443
- addClass: jest.fn().mockReturnThis(),
444
- removeClass: jest.fn().mockReturnThis()
445
- };
446
- });
447
-
448
- mockFormMatching.removeClass = jest.fn().mockReturnThis();
449
-
450
- // Just verify it doesn't throw
451
- expect(() => {
452
- ImportFromCsv.displayFormMatching(mockFormMatching, importColumns, header, hasHeader);
453
- }).not.toThrow();
299
+
300
+ ImportFromCsv.displayFormMatching(formMatching, { field: 'Field' }, header, false);
301
+
302
+ const select = formMatching.querySelector('select');
303
+ const optionValues = [...select.options].map(o => o.value);
304
+ expect(optionValues).toContain('0');
305
+ expect(optionValues).toContain('1');
306
+ });
307
+
308
+ test('should auto-select option matching column label', () => {
309
+ const formMatching = setupFormMatching();
310
+ const importColumns = { name: 'Name', email: 'Email' };
311
+ const header = ['Name', 'Email', 'Phone'];
312
+
313
+ ImportFromCsv.displayFormMatching(formMatching, importColumns, header, true);
314
+
315
+ const nameSelect = formMatching.querySelector('select[name="name"]');
316
+ expect(nameSelect.value).toBe('Name');
317
+
318
+ const emailSelect = formMatching.querySelector('select[name="email"]');
319
+ expect(emailSelect.value).toBe('Email');
320
+ });
321
+
322
+ test('should show formMatching and hide errors', () => {
323
+ const formMatching = setupFormMatching();
324
+ formMatching.classList.add('hide');
325
+
326
+ ImportFromCsv.displayFormMatching(formMatching, { col: 'Col' }, ['Col'], true);
327
+
328
+ expect(formMatching.classList.contains('hide')).toBe(false);
329
+ expect(formMatching.querySelector('div.errors').classList.contains('hide')).toBe(true);
330
+ });
331
+
332
+ test('should clear previous select content', () => {
333
+ const formMatching = setupFormMatching();
334
+
335
+ ImportFromCsv.displayFormMatching(formMatching, { a: 'A' }, ['A'], true);
336
+ ImportFromCsv.displayFormMatching(formMatching, { b: 'B', c: 'C' }, ['B', 'C'], true);
337
+
338
+ const selects = formMatching.querySelectorAll('select');
339
+ expect(selects).toHaveLength(2);
454
340
  });
455
341
  });
456
342
 
457
343
  describe('displayData', () => {
458
344
  test('should create table with header when header is provided', () => {
459
- const data = [
460
- ['John', 'john@example.com'],
461
- ['Jane', 'jane@example.com']
462
- ];
345
+ const formMatching = setupFormMatching();
346
+ const divResult = document.createElement('div');
347
+ divResult.classList.add('csv_result', 'hide');
348
+ document.body.appendChild(divResult);
349
+
350
+ const data = [['John', 'john@example.com'], ['Jane', 'jane@example.com']];
463
351
  const header = ['Name', 'Email'];
464
352
 
465
- const mockTable = {
466
- empty: jest.fn().mockReturnThis(),
467
- html: jest.fn().mockReturnThis(),
468
- find: jest.fn(() => ({
469
- each: jest.fn()
470
- })),
471
- length: 1
472
- };
473
-
474
- mockDivResult.find = jest.fn((selector) => {
475
- if (selector === 'table') {
476
- return mockTable;
477
- }
478
- return { length: 0 };
479
- });
480
-
481
- mockDivResult.removeClass = jest.fn().mockReturnThis();
482
- mockDivResult.append = jest.fn().mockReturnThis();
483
-
484
- ImportFromCsv.displayData(mockDivResult, data, header, mockFormMatching);
485
-
486
- expect(mockTable.empty).toHaveBeenCalled();
487
- expect(mockTable.html).toHaveBeenCalled();
488
- const htmlContent = mockTable.html.mock.calls[0][0];
489
- expect(htmlContent).toContain('<thead>');
490
- expect(htmlContent).toContain('Name');
491
- expect(htmlContent).toContain('Email');
492
- expect(htmlContent).toContain('<tbody>');
493
- expect(htmlContent).toContain('John');
494
- expect(htmlContent).toContain('jane@example.com');
495
- expect(mockDivResult.removeClass).toHaveBeenCalledWith('hide');
353
+ ImportFromCsv.displayData(divResult, data, header, formMatching);
354
+
355
+ const thead = divResult.querySelector('thead');
356
+ expect(thead).not.toBeNull();
357
+ expect(thead.textContent).toContain('Name');
358
+ expect(thead.textContent).toContain('Email');
496
359
  });
497
360
 
498
361
  test('should create table without header when header is null', () => {
499
- const data = [
500
- ['John', 'john@example.com']
501
- ];
502
-
503
- const mockTable = {
504
- empty: jest.fn().mockReturnThis(),
505
- html: jest.fn().mockReturnThis(),
506
- find: jest.fn(() => ({
507
- each: jest.fn()
508
- })),
509
- length: 1
510
- };
511
-
512
- mockDivResult.find = jest.fn(() => mockTable);
513
- mockDivResult.removeClass = jest.fn().mockReturnThis();
362
+ const formMatching = setupFormMatching();
363
+ const divResult = document.createElement('div');
364
+ document.body.appendChild(divResult);
514
365
 
515
- ImportFromCsv.displayData(mockDivResult, data, null, mockFormMatching);
366
+ ImportFromCsv.displayData(divResult, [['John']], null, formMatching);
516
367
 
517
- const htmlContent = mockTable.html.mock.calls[0][0];
518
- expect(htmlContent).not.toContain('<thead>');
519
- expect(htmlContent).toContain('<tbody>');
368
+ expect(divResult.querySelector('thead')).toBeNull();
369
+ expect(divResult.querySelector('tbody')).not.toBeNull();
520
370
  });
521
371
 
522
372
  test('should add checkboxes for each row', () => {
523
- const data = [['John'], ['Jane']];
373
+ const formMatching = setupFormMatching();
374
+ const divResult = document.createElement('div');
375
+ document.body.appendChild(divResult);
524
376
 
525
- const mockTable = {
526
- empty: jest.fn().mockReturnThis(),
527
- html: jest.fn().mockReturnThis(),
528
- find: jest.fn(() => ({
529
- each: jest.fn()
530
- })),
531
- length: 1
532
- };
377
+ ImportFromCsv.displayData(divResult, [['John'], ['Jane']], null, formMatching);
533
378
 
534
- mockDivResult.find = jest.fn(() => mockTable);
535
- mockDivResult.removeClass = jest.fn().mockReturnThis();
379
+ const checkboxes = divResult.querySelectorAll('input.import_line_checkbox');
380
+ expect(checkboxes).toHaveLength(2);
381
+ expect(checkboxes[0].checked).toBe(true);
382
+ });
383
+
384
+ test('should set data-line attribute on rows', () => {
385
+ const formMatching = setupFormMatching();
386
+ const divResult = document.createElement('div');
387
+ document.body.appendChild(divResult);
536
388
 
537
- ImportFromCsv.displayData(mockDivResult, data, null, mockFormMatching);
389
+ ImportFromCsv.displayData(divResult, [['A'], ['B'], ['C']], null, formMatching);
538
390
 
539
- const htmlContent = mockTable.html.mock.calls[0][0];
540
- expect(htmlContent).toContain('import_line_checkbox');
541
- expect(htmlContent).toContain('checked="checked"');
542
- expect(htmlContent).toContain('data-line="1"');
543
- expect(htmlContent).toContain('data-line="2"');
391
+ expect(divResult.querySelector('tr[data-line="1"]')).not.toBeNull();
392
+ expect(divResult.querySelector('tr[data-line="2"]')).not.toBeNull();
393
+ expect(divResult.querySelector('tr[data-line="3"]')).not.toBeNull();
544
394
  });
545
395
 
546
396
  test('should create table if it does not exist', () => {
547
- const data = [['test']];
548
-
549
- mockDivResult.find = jest.fn((selector) => {
550
- if (selector === 'table') {
551
- return { length: 0 }; // No table exists
552
- }
553
- return {
554
- each: jest.fn()
555
- };
556
- });
557
-
558
- let appendedTable = null;
559
- mockDivResult.append = jest.fn((html) => {
560
- appendedTable = html;
561
- // After append, table exists
562
- mockDivResult.find = jest.fn((sel) => {
563
- if (sel === 'table') {
564
- return {
565
- empty: jest.fn().mockReturnThis(),
566
- html: jest.fn().mockReturnThis(),
567
- find: jest.fn(() => ({ each: jest.fn() })),
568
- length: 1
569
- };
570
- }
571
- return { each: jest.fn() };
572
- });
573
- return mockDivResult;
574
- });
575
-
576
- mockDivResult.removeClass = jest.fn().mockReturnThis();
577
-
578
- ImportFromCsv.displayData(mockDivResult, data, null, mockFormMatching);
579
-
580
- expect(mockDivResult.append).toHaveBeenCalledWith('<table class="table table-sm table-bordered"></table>');
397
+ const formMatching = setupFormMatching();
398
+ const divResult = document.createElement('div');
399
+ document.body.appendChild(divResult);
400
+
401
+ ImportFromCsv.displayData(divResult, [['test']], null, formMatching);
402
+
403
+ expect(divResult.querySelector('table')).not.toBeNull();
404
+ expect(divResult.querySelector('table').classList.contains('table')).toBe(true);
581
405
  });
582
406
 
583
407
  test('should handle null values in data', () => {
584
- const data = [
585
- ['John', null, 'test@example.com']
586
- ];
408
+ const formMatching = setupFormMatching();
409
+ const divResult = document.createElement('div');
410
+ document.body.appendChild(divResult);
411
+
412
+ ImportFromCsv.displayData(divResult, [['John', null, 'test@example.com']], null, formMatching);
413
+
414
+ const tds = divResult.querySelectorAll('tbody td[data-key]');
415
+ expect(tds[0].textContent).toBe('John');
416
+ expect(tds[1].textContent).toBe('');
417
+ expect(tds[2].textContent).toBe('test@example.com');
418
+ });
419
+
420
+ test('should show divResult after populating data', () => {
421
+ const formMatching = setupFormMatching();
422
+ const divResult = document.createElement('div');
423
+ divResult.classList.add('hide');
424
+ document.body.appendChild(divResult);
425
+
426
+ ImportFromCsv.displayData(divResult, [['A']], null, formMatching);
427
+
428
+ expect(divResult.classList.contains('hide')).toBe(false);
429
+ });
430
+
431
+ test('should add edit links to each row', () => {
432
+ const formMatching = setupFormMatching();
433
+ const divResult = document.createElement('div');
434
+ document.body.appendChild(divResult);
435
+
436
+ ImportFromCsv.displayData(divResult, [['A'], ['B']], null, formMatching);
437
+
438
+ const editLinks = divResult.querySelectorAll('a.import_edit_line');
439
+ expect(editLinks).toHaveLength(2);
440
+ });
587
441
 
588
- const mockTable = {
589
- empty: jest.fn().mockReturnThis(),
590
- html: jest.fn().mockReturnThis(),
591
- find: jest.fn(() => ({
592
- each: jest.fn()
593
- })),
594
- length: 1
595
- };
442
+ test('should handle object-based rows (with header)', () => {
443
+ const formMatching = setupFormMatching();
444
+ const divResult = document.createElement('div');
445
+ document.body.appendChild(divResult);
596
446
 
597
- mockDivResult.find = jest.fn(() => mockTable);
598
- mockDivResult.removeClass = jest.fn().mockReturnThis();
447
+ const data = [{ name: 'John', email: 'john@example.com' }];
448
+ const header = ['name', 'email'];
599
449
 
600
- ImportFromCsv.displayData(mockDivResult, data, null, mockFormMatching);
450
+ ImportFromCsv.displayData(divResult, data, header, formMatching);
601
451
 
602
- const htmlContent = mockTable.html.mock.calls[0][0];
603
- expect(htmlContent).toContain('John');
604
- expect(htmlContent).toContain('test@example.com');
605
- // Null should be replaced with empty string
606
- expect(htmlContent).toMatch(/data-key="1">(<\/td>|<)/);
452
+ expect(divResult.querySelector('td[data-key="name"]').textContent).toBe('John');
453
+ expect(divResult.querySelector('td[data-key="email"]').textContent).toBe('john@example.com');
607
454
  });
608
455
  });
609
456
 
610
457
  describe('initEditLink', () => {
611
- test('should setup edit link with click handler', () => {
612
- const mockTd = {
613
- html: jest.fn().mockReturnThis(),
614
- find: jest.fn().mockReturnThis()
615
- };
616
-
617
- let clickHandler = null;
618
- mockTd.find = jest.fn((selector) => {
619
- if (selector === 'a.import_edit_line') {
620
- return {
621
- click: jest.fn((handler) => {
622
- clickHandler = handler;
623
- return mockTd;
624
- })
625
- };
626
- }
627
- return mockTd;
628
- });
629
-
630
- global.$ = jest.fn((selector) => {
631
- if (typeof selector === 'string' && selector.includes('<a')) {
632
- return mockTd;
633
- }
634
- return mockTd;
635
- });
636
-
637
- ImportFromCsv.initEditLink(mockFormMatching, mockTd);
638
-
639
- expect(mockTd.html).toHaveBeenCalled();
640
- expect(clickHandler).toBeDefined();
641
- });
642
-
643
- test('should convert td contents to inputs when edit link is clicked', () => {
644
- const mockInput = {
645
- html: jest.fn().mockReturnThis(),
646
- find: jest.fn().mockReturnThis(),
647
- val: jest.fn()
648
- };
649
-
650
- const mockTdToEdit = {
651
- hasClass: jest.fn((className) => false),
652
- html: jest.fn(function(value) {
653
- if (value !== undefined) return this;
654
- return 'John Doe';
655
- }),
656
- data: jest.fn().mockReturnThis()
657
- };
658
-
659
- const mockTr = {
660
- find: jest.fn((selector) => {
661
- if (selector === 'td') {
662
- return {
663
- each: jest.fn((callback) => {
664
- callback(0, mockTdToEdit);
665
- })
666
- };
667
- }
668
- return mockInput;
669
- })
670
- };
671
-
672
- const mockEditTd = {
673
- html: jest.fn().mockReturnThis(),
674
- find: jest.fn().mockReturnThis(),
675
- parent: jest.fn(() => ({ parent: jest.fn(() => mockTr) }))
676
- };
677
-
678
- let clickHandler = null;
679
- const mockValidateLink = {
680
- click: jest.fn().mockReturnThis()
681
- };
682
- mockEditTd.find = jest.fn((selector) => {
683
- if (selector === 'a.import_edit_line') {
684
- return {
685
- click: jest.fn((handler) => {
686
- clickHandler = handler;
687
- return mockEditTd;
688
- })
689
- };
690
- }
691
- if (selector === 'a.import_validate_line') {
692
- return mockValidateLink;
693
- }
694
- if (selector === 'td input[type="text"]') {
695
- return { length: 1 };
696
- }
697
- return mockEditTd;
698
- });
699
- mockEditTd.closest = jest.fn(() => ({
700
- find: jest.fn((selector) => {
701
- if (selector === 'td input[type="text"]') {
702
- return { length: 1 };
703
- }
704
- return { length: 0 };
705
- })
706
- }));
707
-
708
- global.$ = jest.fn((selector) => {
709
- if (typeof selector === 'string') {
710
- if (selector.includes('<a')) {
711
- return mockEditTd;
712
- }
713
- if (selector.includes('<input')) {
714
- return mockInput;
715
- }
716
- }
717
- if (selector === mockTdToEdit) return mockTdToEdit;
718
- return { parent: jest.fn(() => ({ parent: jest.fn(() => mockTr) })) };
719
- });
720
-
721
- mockFormMatching.find = jest.fn(() => ({
722
- prop: jest.fn().mockReturnThis()
723
- }));
724
-
725
- ImportFromCsv.initEditLink(mockFormMatching, mockEditTd);
726
-
727
- // Trigger the click
728
- clickHandler();
729
-
730
- expect(mockTdToEdit.data).toHaveBeenCalledWith('original_value', 'John Doe');
731
- expect(mockTdToEdit.html).toHaveBeenCalled();
458
+ test('should set up edit link in td', () => {
459
+ const { editTd, fm } = setupTableWithRow();
460
+
461
+ ImportFromCsv.initEditLink(fm, editTd);
462
+
463
+ expect(editTd.querySelector('a.import_edit_line')).not.toBeNull();
464
+ });
465
+
466
+ test('should convert cells to inputs on click', () => {
467
+ const { editTd, fm } = setupTableWithRow(['John', 'john@example.com']);
468
+
469
+ ImportFromCsv.initEditLink(fm, editTd);
470
+ editTd.querySelector('a.import_edit_line').click();
471
+
472
+ const inputs = document.querySelectorAll('td[data-key] input[type="text"]');
473
+ expect(inputs).toHaveLength(2);
474
+ expect(inputs[0].value).toBe('John');
475
+ expect(inputs[1].value).toBe('john@example.com');
476
+ });
477
+
478
+ test('should disable submit button when editing', () => {
479
+ const { editTd, fm } = setupTableWithRow(['John']);
480
+
481
+ ImportFromCsv.initEditLink(fm, editTd);
482
+ editTd.querySelector('a.import_edit_line').click();
483
+
484
+ expect(fm.querySelector('button[type="submit"]').disabled).toBe(true);
485
+ });
486
+
487
+ test('should save original cell value in dataset', () => {
488
+ const { editTd, fm } = setupTableWithRow(['John']);
489
+
490
+ ImportFromCsv.initEditLink(fm, editTd);
491
+ editTd.querySelector('a.import_edit_line').click();
492
+
493
+ const dataCell = document.querySelector('td[data-key="0"]');
494
+ expect(dataCell.dataset.original_value).toBe('John');
495
+ });
496
+
497
+ test('should replace edit link with validate link after click', () => {
498
+ const { editTd, fm } = setupTableWithRow(['John']);
499
+
500
+ ImportFromCsv.initEditLink(fm, editTd);
501
+ editTd.querySelector('a.import_edit_line').click();
502
+
503
+ expect(editTd.querySelector('a.import_edit_line')).toBeNull();
504
+ expect(editTd.querySelector('a.import_validate_line')).not.toBeNull();
505
+ });
506
+
507
+ test('should not affect select_line_checkbox or edit_line_button cells', () => {
508
+ const { editTd, fm } = setupTableWithRow(['John']);
509
+
510
+ ImportFromCsv.initEditLink(fm, editTd);
511
+ editTd.querySelector('a.import_edit_line').click();
512
+
513
+ const checkboxTd = document.querySelector('td.select_line_checkbox');
514
+ expect(checkboxTd.querySelector('input[type="text"]')).toBeNull();
732
515
  });
733
516
  });
734
517
 
735
518
  describe('initValidateLine', () => {
736
- test('should setup validate link with click handler', () => {
737
- const mockTd = {
738
- html: jest.fn().mockReturnThis(),
739
- find: jest.fn().mockReturnThis()
740
- };
741
-
742
- let clickHandler = null;
743
- mockTd.find = jest.fn((selector) => {
744
- if (selector === 'a.import_validate_line') {
745
- return {
746
- click: jest.fn((handler) => {
747
- clickHandler = handler;
748
- return mockTd;
749
- })
750
- };
751
- }
752
- return mockTd;
753
- });
754
-
755
- global.$ = jest.fn((selector) => {
756
- if (typeof selector === 'string' && selector.includes('<a')) {
757
- return mockTd;
758
- }
759
- return mockTd;
760
- });
761
-
762
- ImportFromCsv.initValidateLine(mockFormMatching, mockTd);
763
-
764
- expect(mockTd.html).toHaveBeenCalled();
765
- expect(clickHandler).toBeDefined();
766
- });
767
-
768
- test('should convert inputs back to text when validate is clicked', () => {
769
- const mockInput = {
770
- val: jest.fn(() => 'Updated Value')
771
- };
772
-
773
- const mockTdWithInput = {
774
- hasClass: jest.fn((className) => false),
775
- html: jest.fn().mockReturnThis(),
776
- find: jest.fn(() => mockInput)
777
- };
778
-
779
- const mockTr = {
780
- find: jest.fn((selector) => {
781
- if (selector === 'td') {
782
- return {
783
- each: jest.fn((callback) => {
784
- callback(0, mockTdWithInput);
785
- })
786
- };
787
- }
788
- return { length: 0 };
789
- })
790
- };
791
-
792
- const mockValidateTd = {
793
- html: jest.fn().mockReturnThis(),
794
- find: jest.fn().mockReturnThis(),
795
- parent: jest.fn(() => ({ parent: jest.fn(() => mockTr) })),
796
- closest: jest.fn(() => ({
797
- find: jest.fn(() => ({ length: 0 }))
798
- }))
799
- };
800
-
801
- let clickHandler = null;
802
- mockValidateTd.find = jest.fn((selector) => {
803
- if (selector === 'a.import_validate_line') {
804
- return {
805
- click: jest.fn((handler) => {
806
- clickHandler = handler;
807
- return mockValidateTd;
808
- })
809
- };
810
- }
811
- if (selector === 'a.import_edit_line') {
812
- return { click: jest.fn().mockReturnThis() };
813
- }
814
- if (selector === 'td input[type="text"]') {
815
- return { length: 0 };
816
- }
817
- return mockValidateTd;
818
- });
819
- global.$ = jest.fn((selector) => {
820
- if (typeof selector === 'string' && selector.includes('<a')) {
821
- return mockValidateTd;
822
- }
823
- if (selector === mockTdWithInput) return mockTdWithInput;
824
- return { parent: jest.fn(() => ({ parent: jest.fn(() => mockTr) })) };
825
- });
826
-
827
- mockFormMatching.find = jest.fn(() => ({
828
- prop: jest.fn().mockReturnThis()
829
- }));
830
-
831
- ImportFromCsv.initValidateLine(mockFormMatching, mockValidateTd);
832
-
833
- // Trigger the click
834
- clickHandler();
835
-
836
- expect(mockTdWithInput.html).toHaveBeenCalledWith('Updated Value');
519
+ test('should set up validate link in td', () => {
520
+ const { editTd, fm } = setupTableWithRow();
521
+
522
+ ImportFromCsv.initValidateLine(fm, editTd);
523
+
524
+ expect(editTd.querySelector('a.import_validate_line')).not.toBeNull();
525
+ });
526
+
527
+ test('should convert inputs back to text on click', () => {
528
+ const { editTd, fm } = setupTableWithRow(['John']);
529
+
530
+ // First enter edit mode
531
+ ImportFromCsv.initEditLink(fm, editTd);
532
+ editTd.querySelector('a.import_edit_line').click();
533
+
534
+ // Now validate
535
+ editTd.querySelector('a.import_validate_line').click();
536
+
537
+ const dataCell = document.querySelector('td[data-key="0"]');
538
+ expect(dataCell.querySelector('input')).toBeNull();
539
+ expect(dataCell.textContent).toBe('John');
540
+ });
541
+
542
+ test('should use input value when restoring cell content', () => {
543
+ const { editTd, fm } = setupTableWithRow(['John']);
544
+
545
+ ImportFromCsv.initEditLink(fm, editTd);
546
+ editTd.querySelector('a.import_edit_line').click();
547
+
548
+ // Change input value
549
+ document.querySelector('td[data-key="0"] input').value = 'Jane';
550
+
551
+ editTd.querySelector('a.import_validate_line').click();
552
+
553
+ expect(document.querySelector('td[data-key="0"]').textContent).toBe('Jane');
554
+ });
555
+
556
+ test('should re-enable submit button when no inputs remain', () => {
557
+ const { editTd, fm } = setupTableWithRow(['John']);
558
+ fm.querySelector('button[type="submit"]').disabled = true;
559
+
560
+ ImportFromCsv.initEditLink(fm, editTd);
561
+ editTd.querySelector('a.import_edit_line').click();
562
+ editTd.querySelector('a.import_validate_line').click();
563
+
564
+ expect(fm.querySelector('button[type="submit"]').disabled).toBe(false);
565
+ });
566
+
567
+ test('should replace validate link with edit link after click', () => {
568
+ const { editTd, fm } = setupTableWithRow(['John']);
569
+
570
+ ImportFromCsv.initEditLink(fm, editTd);
571
+ editTd.querySelector('a.import_edit_line').click();
572
+ editTd.querySelector('a.import_validate_line').click();
573
+
574
+ expect(editTd.querySelector('a.import_validate_line')).toBeNull();
575
+ expect(editTd.querySelector('a.import_edit_line')).not.toBeNull();
837
576
  });
838
577
  });
839
578
  });