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