@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.
- package/count_down.js +46 -48
- package/date_time.js +0 -1
- package/details_sub_array.js +65 -50
- package/flash_message.js +10 -6
- package/form_date.js +144 -153
- package/form_helper.js +283 -232
- package/google_charts.js +154 -144
- package/google_maps.js +1 -1
- package/import_from_csv.js +198 -160
- package/multi_files_input.js +44 -35
- package/multiple_action_in_table.js +123 -109
- package/package.json +1 -1
- package/paging.js +103 -84
- package/select_all.js +65 -70
- package/sortable_list.js +12 -13
- package/tests/count_down.test.js +131 -352
- package/tests/details_sub_array.test.js +213 -258
- package/tests/flash_message.test.js +21 -153
- package/tests/form_date.test.js +287 -961
- package/tests/form_helper.test.js +553 -673
- package/tests/google_charts.test.js +338 -339
- package/tests/google_maps.test.js +3 -15
- package/tests/import_from_csv.test.js +421 -640
- package/tests/multi_files_input.test.js +305 -737
- package/tests/multiple_action_in_table.test.js +442 -429
- package/tests/open_street_map.test.js +15 -23
- package/tests/paging.test.js +344 -475
- package/tests/select_all.test.js +232 -318
- package/tests/sortable_list.test.js +176 -500
- package/tests/user.test.js +163 -54
- package/user.js +35 -38
|
@@ -3,87 +3,105 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const { ImportFromCsv } = require('../import_from_csv');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
prop: el.prop
|
|
136
|
-
}));
|
|
152
|
+
expect(result).toEqual({ name: '0', email: '1' });
|
|
153
|
+
});
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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(
|
|
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
|
|
160
|
-
|
|
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(
|
|
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
|
|
192
|
-
|
|
193
|
-
const mockTr = {
|
|
194
|
-
addClass: jest.fn().mockReturnThis()
|
|
195
|
-
};
|
|
193
|
+
const divResult = setupDivResult([['a'], ['b']]);
|
|
196
194
|
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
198
|
+
const tr2 = divResult.querySelector('table tr[data-line="2"]');
|
|
199
|
+
expect(tr2.classList.contains('danger')).toBe(true);
|
|
205
200
|
|
|
206
|
-
|
|
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
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
230
|
+
const divResult = setupDivResult([['John']]);
|
|
231
|
+
divResult.querySelector('input.import_line_checkbox').checked = false;
|
|
279
232
|
|
|
280
|
-
const
|
|
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
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
349
|
-
|
|
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
|
-
|
|
363
|
-
addClass: jest.fn().mockReturnThis()
|
|
364
|
-
};
|
|
274
|
+
ImportFromCsv.displayFormMatching(formMatching, importColumns, header, true);
|
|
365
275
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
283
|
+
test('should populate options from header', () => {
|
|
284
|
+
const formMatching = setupFormMatching();
|
|
285
|
+
const header = ['Col A', 'Col B'];
|
|
377
286
|
|
|
378
|
-
ImportFromCsv.displayFormMatching(
|
|
287
|
+
ImportFromCsv.displayFormMatching(formMatching, { field: 'Field' }, header, true);
|
|
379
288
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
expect(
|
|
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
|
|
296
|
+
test('should use index as option value when hasHeader is false', () => {
|
|
297
|
+
const formMatching = setupFormMatching();
|
|
387
298
|
const header = ['Col1', 'Col2'];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
458
|
-
|
|
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
|
-
|
|
471
|
-
mockDivResult.removeClass = jest.fn().mockReturnThis();
|
|
366
|
+
ImportFromCsv.displayData(divResult, [['John']], null, formMatching);
|
|
472
367
|
|
|
473
|
-
|
|
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
|
|
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
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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(
|
|
389
|
+
ImportFromCsv.displayData(divResult, [['A'], ['B'], ['C']], null, formMatching);
|
|
496
390
|
|
|
497
|
-
|
|
498
|
-
expect(
|
|
499
|
-
expect(
|
|
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
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
|
543
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
556
|
-
|
|
447
|
+
const data = [{ name: 'John', email: 'john@example.com' }];
|
|
448
|
+
const header = ['name', 'email'];
|
|
557
449
|
|
|
558
|
-
ImportFromCsv.displayData(
|
|
450
|
+
ImportFromCsv.displayData(divResult, data, header, formMatching);
|
|
559
451
|
|
|
560
|
-
|
|
561
|
-
expect(
|
|
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
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
ImportFromCsv.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
});
|