@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.
- package/details_sub_array.js +45 -41
- package/form_helper.js +283 -232
- package/google_charts.js +154 -144
- package/google_maps.js +1 -1
- package/import_from_csv.js +166 -157
- package/multi_files_input.js +42 -34
- package/multiple_action_in_table.js +115 -105
- package/package.json +1 -1
- package/paging.js +103 -84
- package/select_all.js +65 -70
- package/sortable_list.js +12 -13
- package/tests/details_sub_array.test.js +211 -239
- 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 +391 -652
- package/tests/multi_files_input.test.js +292 -722
- package/tests/multiple_action_in_table.test.js +439 -417
- 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
|
@@ -4,277 +4,239 @@
|
|
|
4
4
|
require('../array'); // For removeEmptyValues method
|
|
5
5
|
const { FormHelper, ArrayField, EditValue } = require('../form_helper');
|
|
6
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
|
-
0: document.createElement('form')
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
mockButton = {
|
|
45
|
-
...mockInput,
|
|
46
|
-
attr: jest.fn((key, value) => {
|
|
47
|
-
if (key === 'disabled' && value === undefined) return false;
|
|
48
|
-
return mockButton;
|
|
49
|
-
})
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
mockForm = {
|
|
53
|
-
...mockInput,
|
|
54
|
-
find: jest.fn((selector) => {
|
|
55
|
-
if (selector.includes('button[name="validate"]')) return mockButton;
|
|
56
|
-
if (selector.includes('input[name]:not')) return mockInput;
|
|
57
|
-
if (selector.includes('select.selectpicker')) return mockInput;
|
|
58
|
-
if (selector.includes('div.form_errors')) return mockInput;
|
|
59
|
-
if (selector.includes('.form_errors_content')) return { length: 0 };
|
|
60
|
-
if (selector.includes('.modal-body')) return { length: 0 };
|
|
61
|
-
if (selector.includes('.form-group:first')) return mockInput;
|
|
62
|
-
return mockInput;
|
|
63
|
-
})
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
global.$ = jest.fn((selector) => {
|
|
67
|
-
if (typeof selector === 'function') {
|
|
68
|
-
// Document ready
|
|
69
|
-
return mockInput;
|
|
70
|
-
}
|
|
71
|
-
if (selector === mockButton || selector === mockInput || selector === mockForm) {
|
|
72
|
-
return selector;
|
|
73
|
-
}
|
|
74
|
-
return mockInput;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
global.$.each = jest.fn((obj, callback) => {
|
|
78
|
-
if (Array.isArray(obj)) {
|
|
79
|
-
obj.forEach((item, idx) => callback(idx, item));
|
|
80
|
-
} else {
|
|
81
|
-
Object.keys(obj).forEach(key => callback(key, obj[key]));
|
|
82
|
-
}
|
|
83
|
-
});
|
|
7
|
+
// Helper functions
|
|
8
|
+
function setupForm() {
|
|
9
|
+
const form = document.createElement('form');
|
|
10
|
+
document.body.appendChild(form);
|
|
11
|
+
return form;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function addButton(form, name = 'validate', label = 'Submit') {
|
|
15
|
+
const btn = document.createElement('button');
|
|
16
|
+
btn.type = 'submit';
|
|
17
|
+
btn.name = name;
|
|
18
|
+
btn.textContent = label;
|
|
19
|
+
form.appendChild(btn);
|
|
20
|
+
return btn;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function addInput(form, name, type = 'text', value = '') {
|
|
24
|
+
const input = document.createElement('input');
|
|
25
|
+
input.name = name;
|
|
26
|
+
input.type = type;
|
|
27
|
+
input.value = value;
|
|
28
|
+
form.appendChild(input);
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addSelect(form, name, options = []) {
|
|
33
|
+
const select = document.createElement('select');
|
|
34
|
+
select.name = name;
|
|
35
|
+
options.forEach(({ value, text, disabled }) => {
|
|
36
|
+
const opt = document.createElement('option');
|
|
37
|
+
opt.value = value;
|
|
38
|
+
opt.textContent = text;
|
|
39
|
+
if (disabled) opt.disabled = true;
|
|
40
|
+
select.appendChild(opt);
|
|
84
41
|
});
|
|
42
|
+
form.appendChild(select);
|
|
43
|
+
return select;
|
|
44
|
+
}
|
|
85
45
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
document.body.innerHTML = '';
|
|
48
|
+
jest.clearAllMocks();
|
|
49
|
+
});
|
|
90
50
|
|
|
51
|
+
describe('FormHelper', () => {
|
|
91
52
|
describe('init', () => {
|
|
92
|
-
test('should initialize form with default submit button', () => {
|
|
53
|
+
test('should initialize form with default submit button and return form', () => {
|
|
54
|
+
const form = setupForm();
|
|
55
|
+
addButton(form, 'validate', 'Submit');
|
|
93
56
|
const onSubmitCallback = jest.fn();
|
|
94
57
|
|
|
95
|
-
const result = FormHelper.init(
|
|
58
|
+
const result = FormHelper.init(form, onSubmitCallback);
|
|
96
59
|
|
|
97
|
-
expect(
|
|
98
|
-
expect(mockButton.off).toHaveBeenCalledWith('click');
|
|
99
|
-
expect(mockButton.click).toHaveBeenCalled();
|
|
100
|
-
expect(result).toBe(mockForm);
|
|
60
|
+
expect(result).toBe(form);
|
|
101
61
|
});
|
|
102
62
|
|
|
103
|
-
test('should
|
|
63
|
+
test('should call callback on button click', () => {
|
|
64
|
+
const form = setupForm();
|
|
65
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
104
66
|
const onSubmitCallback = jest.fn();
|
|
105
|
-
const customButton = { ...mockButton };
|
|
106
67
|
|
|
107
|
-
FormHelper.init(
|
|
68
|
+
FormHelper.init(form, onSubmitCallback);
|
|
108
69
|
|
|
109
|
-
|
|
110
|
-
|
|
70
|
+
btn.click();
|
|
71
|
+
|
|
72
|
+
expect(onSubmitCallback).toHaveBeenCalledWith(form, btn);
|
|
111
73
|
});
|
|
112
74
|
|
|
113
|
-
test('should
|
|
75
|
+
test('should use custom submit button when provided', () => {
|
|
76
|
+
const form = setupForm();
|
|
77
|
+
addInput(form, 'name');
|
|
78
|
+
const customBtn = document.createElement('button');
|
|
79
|
+
customBtn.type = 'button';
|
|
80
|
+
customBtn.name = 'custom';
|
|
81
|
+
customBtn.textContent = 'Custom';
|
|
82
|
+
document.body.appendChild(customBtn);
|
|
114
83
|
const onSubmitCallback = jest.fn();
|
|
115
|
-
let clickHandler;
|
|
116
|
-
|
|
117
|
-
mockButton.click.mockImplementation((handler) => {
|
|
118
|
-
clickHandler = handler;
|
|
119
|
-
return mockButton;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
FormHelper.init(mockForm, onSubmitCallback);
|
|
123
84
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
clickHandler.call(mockButton, mockEvent);
|
|
85
|
+
FormHelper.init(form, onSubmitCallback, customBtn);
|
|
86
|
+
customBtn.click();
|
|
127
87
|
|
|
128
|
-
expect(
|
|
129
|
-
expect(onSubmitCallback).toHaveBeenCalledWith(mockForm, mockButton);
|
|
88
|
+
expect(onSubmitCallback).toHaveBeenCalledWith(form, customBtn);
|
|
130
89
|
});
|
|
131
90
|
|
|
132
|
-
test('should
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
mockButton.click.mockImplementation((handler) => {
|
|
138
|
-
clickHandler = handler;
|
|
139
|
-
return mockButton;
|
|
140
|
-
});
|
|
91
|
+
test('should call buttonLoader with loading on submit', () => {
|
|
92
|
+
const form = setupForm();
|
|
93
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
94
|
+
const loaderSpy = jest.spyOn(FormHelper, 'buttonLoader');
|
|
141
95
|
|
|
142
|
-
FormHelper.init(
|
|
143
|
-
|
|
96
|
+
FormHelper.init(form, jest.fn());
|
|
97
|
+
btn.click();
|
|
144
98
|
|
|
145
|
-
expect(
|
|
99
|
+
expect(loaderSpy).toHaveBeenCalledWith(btn, 'loading');
|
|
100
|
+
loaderSpy.mockRestore();
|
|
146
101
|
});
|
|
147
102
|
});
|
|
148
103
|
|
|
149
104
|
describe('reset', () => {
|
|
150
|
-
test('should reset
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
105
|
+
test('should reset text input values', () => {
|
|
106
|
+
const form = setupForm();
|
|
107
|
+
addButton(form, 'validate', 'Submit');
|
|
108
|
+
const input = addInput(form, 'name', 'text', 'hello');
|
|
155
109
|
|
|
156
|
-
const result = FormHelper.reset(
|
|
110
|
+
const result = FormHelper.reset(form);
|
|
157
111
|
|
|
158
|
-
expect(
|
|
159
|
-
expect(
|
|
160
|
-
expect(result).toBe(mockForm);
|
|
112
|
+
expect(input.value).toBe('');
|
|
113
|
+
expect(result).toBe(form);
|
|
161
114
|
});
|
|
162
115
|
|
|
163
|
-
test('should reset
|
|
164
|
-
const
|
|
116
|
+
test('should reset textarea values', () => {
|
|
117
|
+
const form = setupForm();
|
|
118
|
+
addButton(form, 'validate', 'Submit');
|
|
119
|
+
const textarea = document.createElement('textarea');
|
|
120
|
+
textarea.name = 'bio';
|
|
121
|
+
textarea.value = 'some text';
|
|
122
|
+
form.appendChild(textarea);
|
|
165
123
|
|
|
166
|
-
FormHelper.reset(
|
|
124
|
+
FormHelper.reset(form);
|
|
167
125
|
|
|
168
|
-
expect(
|
|
126
|
+
expect(textarea.value).toBe('');
|
|
169
127
|
});
|
|
170
|
-
});
|
|
171
128
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
129
|
+
test('should not reset checkbox/radio inputs', () => {
|
|
130
|
+
const form = setupForm();
|
|
131
|
+
addButton(form, 'validate', 'Submit');
|
|
132
|
+
const checkbox = addInput(form, 'agree', 'checkbox');
|
|
133
|
+
checkbox.checked = true;
|
|
175
134
|
|
|
176
|
-
FormHelper.
|
|
135
|
+
FormHelper.reset(form);
|
|
177
136
|
|
|
178
|
-
//
|
|
179
|
-
|
|
137
|
+
// checkboxes should not be cleared by reset (they match :not([type="checkbox"]) exclusion)
|
|
138
|
+
// The selector explicitly excludes them
|
|
139
|
+
expect(checkbox.checked).toBe(true);
|
|
180
140
|
});
|
|
181
141
|
|
|
182
|
-
test('should
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
mockForm.find = jest.fn((selector) => {
|
|
190
|
-
if (selector.includes('employees_display_type')) return mockInput;
|
|
191
|
-
if (selector.includes('[]')) return selectMock;
|
|
192
|
-
return mockInput;
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const data = { tags: ['tag1', 'tag2'] };
|
|
142
|
+
test('should reset with custom submit button', () => {
|
|
143
|
+
const form = setupForm();
|
|
144
|
+
const customBtn = document.createElement('button');
|
|
145
|
+
customBtn.type = 'button';
|
|
146
|
+
customBtn.innerHTML = 'Save';
|
|
147
|
+
document.body.appendChild(customBtn);
|
|
148
|
+
const input = addInput(form, 'name', 'text', 'hello');
|
|
196
149
|
|
|
197
|
-
FormHelper.
|
|
150
|
+
FormHelper.reset(form, customBtn);
|
|
198
151
|
|
|
199
|
-
expect(
|
|
200
|
-
expect(selectMock.data).toHaveBeenCalledWith('default_id', 'tag1,tag2');
|
|
152
|
+
expect(input.value).toBe('');
|
|
201
153
|
});
|
|
154
|
+
});
|
|
202
155
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const data = { gender: 'male' };
|
|
156
|
+
describe('populateForm', () => {
|
|
157
|
+
test('should skip null values', () => {
|
|
158
|
+
const form = setupForm();
|
|
159
|
+
const input = addInput(form, 'field1', 'text', 'original');
|
|
210
160
|
|
|
211
|
-
FormHelper.populateForm(
|
|
161
|
+
FormHelper.populateForm(form, { field1: null });
|
|
212
162
|
|
|
213
|
-
expect(
|
|
163
|
+
expect(input.value).toBe('original');
|
|
214
164
|
});
|
|
215
165
|
|
|
216
|
-
test('should
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return mockInput;
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const data = { terms: 'accepted' };
|
|
166
|
+
test('should set text input value', () => {
|
|
167
|
+
const form = setupForm();
|
|
168
|
+
const input = addInput(form, 'name', 'text', '');
|
|
223
169
|
|
|
224
|
-
FormHelper.populateForm(
|
|
170
|
+
FormHelper.populateForm(form, { name: 'John' });
|
|
225
171
|
|
|
226
|
-
expect(
|
|
172
|
+
expect(input.value).toBe('John');
|
|
227
173
|
});
|
|
228
174
|
|
|
229
|
-
test('should handle
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
175
|
+
test('should handle radio button values', () => {
|
|
176
|
+
const form = setupForm();
|
|
177
|
+
const radio1 = addInput(form, 'gender', 'radio', 'male');
|
|
178
|
+
const radio2 = addInput(form, 'gender', 'radio', 'female');
|
|
233
179
|
|
|
234
|
-
FormHelper.populateForm(
|
|
180
|
+
FormHelper.populateForm(form, { gender: 'female' });
|
|
235
181
|
|
|
236
|
-
expect(
|
|
182
|
+
expect(radio1.checked).toBe(false);
|
|
183
|
+
expect(radio2.checked).toBe(true);
|
|
237
184
|
});
|
|
238
|
-
});
|
|
239
185
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return mockInput;
|
|
245
|
-
});
|
|
186
|
+
test('should handle checkbox values', () => {
|
|
187
|
+
const form = setupForm();
|
|
188
|
+
const cb1 = addInput(form, 'terms', 'checkbox', 'accepted');
|
|
189
|
+
const cb2 = addInput(form, 'terms', 'checkbox', 'declined');
|
|
246
190
|
|
|
247
|
-
|
|
191
|
+
FormHelper.populateForm(form, { terms: 'accepted' });
|
|
248
192
|
|
|
249
|
-
expect(
|
|
250
|
-
expect(
|
|
251
|
-
expect(result).toBe(mockForm);
|
|
193
|
+
expect(cb1.checked).toBe(true);
|
|
194
|
+
expect(cb2.checked).toBe(false);
|
|
252
195
|
});
|
|
253
196
|
|
|
254
|
-
test('should
|
|
255
|
-
const
|
|
256
|
-
|
|
197
|
+
test('should handle object values as array select', () => {
|
|
198
|
+
const form = setupForm();
|
|
199
|
+
const select = document.createElement('select');
|
|
200
|
+
select.name = 'tags[]';
|
|
201
|
+
select.multiple = true;
|
|
202
|
+
['tag1', 'tag2', 'tag3'].forEach(v => {
|
|
203
|
+
const opt = document.createElement('option');
|
|
204
|
+
opt.value = v;
|
|
205
|
+
opt.textContent = v;
|
|
206
|
+
select.appendChild(opt);
|
|
207
|
+
});
|
|
208
|
+
form.appendChild(select);
|
|
257
209
|
|
|
258
|
-
FormHelper.
|
|
210
|
+
FormHelper.populateForm(form, { tags: ['tag1', 'tag3'] });
|
|
259
211
|
|
|
260
|
-
expect(
|
|
212
|
+
expect(select.querySelector('option[value="tag1"]').selected).toBe(true);
|
|
213
|
+
expect(select.querySelector('option[value="tag2"]').selected).toBe(false);
|
|
214
|
+
expect(select.querySelector('option[value="tag3"]').selected).toBe(true);
|
|
215
|
+
expect(select.dataset.default_id).toBe('tag1,tag3');
|
|
261
216
|
});
|
|
262
217
|
});
|
|
263
218
|
|
|
264
219
|
describe('getFormData', () => {
|
|
265
|
-
test('should return FormData from form element', () => {
|
|
266
|
-
const
|
|
220
|
+
test('should return FormData from native form element', () => {
|
|
221
|
+
const form = setupForm();
|
|
222
|
+
addInput(form, 'name', 'text', 'John');
|
|
223
|
+
|
|
224
|
+
const result = FormHelper.getFormData(form);
|
|
267
225
|
|
|
268
226
|
expect(result).toBeInstanceOf(FormData);
|
|
269
227
|
});
|
|
270
228
|
});
|
|
271
229
|
|
|
272
230
|
describe('getFormDataQueryString', () => {
|
|
273
|
-
test('should return
|
|
274
|
-
const
|
|
231
|
+
test('should return query string from form data', () => {
|
|
232
|
+
const form = setupForm();
|
|
233
|
+
addInput(form, 'name', 'text', 'John');
|
|
234
|
+
addInput(form, 'age', 'text', '30');
|
|
275
235
|
|
|
276
|
-
|
|
277
|
-
|
|
236
|
+
const result = FormHelper.getFormDataQueryString(form);
|
|
237
|
+
|
|
238
|
+
expect(result).toContain('name=John');
|
|
239
|
+
expect(result).toContain('age=30');
|
|
278
240
|
});
|
|
279
241
|
});
|
|
280
242
|
|
|
@@ -287,338 +249,491 @@ describe('FormHelper', () => {
|
|
|
287
249
|
jest.useRealTimers();
|
|
288
250
|
});
|
|
289
251
|
|
|
290
|
-
test('should
|
|
252
|
+
test('should call callback after keyup + timeout', () => {
|
|
253
|
+
const form = setupForm();
|
|
254
|
+
const input = addInput(form, 'search');
|
|
291
255
|
const callback = jest.fn();
|
|
292
|
-
let keyupHandler;
|
|
293
256
|
|
|
294
|
-
|
|
295
|
-
if (event === 'keyup') keyupHandler = handler;
|
|
296
|
-
return mockInput;
|
|
297
|
-
});
|
|
257
|
+
FormHelper.setOnInputChange(input, callback, 500);
|
|
298
258
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
expect(mockInput.on).toHaveBeenCalledWith('keyup', expect.any(Function));
|
|
302
|
-
|
|
303
|
-
keyupHandler();
|
|
259
|
+
input.dispatchEvent(new KeyboardEvent('keyup'));
|
|
304
260
|
jest.advanceTimersByTime(500);
|
|
305
261
|
|
|
306
262
|
expect(callback).toHaveBeenCalled();
|
|
307
263
|
});
|
|
308
264
|
|
|
309
|
-
test('should clear timeout on keydown', () => {
|
|
265
|
+
test('should clear timeout on keydown after keyup', () => {
|
|
266
|
+
const form = setupForm();
|
|
267
|
+
const input = addInput(form, 'search');
|
|
310
268
|
const callback = jest.fn();
|
|
311
|
-
let keyupHandler, keydownHandler;
|
|
312
|
-
|
|
313
|
-
mockInput.on.mockImplementation((event, handler) => {
|
|
314
|
-
if (event === 'keyup') keyupHandler = handler;
|
|
315
|
-
if (event === 'keydown') keydownHandler = handler;
|
|
316
|
-
return mockInput;
|
|
317
|
-
});
|
|
318
269
|
|
|
319
|
-
FormHelper.setOnInputChange(
|
|
270
|
+
FormHelper.setOnInputChange(input, callback, 500);
|
|
320
271
|
|
|
321
|
-
|
|
322
|
-
|
|
272
|
+
input.dispatchEvent(new KeyboardEvent('keyup'));
|
|
273
|
+
input.dispatchEvent(new KeyboardEvent('keydown'));
|
|
323
274
|
jest.advanceTimersByTime(500);
|
|
324
275
|
|
|
325
276
|
expect(callback).not.toHaveBeenCalled();
|
|
326
277
|
});
|
|
327
278
|
|
|
328
|
-
test('should call callback on focusout', () => {
|
|
279
|
+
test('should call callback immediately on focusout', () => {
|
|
280
|
+
const form = setupForm();
|
|
281
|
+
const input = addInput(form, 'search');
|
|
329
282
|
const callback = jest.fn();
|
|
330
|
-
let focusoutHandler;
|
|
331
283
|
|
|
332
|
-
|
|
333
|
-
if (event === 'focusout') focusoutHandler = handler;
|
|
334
|
-
return mockInput;
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
FormHelper.setOnInputChange(mockInput, callback);
|
|
284
|
+
FormHelper.setOnInputChange(input, callback);
|
|
338
285
|
|
|
339
|
-
|
|
286
|
+
input.dispatchEvent(new Event('focusout'));
|
|
340
287
|
|
|
341
288
|
expect(callback).toHaveBeenCalled();
|
|
342
289
|
});
|
|
343
290
|
|
|
344
291
|
test('should use default interval of 700ms', () => {
|
|
292
|
+
const form = setupForm();
|
|
293
|
+
const input = addInput(form, 'search');
|
|
345
294
|
const callback = jest.fn();
|
|
346
|
-
let keyupHandler;
|
|
347
|
-
|
|
348
|
-
mockInput.on.mockImplementation((event, handler) => {
|
|
349
|
-
if (event === 'keyup') keyupHandler = handler;
|
|
350
|
-
return mockInput;
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
FormHelper.setOnInputChange(mockInput, callback);
|
|
354
295
|
|
|
355
|
-
|
|
356
|
-
jest.advanceTimersByTime(700);
|
|
296
|
+
FormHelper.setOnInputChange(input, callback);
|
|
357
297
|
|
|
298
|
+
input.dispatchEvent(new KeyboardEvent('keyup'));
|
|
299
|
+
jest.advanceTimersByTime(699);
|
|
300
|
+
expect(callback).not.toHaveBeenCalled();
|
|
301
|
+
jest.advanceTimersByTime(1);
|
|
358
302
|
expect(callback).toHaveBeenCalled();
|
|
359
303
|
});
|
|
360
304
|
});
|
|
361
305
|
|
|
362
306
|
describe('Select methods', () => {
|
|
363
|
-
test('resetSelectOption should reset
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
307
|
+
test('resetSelectOption should reset all options', () => {
|
|
308
|
+
const form = setupForm();
|
|
309
|
+
const select = addSelect(form, 'category', [
|
|
310
|
+
{ value: '', text: '-- choose --' },
|
|
311
|
+
{ value: '1', text: 'One' },
|
|
312
|
+
{ value: '2', text: 'Two', disabled: true },
|
|
313
|
+
]);
|
|
314
|
+
select.querySelector('option[value="1"]').selected = true;
|
|
315
|
+
select.querySelector('option[value="2"]').disabled = true;
|
|
367
316
|
|
|
368
|
-
|
|
317
|
+
FormHelper.resetSelectOption(form, 'category');
|
|
369
318
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
expect(
|
|
373
|
-
expect(optionMock.prop).toHaveBeenCalledWith('disabled', false);
|
|
374
|
-
expect(optionMock.prop).toHaveBeenCalledWith('selected', false);
|
|
319
|
+
expect(select.querySelector('option[value="1"]').disabled).toBe(false);
|
|
320
|
+
expect(select.querySelector('option[value="1"]').selected).toBe(false);
|
|
321
|
+
expect(select.querySelector('option[value="2"]').disabled).toBe(false);
|
|
375
322
|
});
|
|
376
323
|
|
|
377
|
-
test('setSelectedSelectOption should select option', () => {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
324
|
+
test('setSelectedSelectOption should select specific option', () => {
|
|
325
|
+
const form = setupForm();
|
|
326
|
+
addSelect(form, 'category', [
|
|
327
|
+
{ value: '1', text: 'One' },
|
|
328
|
+
{ value: '5', text: 'Five' },
|
|
329
|
+
]);
|
|
383
330
|
|
|
384
|
-
FormHelper.setSelectedSelectOption(
|
|
331
|
+
FormHelper.setSelectedSelectOption(form, 'category', '5');
|
|
385
332
|
|
|
386
|
-
expect(
|
|
387
|
-
expect(optionMock.prop).toHaveBeenCalledWith('selected', true);
|
|
333
|
+
expect(form.querySelector('select[name="category"] option[value="5"]').selected).toBe(true);
|
|
388
334
|
});
|
|
389
335
|
|
|
390
336
|
test('setSelectedSelectOptions should select multiple options', () => {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
337
|
+
const form = setupForm();
|
|
338
|
+
const select = document.createElement('select');
|
|
339
|
+
select.name = 'tags';
|
|
340
|
+
select.multiple = true;
|
|
341
|
+
['1', '2', '3'].forEach(v => {
|
|
342
|
+
const opt = document.createElement('option');
|
|
343
|
+
opt.value = v;
|
|
344
|
+
select.appendChild(opt);
|
|
345
|
+
});
|
|
346
|
+
form.appendChild(select);
|
|
396
347
|
|
|
397
|
-
FormHelper.setSelectedSelectOptions(
|
|
348
|
+
FormHelper.setSelectedSelectOptions(form, 'tags', ['1', '3']);
|
|
398
349
|
|
|
399
|
-
expect(
|
|
400
|
-
expect(
|
|
350
|
+
expect(select.querySelector('option[value="1"]').selected).toBe(true);
|
|
351
|
+
expect(select.querySelector('option[value="2"]').selected).toBe(false);
|
|
352
|
+
expect(select.querySelector('option[value="3"]').selected).toBe(true);
|
|
401
353
|
});
|
|
402
354
|
|
|
403
|
-
test('disableSelectOption should disable option', () => {
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
355
|
+
test('disableSelectOption should disable specific option', () => {
|
|
356
|
+
const form = setupForm();
|
|
357
|
+
addSelect(form, 'category', [
|
|
358
|
+
{ value: '1', text: 'One' },
|
|
359
|
+
{ value: '5', text: 'Five' },
|
|
360
|
+
]);
|
|
407
361
|
|
|
408
|
-
|
|
362
|
+
FormHelper.disableSelectOption(form, 'category', '5');
|
|
409
363
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expect(mockForm.find).toHaveBeenCalledWith('select[name="category"] option[value="5"]');
|
|
413
|
-
expect(optionMock.prop).toHaveBeenCalledWith('disabled', true);
|
|
364
|
+
expect(form.querySelector('select[name="category"] option[value="5"]').disabled).toBe(true);
|
|
365
|
+
expect(form.querySelector('select[name="category"] option[value="1"]').disabled).toBe(false);
|
|
414
366
|
});
|
|
415
367
|
|
|
416
368
|
test('disableSelectOptions should disable multiple options', () => {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
369
|
+
const form = setupForm();
|
|
370
|
+
addSelect(form, 'category', [
|
|
371
|
+
{ value: '1', text: 'One' },
|
|
372
|
+
{ value: '2', text: 'Two' },
|
|
373
|
+
{ value: '3', text: 'Three' },
|
|
374
|
+
]);
|
|
420
375
|
|
|
421
|
-
|
|
376
|
+
FormHelper.disableSelectOptions(form, 'category', ['1', '2']);
|
|
422
377
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
expect(
|
|
426
|
-
expect(optionMock.prop).toHaveBeenCalledWith('disabled', true);
|
|
378
|
+
expect(form.querySelector('option[value="1"]').disabled).toBe(true);
|
|
379
|
+
expect(form.querySelector('option[value="2"]').disabled).toBe(true);
|
|
380
|
+
expect(form.querySelector('option[value="3"]').disabled).toBe(false);
|
|
427
381
|
});
|
|
428
382
|
|
|
429
383
|
test('countSelectOptions should count non-disabled options', () => {
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
384
|
+
const form = setupForm();
|
|
385
|
+
addSelect(form, 'category', [
|
|
386
|
+
{ value: '1', text: 'One' },
|
|
387
|
+
{ value: '2', text: 'Two', disabled: true },
|
|
388
|
+
{ value: '3', text: 'Three' },
|
|
389
|
+
{ value: '4', text: 'Four', disabled: true },
|
|
390
|
+
{ value: '5', text: 'Five' },
|
|
391
|
+
]);
|
|
433
392
|
|
|
434
|
-
|
|
393
|
+
const count = FormHelper.countSelectOptions(form, 'category');
|
|
435
394
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
expect(mockForm.find).toHaveBeenCalledWith('select[name="category"] option:not([disabled])');
|
|
439
|
-
expect(count).toBe(5);
|
|
395
|
+
expect(count).toBe(3);
|
|
440
396
|
});
|
|
441
397
|
});
|
|
442
398
|
|
|
443
399
|
describe('Checkbox methods', () => {
|
|
444
400
|
test('getCheckedValues should return checked values', () => {
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
];
|
|
452
|
-
return { get: () => results.filter(r => r !== undefined) };
|
|
453
|
-
})
|
|
454
|
-
};
|
|
401
|
+
const form = setupForm();
|
|
402
|
+
const cb1 = addInput(form, 'opt', 'checkbox', 'option1');
|
|
403
|
+
const cb2 = addInput(form, 'opt', 'checkbox', 'option2');
|
|
404
|
+
const cb3 = addInput(form, 'opt', 'checkbox', 'option3');
|
|
405
|
+
cb1.checked = true;
|
|
406
|
+
cb3.checked = true;
|
|
455
407
|
|
|
456
|
-
const result = FormHelper.getCheckedValues(
|
|
408
|
+
const result = FormHelper.getCheckedValues(form.querySelectorAll('[name="opt"]'));
|
|
457
409
|
|
|
458
410
|
expect(result).toEqual(['option1', 'option3']);
|
|
459
411
|
});
|
|
460
412
|
|
|
461
413
|
test('setCheckedValues should check specified values', () => {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
414
|
+
const form = setupForm();
|
|
415
|
+
const container = document.createElement('div');
|
|
416
|
+
form.appendChild(container);
|
|
417
|
+
const cb1 = document.createElement('input');
|
|
418
|
+
cb1.type = 'checkbox'; cb1.value = 'value1';
|
|
419
|
+
const cb2 = document.createElement('input');
|
|
420
|
+
cb2.type = 'checkbox'; cb2.value = 'value2';
|
|
421
|
+
const cb3 = document.createElement('input');
|
|
422
|
+
cb3.type = 'checkbox'; cb3.value = 'value3';
|
|
423
|
+
container.appendChild(cb1);
|
|
424
|
+
container.appendChild(cb2);
|
|
425
|
+
container.appendChild(cb3);
|
|
426
|
+
|
|
427
|
+
FormHelper.setCheckedValues(container.querySelectorAll('input'), ['value1', 'value3']);
|
|
428
|
+
|
|
429
|
+
expect(cb1.checked).toBe(true);
|
|
430
|
+
expect(cb2.checked).toBe(false);
|
|
431
|
+
expect(cb3.checked).toBe(true);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('getInputListValues should return non-empty input values', () => {
|
|
435
|
+
const form = setupForm();
|
|
436
|
+
const i1 = addInput(form, 'item', 'text', 'value1');
|
|
437
|
+
const i2 = addInput(form, 'item', 'text', 'value2');
|
|
438
|
+
const i3 = addInput(form, 'item', 'text', '');
|
|
439
|
+
|
|
440
|
+
const result = FormHelper.getInputListValues(form.querySelectorAll('[name="item"]'));
|
|
466
441
|
|
|
467
|
-
|
|
442
|
+
expect(result).toEqual(['value1', 'value2']);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe('initTypeFields', () => {
|
|
447
|
+
test('should wrap password fields with input-group.password', () => {
|
|
448
|
+
const form = setupForm();
|
|
449
|
+
const input = addInput(form, 'password', 'password');
|
|
468
450
|
|
|
469
|
-
FormHelper.
|
|
451
|
+
FormHelper.initTypeFields(form);
|
|
470
452
|
|
|
471
|
-
|
|
472
|
-
expect(
|
|
473
|
-
expect(
|
|
453
|
+
const wrapper = form.querySelector('.input-group.password');
|
|
454
|
+
expect(wrapper).not.toBeNull();
|
|
455
|
+
expect(wrapper.querySelector('input[type="password"]')).not.toBeNull();
|
|
474
456
|
});
|
|
475
457
|
|
|
476
|
-
test('
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
const results = [
|
|
480
|
-
callback.call({ value: 'value1' }),
|
|
481
|
-
callback.call({ value: 'value2' }),
|
|
482
|
-
callback.call({ value: '' })
|
|
483
|
-
];
|
|
484
|
-
return {
|
|
485
|
-
get: () => results.filter(r => r.length > 0)
|
|
486
|
-
};
|
|
487
|
-
})
|
|
488
|
-
};
|
|
458
|
+
test('should add toggle button span inside wrapper', () => {
|
|
459
|
+
const form = setupForm();
|
|
460
|
+
addInput(form, 'password', 'password');
|
|
489
461
|
|
|
490
|
-
|
|
462
|
+
FormHelper.initTypeFields(form);
|
|
491
463
|
|
|
492
|
-
|
|
464
|
+
const span = form.querySelector('.input-group.password .input-group-text');
|
|
465
|
+
expect(span).not.toBeNull();
|
|
466
|
+
expect(span.querySelector('i.fa-eye')).not.toBeNull();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test('should toggle password visibility on icon click', () => {
|
|
470
|
+
const form = setupForm();
|
|
471
|
+
addInput(form, 'password', 'password');
|
|
472
|
+
|
|
473
|
+
FormHelper.initTypeFields(form);
|
|
474
|
+
|
|
475
|
+
const icon = form.querySelector('.input-group.password i.fa-eye');
|
|
476
|
+
const passwordInput = form.querySelector('.input-group.password input');
|
|
477
|
+
|
|
478
|
+
expect(passwordInput.type).toBe('password');
|
|
479
|
+
icon.click();
|
|
480
|
+
expect(passwordInput.type).toBe('text');
|
|
481
|
+
icon.click();
|
|
482
|
+
expect(passwordInput.type).toBe('password');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test('should set max-width on date inputs when Modernizr.inputtypes.date is false', () => {
|
|
486
|
+
const form = setupForm();
|
|
487
|
+
const dateInput = addInput(form, 'mydate', 'date');
|
|
488
|
+
global.Modernizr = { inputtypes: { date: false, time: true } };
|
|
489
|
+
|
|
490
|
+
FormHelper.initTypeFields(form);
|
|
491
|
+
|
|
492
|
+
expect(dateInput.style.maxWidth).toBe('120px');
|
|
493
|
+
delete global.Modernizr;
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('should set placeholder on time inputs when Modernizr.inputtypes.time is false', () => {
|
|
497
|
+
const form = setupForm();
|
|
498
|
+
const timeInput = addInput(form, 'mytime', 'time');
|
|
499
|
+
global.Modernizr = { inputtypes: { date: true, time: false } };
|
|
500
|
+
|
|
501
|
+
FormHelper.initTypeFields(form);
|
|
502
|
+
|
|
503
|
+
expect(timeInput.style.maxWidth).toBe('100px');
|
|
504
|
+
expect(timeInput.placeholder).toBe('hh:mm');
|
|
505
|
+
delete global.Modernizr;
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test('should skip Modernizr block when Modernizr not defined', () => {
|
|
509
|
+
const form = setupForm();
|
|
510
|
+
addInput(form, 'password', 'password');
|
|
511
|
+
|
|
512
|
+
// Should not throw when Modernizr is undefined
|
|
513
|
+
expect(() => FormHelper.initTypeFields(form)).not.toThrow();
|
|
493
514
|
});
|
|
494
515
|
});
|
|
495
516
|
|
|
496
|
-
describe('
|
|
497
|
-
test('should
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
517
|
+
describe('hideField', () => {
|
|
518
|
+
test('should add hide class to closest .form-group', () => {
|
|
519
|
+
const form = setupForm();
|
|
520
|
+
const formGroup = document.createElement('div');
|
|
521
|
+
formGroup.className = 'form-group';
|
|
522
|
+
form.appendChild(formGroup);
|
|
523
|
+
const input = document.createElement('input');
|
|
524
|
+
formGroup.appendChild(input);
|
|
502
525
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
526
|
+
FormHelper.hideField(input);
|
|
527
|
+
|
|
528
|
+
expect(formGroup.classList.contains('hide')).toBe(true);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe('hideFormErrors', () => {
|
|
533
|
+
test('should remove all div.form_errors elements', () => {
|
|
534
|
+
const form = setupForm();
|
|
535
|
+
const err1 = document.createElement('div');
|
|
536
|
+
err1.className = 'form_errors';
|
|
537
|
+
const err2 = document.createElement('div');
|
|
538
|
+
err2.className = 'form_errors';
|
|
539
|
+
form.appendChild(err1);
|
|
540
|
+
form.appendChild(err2);
|
|
509
541
|
|
|
510
|
-
FormHelper.
|
|
542
|
+
const result = FormHelper.hideFormErrors(form);
|
|
511
543
|
|
|
512
|
-
expect(
|
|
513
|
-
expect(
|
|
544
|
+
expect(form.querySelectorAll('div.form_errors').length).toBe(0);
|
|
545
|
+
expect(result).toBe(form);
|
|
514
546
|
});
|
|
547
|
+
});
|
|
515
548
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
549
|
+
describe('displayFormErrorsFromText', () => {
|
|
550
|
+
test('should insert into errorWrapperDiv when provided', () => {
|
|
551
|
+
const form = setupForm();
|
|
552
|
+
const wrapper = document.createElement('div');
|
|
553
|
+
document.body.appendChild(wrapper);
|
|
521
554
|
|
|
522
|
-
|
|
523
|
-
wrap: jest.fn().mockReturnThis(),
|
|
524
|
-
after: jest.fn().mockReturnThis(),
|
|
525
|
-
length: 0
|
|
526
|
-
};
|
|
555
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text', wrapper);
|
|
527
556
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (selector === 'input[type="password"]') return passwordInput;
|
|
532
|
-
return mockInput;
|
|
533
|
-
});
|
|
557
|
+
expect(wrapper.querySelector('.form_errors')).not.toBeNull();
|
|
558
|
+
expect(wrapper.querySelector('.form_errors').textContent).toBe('Error text');
|
|
559
|
+
});
|
|
534
560
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
};
|
|
561
|
+
test('should insert into .form_errors_content when exists', () => {
|
|
562
|
+
const form = setupForm();
|
|
563
|
+
const container = document.createElement('div');
|
|
564
|
+
container.className = 'form_errors_content';
|
|
565
|
+
form.appendChild(container);
|
|
541
566
|
|
|
542
|
-
FormHelper.
|
|
567
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text');
|
|
543
568
|
|
|
544
|
-
expect(
|
|
569
|
+
expect(container.querySelector('.form_errors')).not.toBeNull();
|
|
570
|
+
});
|
|
545
571
|
|
|
546
|
-
|
|
572
|
+
test('should insert before first .form-group when present', () => {
|
|
573
|
+
const form = setupForm();
|
|
574
|
+
const fg = document.createElement('div');
|
|
575
|
+
fg.className = 'form-group';
|
|
576
|
+
form.appendChild(fg);
|
|
577
|
+
|
|
578
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text');
|
|
579
|
+
|
|
580
|
+
const errors = form.querySelector('.form_errors');
|
|
581
|
+
expect(errors).not.toBeNull();
|
|
582
|
+
// The error div should be before the form-group
|
|
583
|
+
expect(form.children[0].classList.contains('form_errors')).toBe(true);
|
|
547
584
|
});
|
|
548
585
|
|
|
549
|
-
test('should
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
586
|
+
test('should insert before row grandparent when form-group is inside a .row', () => {
|
|
587
|
+
const form = setupForm();
|
|
588
|
+
const row = document.createElement('div');
|
|
589
|
+
row.className = 'row';
|
|
590
|
+
form.appendChild(row);
|
|
591
|
+
const col = document.createElement('div');
|
|
592
|
+
col.className = 'col';
|
|
593
|
+
row.appendChild(col);
|
|
594
|
+
const fg = document.createElement('div');
|
|
595
|
+
fg.className = 'form-group';
|
|
596
|
+
col.appendChild(fg);
|
|
555
597
|
|
|
556
|
-
|
|
557
|
-
wrap: jest.fn().mockReturnThis(),
|
|
558
|
-
after: jest.fn().mockReturnThis(),
|
|
559
|
-
length: 0
|
|
560
|
-
};
|
|
598
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text');
|
|
561
599
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return mockInput;
|
|
568
|
-
});
|
|
600
|
+
// Error should be inserted before the .row
|
|
601
|
+
const errors = form.querySelector('.form_errors');
|
|
602
|
+
expect(errors).not.toBeNull();
|
|
603
|
+
expect(form.children[0].classList.contains('form_errors')).toBe(true);
|
|
604
|
+
});
|
|
569
605
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
date: true,
|
|
573
|
-
time: false
|
|
574
|
-
}
|
|
575
|
-
};
|
|
606
|
+
test('should prepend to form when no other location found', () => {
|
|
607
|
+
const form = setupForm();
|
|
576
608
|
|
|
577
|
-
FormHelper.
|
|
609
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text');
|
|
578
610
|
|
|
579
|
-
expect(
|
|
580
|
-
|
|
611
|
+
expect(form.children[0].classList.contains('form_errors')).toBe(true);
|
|
612
|
+
});
|
|
581
613
|
|
|
582
|
-
|
|
614
|
+
test('should place errors inside modal-body when present', () => {
|
|
615
|
+
const form = setupForm();
|
|
616
|
+
const modalBody = document.createElement('div');
|
|
617
|
+
modalBody.className = 'modal-body';
|
|
618
|
+
form.appendChild(modalBody);
|
|
619
|
+
|
|
620
|
+
FormHelper.displayFormErrorsFromText(form, 'Error text');
|
|
621
|
+
|
|
622
|
+
// The form_errors should be inside modal-body (prepended)
|
|
623
|
+
expect(modalBody.querySelector('.form_errors')).not.toBeNull();
|
|
583
624
|
});
|
|
625
|
+
});
|
|
584
626
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
length: 1
|
|
590
|
-
};
|
|
627
|
+
describe('buttonLoader', () => {
|
|
628
|
+
test('should disable button and set loading text on loading action', () => {
|
|
629
|
+
const form = setupForm();
|
|
630
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
591
631
|
|
|
592
|
-
|
|
593
|
-
length: 0,
|
|
594
|
-
css: jest.fn().mockReturnThis(),
|
|
595
|
-
attr: jest.fn().mockReturnThis()
|
|
596
|
-
};
|
|
632
|
+
FormHelper.buttonLoader(btn, 'loading');
|
|
597
633
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
634
|
+
expect(btn.disabled).toBe(true);
|
|
635
|
+
expect(btn.classList.contains('disabled')).toBe(true);
|
|
636
|
+
expect(btn.innerHTML).toContain('fa-spin');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test('should not process if already disabled', () => {
|
|
640
|
+
const form = setupForm();
|
|
641
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
642
|
+
btn.disabled = true;
|
|
643
|
+
const originalHTML = btn.innerHTML;
|
|
602
644
|
|
|
603
|
-
FormHelper.
|
|
645
|
+
FormHelper.buttonLoader(btn, 'loading');
|
|
604
646
|
|
|
605
|
-
|
|
606
|
-
|
|
647
|
+
expect(btn.innerHTML).toBe(originalHTML);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test('should restore original text on reset', () => {
|
|
651
|
+
const form = setupForm();
|
|
652
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
653
|
+
|
|
654
|
+
FormHelper.buttonLoader(btn, 'loading');
|
|
655
|
+
FormHelper.buttonLoader(btn, 'reset');
|
|
656
|
+
|
|
657
|
+
expect(btn.innerHTML).toBe('Submit');
|
|
658
|
+
expect(btn.disabled).toBe(false);
|
|
659
|
+
expect(btn.classList.contains('disabled')).toBe(false);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test('should use data-load-text when provided', () => {
|
|
663
|
+
const form = setupForm();
|
|
664
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
665
|
+
btn.dataset.loadText = 'Loading...';
|
|
666
|
+
|
|
667
|
+
FormHelper.buttonLoader(btn, 'start');
|
|
668
|
+
|
|
669
|
+
expect(btn.innerHTML).toBe('Loading...');
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test('should use data-loading-text when provided', () => {
|
|
673
|
+
const form = setupForm();
|
|
674
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
675
|
+
btn.dataset.loadingText = 'Custom...';
|
|
676
|
+
|
|
677
|
+
FormHelper.buttonLoader(btn, 'start');
|
|
678
|
+
|
|
679
|
+
expect(btn.innerHTML).toBe('Custom...');
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
test('should return the button element', () => {
|
|
683
|
+
const form = setupForm();
|
|
684
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
685
|
+
|
|
686
|
+
const result = FormHelper.buttonLoader(btn, 'reset');
|
|
687
|
+
|
|
688
|
+
expect(result).toBe(btn);
|
|
607
689
|
});
|
|
608
690
|
});
|
|
609
691
|
|
|
610
|
-
describe('
|
|
611
|
-
test('should
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
692
|
+
describe('getInputValue', () => {
|
|
693
|
+
test('should return null for undefined input', () => {
|
|
694
|
+
expect(FormHelper.getInputValue(undefined)).toBeNull();
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test('should return null for null input', () => {
|
|
698
|
+
expect(FormHelper.getInputValue(null)).toBeNull();
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('should return null for empty string value', () => {
|
|
702
|
+
const form = setupForm();
|
|
703
|
+
const input = addInput(form, 'name', 'text', '');
|
|
704
|
+
|
|
705
|
+
expect(FormHelper.getInputValue(input)).toBeNull();
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
test('should return value when present', () => {
|
|
709
|
+
const form = setupForm();
|
|
710
|
+
const input = addInput(form, 'name', 'text', 'test value');
|
|
711
|
+
|
|
712
|
+
expect(FormHelper.getInputValue(input)).toBe('test value');
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
describe('getLinesOfTextarea', () => {
|
|
717
|
+
test('should split textarea by newlines and filter empty', () => {
|
|
718
|
+
const form = setupForm();
|
|
719
|
+
const textarea = document.createElement('textarea');
|
|
720
|
+
textarea.value = 'line1\nline2\nline3';
|
|
721
|
+
form.appendChild(textarea);
|
|
722
|
+
|
|
723
|
+
const result = FormHelper.getLinesOfTextarea(textarea);
|
|
615
724
|
|
|
616
|
-
|
|
725
|
+
expect(result).toEqual(['line1', 'line2', 'line3']);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test('should handle Windows line endings', () => {
|
|
729
|
+
const form = setupForm();
|
|
730
|
+
const textarea = document.createElement('textarea');
|
|
731
|
+
textarea.value = 'line1\r\nline2\r\nline3';
|
|
732
|
+
form.appendChild(textarea);
|
|
617
733
|
|
|
618
|
-
FormHelper.
|
|
734
|
+
const result = FormHelper.getLinesOfTextarea(textarea);
|
|
619
735
|
|
|
620
|
-
expect(
|
|
621
|
-
expect(formGroupMock.addClass).toHaveBeenCalledWith('hide');
|
|
736
|
+
expect(result).toEqual(['line1', 'line2', 'line3']);
|
|
622
737
|
});
|
|
623
738
|
});
|
|
624
739
|
|
|
@@ -756,16 +871,18 @@ describe('FormHelper', () => {
|
|
|
756
871
|
|
|
757
872
|
describe('displayFormErrors', () => {
|
|
758
873
|
test('should display errors and reset button', () => {
|
|
874
|
+
const form = setupForm();
|
|
875
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
759
876
|
const errors = { field: 'Error message' };
|
|
760
877
|
const displaySpy = jest.spyOn(FormHelper, 'displayFormErrorsFromText');
|
|
761
878
|
const getTextSpy = jest.spyOn(FormHelper, 'getFormErrorText').mockReturnValue('<span>Error</span>');
|
|
762
879
|
const buttonLoaderSpy = jest.spyOn(FormHelper, 'buttonLoader');
|
|
763
880
|
|
|
764
|
-
FormHelper.displayFormErrors(
|
|
881
|
+
FormHelper.displayFormErrors(form, btn, errors);
|
|
765
882
|
|
|
766
883
|
expect(getTextSpy).toHaveBeenCalledWith(errors);
|
|
767
|
-
expect(displaySpy).toHaveBeenCalledWith(
|
|
768
|
-
expect(buttonLoaderSpy).toHaveBeenCalledWith(
|
|
884
|
+
expect(displaySpy).toHaveBeenCalledWith(form, '<span>Error</span>', null);
|
|
885
|
+
expect(buttonLoaderSpy).toHaveBeenCalledWith(btn, 'reset');
|
|
769
886
|
|
|
770
887
|
displaySpy.mockRestore();
|
|
771
888
|
getTextSpy.mockRestore();
|
|
@@ -773,273 +890,36 @@ describe('FormHelper', () => {
|
|
|
773
890
|
});
|
|
774
891
|
|
|
775
892
|
test('should work without button', () => {
|
|
893
|
+
const form = setupForm();
|
|
776
894
|
const errors = { field: 'Error message' };
|
|
777
895
|
const displaySpy = jest.spyOn(FormHelper, 'displayFormErrorsFromText');
|
|
778
896
|
const getTextSpy = jest.spyOn(FormHelper, 'getFormErrorText').mockReturnValue('<span>Error</span>');
|
|
779
897
|
|
|
780
|
-
FormHelper.displayFormErrors(
|
|
898
|
+
FormHelper.displayFormErrors(form, null, errors);
|
|
781
899
|
|
|
782
|
-
expect(displaySpy).toHaveBeenCalledWith(
|
|
900
|
+
expect(displaySpy).toHaveBeenCalledWith(form, '<span>Error</span>', null);
|
|
783
901
|
|
|
784
902
|
displaySpy.mockRestore();
|
|
785
903
|
getTextSpy.mockRestore();
|
|
786
904
|
});
|
|
787
905
|
|
|
788
906
|
test('should use custom error wrapper', () => {
|
|
907
|
+
const form = setupForm();
|
|
908
|
+
const btn = addButton(form, 'validate', 'Submit');
|
|
789
909
|
const errors = { field: 'Error message' };
|
|
790
|
-
const wrapper =
|
|
910
|
+
const wrapper = document.createElement('div');
|
|
911
|
+
document.body.appendChild(wrapper);
|
|
791
912
|
const displaySpy = jest.spyOn(FormHelper, 'displayFormErrorsFromText');
|
|
792
913
|
const getTextSpy = jest.spyOn(FormHelper, 'getFormErrorText').mockReturnValue('<span>Error</span>');
|
|
793
914
|
|
|
794
|
-
FormHelper.displayFormErrors(
|
|
915
|
+
FormHelper.displayFormErrors(form, btn, errors, wrapper);
|
|
795
916
|
|
|
796
|
-
expect(displaySpy).toHaveBeenCalledWith(
|
|
917
|
+
expect(displaySpy).toHaveBeenCalledWith(form, '<span>Error</span>', wrapper);
|
|
797
918
|
|
|
798
919
|
displaySpy.mockRestore();
|
|
799
920
|
getTextSpy.mockRestore();
|
|
800
921
|
});
|
|
801
922
|
});
|
|
802
|
-
|
|
803
|
-
describe('displayFormErrorsFromText', () => {
|
|
804
|
-
test('should append to errorWrapperDiv when provided', () => {
|
|
805
|
-
const wrapper = {
|
|
806
|
-
append: jest.fn()
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text', wrapper);
|
|
810
|
-
|
|
811
|
-
expect(wrapper.append).toHaveBeenCalledWith('<div class="alert alert-danger form_errors">Error text</div>');
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
test('should append to .form_errors_content when exists', () => {
|
|
815
|
-
const errorContent = {
|
|
816
|
-
append: jest.fn(),
|
|
817
|
-
length: 1
|
|
818
|
-
};
|
|
819
|
-
|
|
820
|
-
mockForm.find = jest.fn((selector) => {
|
|
821
|
-
if (selector === '.form_errors_content') return errorContent;
|
|
822
|
-
return { length: 0 };
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text');
|
|
826
|
-
|
|
827
|
-
expect(errorContent.append).toHaveBeenCalled();
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
test('should prepend to modal-body when exists', () => {
|
|
831
|
-
const formGroupMock = {
|
|
832
|
-
parent: jest.fn().mockReturnThis(),
|
|
833
|
-
hasClass: jest.fn(() => false),
|
|
834
|
-
before: jest.fn(),
|
|
835
|
-
length: 1
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
const modalBody = {
|
|
839
|
-
find: jest.fn((selector) => {
|
|
840
|
-
if (selector === '.form-group:first') return formGroupMock;
|
|
841
|
-
return { length: 0 };
|
|
842
|
-
}),
|
|
843
|
-
length: 1
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
mockForm.find = jest.fn((selector) => {
|
|
847
|
-
if (selector === '.form_errors_content') return { length: 0 };
|
|
848
|
-
if (selector === '.modal-body') return modalBody;
|
|
849
|
-
if (selector === '.form-group:first') return { length: 0 };
|
|
850
|
-
return mockInput;
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text');
|
|
854
|
-
|
|
855
|
-
expect(mockForm.find).toHaveBeenCalledWith('.modal-body');
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
test('should insert before first form-group when exists', () => {
|
|
859
|
-
const formGroup = {
|
|
860
|
-
parent: jest.fn().mockReturnThis(),
|
|
861
|
-
hasClass: jest.fn(() => false),
|
|
862
|
-
before: jest.fn(),
|
|
863
|
-
length: 1
|
|
864
|
-
};
|
|
865
|
-
|
|
866
|
-
mockForm.find = jest.fn((selector) => {
|
|
867
|
-
if (selector === '.form_errors_content') return { length: 0 };
|
|
868
|
-
if (selector === '.modal-body') return { length: 0 };
|
|
869
|
-
if (selector === '.form-group:first') return formGroup;
|
|
870
|
-
return mockInput;
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text');
|
|
874
|
-
|
|
875
|
-
expect(formGroup.before).toHaveBeenCalled();
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
test('should insert before row parent when form-group is in row', () => {
|
|
879
|
-
const parentRow = {
|
|
880
|
-
parent: jest.fn().mockReturnThis(),
|
|
881
|
-
hasClass: jest.fn(() => true),
|
|
882
|
-
before: jest.fn()
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
const formGroup = {
|
|
886
|
-
parent: jest.fn(() => parentRow),
|
|
887
|
-
length: 1
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
mockForm.find = jest.fn((selector) => {
|
|
891
|
-
if (selector === '.form_errors_content') return { length: 0 };
|
|
892
|
-
if (selector === '.modal-body') return { length: 0 };
|
|
893
|
-
if (selector === '.form-group:first') return formGroup;
|
|
894
|
-
return mockInput;
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text');
|
|
898
|
-
|
|
899
|
-
expect(parentRow.before).toHaveBeenCalled();
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
test('should prepend to form when no other location found', () => {
|
|
903
|
-
mockForm.find = jest.fn(() => ({ length: 0 }));
|
|
904
|
-
mockForm.prepend = jest.fn();
|
|
905
|
-
|
|
906
|
-
FormHelper.displayFormErrorsFromText(mockForm, 'Error text');
|
|
907
|
-
|
|
908
|
-
expect(mockForm.prepend).toHaveBeenCalledWith('<div class="alert alert-danger form_errors">Error text</div>');
|
|
909
|
-
});
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
describe('buttonLoader', () => {
|
|
913
|
-
test('should disable button on loading', () => {
|
|
914
|
-
mockButton.attr.mockImplementation((key, value) => {
|
|
915
|
-
if (key === 'disabled' && value === undefined) return false;
|
|
916
|
-
return mockButton;
|
|
917
|
-
});
|
|
918
|
-
mockButton.data.mockImplementation((key, value) => {
|
|
919
|
-
if (key === 'btn-text') return 'Original Text';
|
|
920
|
-
return mockButton;
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
FormHelper.buttonLoader(mockButton, 'loading');
|
|
924
|
-
|
|
925
|
-
expect(mockButton.attr).toHaveBeenCalledWith('disabled', true);
|
|
926
|
-
expect(mockButton.addClass).toHaveBeenCalledWith('disabled');
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
test('should not process if already disabled', () => {
|
|
930
|
-
mockButton.attr.mockImplementation((key) => {
|
|
931
|
-
if (key === 'disabled') return true;
|
|
932
|
-
return mockButton;
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
FormHelper.buttonLoader(mockButton, 'loading');
|
|
936
|
-
|
|
937
|
-
expect(mockButton.html).not.toHaveBeenCalled();
|
|
938
|
-
});
|
|
939
|
-
|
|
940
|
-
test('should enable button on reset', () => {
|
|
941
|
-
mockButton.data.mockReturnValue('Original Text');
|
|
942
|
-
|
|
943
|
-
FormHelper.buttonLoader(mockButton, 'reset');
|
|
944
|
-
|
|
945
|
-
expect(mockButton.html).toHaveBeenCalledWith('Original Text');
|
|
946
|
-
expect(mockButton.removeClass).toHaveBeenCalledWith('disabled');
|
|
947
|
-
expect(mockButton.attr).toHaveBeenCalledWith('disabled', false);
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
test('should use load-text when provided', () => {
|
|
951
|
-
mockButton.attr.mockImplementation((key) => {
|
|
952
|
-
if (key === 'disabled') return false;
|
|
953
|
-
return mockButton;
|
|
954
|
-
});
|
|
955
|
-
mockButton.data.mockImplementation((key) => {
|
|
956
|
-
if (key === 'load-text') return 'Loading...';
|
|
957
|
-
return null;
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
FormHelper.buttonLoader(mockButton, 'start');
|
|
961
|
-
|
|
962
|
-
expect(mockButton.html).toHaveBeenCalledWith('Loading...');
|
|
963
|
-
});
|
|
964
|
-
|
|
965
|
-
test('should use loading-text when provided', () => {
|
|
966
|
-
mockButton.attr.mockImplementation((key) => {
|
|
967
|
-
if (key === 'disabled') return false;
|
|
968
|
-
return mockButton;
|
|
969
|
-
});
|
|
970
|
-
mockButton.data.mockImplementation((key) => {
|
|
971
|
-
if (key === 'loading-text') return 'Custom Loading...';
|
|
972
|
-
return null;
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
FormHelper.buttonLoader(mockButton, 'start');
|
|
976
|
-
|
|
977
|
-
expect(mockButton.html).toHaveBeenCalledWith('Custom Loading...');
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
test('should return button object', () => {
|
|
981
|
-
mockButton.data.mockReturnValue('Text');
|
|
982
|
-
const result = FormHelper.buttonLoader(mockButton, 'reset');
|
|
983
|
-
|
|
984
|
-
expect(result).toBe(mockButton);
|
|
985
|
-
});
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
describe('getInputValue', () => {
|
|
989
|
-
test('should return null for undefined input', () => {
|
|
990
|
-
expect(FormHelper.getInputValue(undefined)).toBeNull();
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
test('should return null for empty string value', () => {
|
|
994
|
-
const mockInput = { val: jest.fn(() => '') };
|
|
995
|
-
global.$ = jest.fn(() => mockInput);
|
|
996
|
-
|
|
997
|
-
expect(FormHelper.getInputValue(mockInput)).toBeNull();
|
|
998
|
-
|
|
999
|
-
delete global.$;
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
test('should return value when present', () => {
|
|
1003
|
-
const mockInput = { val: jest.fn(() => 'test value') };
|
|
1004
|
-
global.$ = jest.fn(() => mockInput);
|
|
1005
|
-
|
|
1006
|
-
expect(FormHelper.getInputValue(mockInput)).toBe('test value');
|
|
1007
|
-
|
|
1008
|
-
delete global.$;
|
|
1009
|
-
});
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
describe('getLinesOfTextarea', () => {
|
|
1013
|
-
test('should split textarea by newlines and filter empty', () => {
|
|
1014
|
-
const mockTextarea = {
|
|
1015
|
-
val: jest.fn(() => 'line1\nline2\nline3')
|
|
1016
|
-
};
|
|
1017
|
-
|
|
1018
|
-
const result = FormHelper.getLinesOfTextarea(mockTextarea);
|
|
1019
|
-
|
|
1020
|
-
expect(result).toEqual(['line1', 'line2', 'line3']);
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
test('should handle Windows line endings', () => {
|
|
1024
|
-
const mockTextarea = {
|
|
1025
|
-
val: jest.fn(() => 'line1\r\nline2\r\nline3')
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
const result = FormHelper.getLinesOfTextarea(mockTextarea);
|
|
1029
|
-
|
|
1030
|
-
expect(result).toEqual(['line1', 'line2', 'line3']);
|
|
1031
|
-
});
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
describe('hideFormErrors', () => {
|
|
1035
|
-
test('should remove form_errors div', () => {
|
|
1036
|
-
const result = FormHelper.hideFormErrors(mockForm);
|
|
1037
|
-
|
|
1038
|
-
expect(mockForm.find).toHaveBeenCalledWith('div.form_errors');
|
|
1039
|
-
expect(mockInput.remove).toHaveBeenCalled();
|
|
1040
|
-
expect(result).toBe(mockForm);
|
|
1041
|
-
});
|
|
1042
|
-
});
|
|
1043
923
|
});
|
|
1044
924
|
|
|
1045
925
|
describe('ArrayField', () => {
|