@onehat/ui 0.3.283 → 0.3.284

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.
@@ -0,0 +1,43 @@
1
+
2
+ /**
3
+ * Get the first DOM node matching the nested selectors.
4
+ * (Note: it gets the first matching element from each selector, not just the last one.)
5
+ * @argument {string | string[]} selectors - data-testid attribute values
6
+ * If an array is given, these will be considered nested selectors.
7
+ * e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
8
+ * @return Cypress chainer
9
+ */
10
+ export function getDomNode(selectors) {
11
+ return cy.get(getTestIdSelectors(selectors, true));
12
+ }
13
+
14
+ /**
15
+ * Get *all* DOM nodes matching the nested selectors.
16
+ * @argument {string | string[]} selectors - data-testid attribute values
17
+ * If an array is given, these will be considered nested selectors.
18
+ * e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
19
+ * @return Cypress chainer
20
+ */
21
+ export function getDomNodes(selectors) {
22
+ return cy.get(getTestIdSelectors(selectors));
23
+ }
24
+
25
+ /**
26
+ * Builds selector string for data-testid attributes.
27
+ * @argument {string | string[]} selectors - data-testid attribute values
28
+ * If an array is given, these will be considered nested selectors.
29
+ * e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
30
+ * @return {string}
31
+ */
32
+ export function getTestIdSelectors(selectors, isGetFirst = false) {
33
+ if (_.isString(selectors)) {
34
+ selectors = [selectors];
35
+ }
36
+ const selectorParts = _.map(selectors, (selector) => {
37
+ if (selector.match(/=/)) { // selector is something like [role="switch"], so don't use data-testid
38
+ return selector;
39
+ }
40
+ return '[data-testid="' + selector + '"]' + (isGetFirst ? ':first' : '');
41
+ });
42
+ return selectorParts.join(' ');
43
+ }
@@ -0,0 +1,510 @@
1
+ import {
2
+ getPropertyDefinitionFromSchema,
3
+ getLastPartOfPath,
4
+ } from './utilities';
5
+ import {
6
+ getDomNode,
7
+ getDomNodes,
8
+ getTestIdSelectors,
9
+ } from './dom_functions';
10
+ import {
11
+ crudCombo,
12
+ crudTag,
13
+ } from './crud_functions';
14
+ import {
15
+ getCustomEditorGetFn,
16
+ getCustomEditorSetFn,
17
+ } from './custom_form_functions'
18
+ import natsort from 'natsort';
19
+ import _ from 'lodash';
20
+ const $ = Cypress.$;
21
+
22
+ let customFormFunctions = {};
23
+ export function setCustomFormFunctions(fns) {
24
+ _.merge(customFormFunctions, fns);
25
+ }
26
+
27
+
28
+ /**
29
+ * Take data and shove it into a form, using keypresses, clicks, etc
30
+ * @param {object} fieldValues - fieldName/value pairs
31
+ * @param {object} schema - fieldName/fieldType pairs
32
+ */
33
+ export function fillForm(selector, fieldValues, schema, level = 0) {
34
+ _.each(fieldValues, (value, fieldName) => {
35
+
36
+ const selectors = [selector, 'field-' + fieldName];
37
+ getDomNode(selectors).scrollIntoView();
38
+
39
+ let editorType = null;
40
+ if (schema.model) {
41
+ // OneHatData schema
42
+ const propertyDefinition = getPropertyDefinitionFromSchema(fieldName, schema);
43
+ if (propertyDefinition.isEditingDisabled) {
44
+ return;
45
+ }
46
+ editorType = propertyDefinition?.editorType?.type;
47
+ } else {
48
+ // basic schema (for ReportsManager, etc.)
49
+ editorType = schema[fieldName];
50
+ }
51
+
52
+ if (editorType === 'Input') {
53
+ setTextValue(selectors, value);
54
+ } else
55
+ if (editorType === 'TextArea') {
56
+ setTextAreaValue(selectors, value);
57
+ } else
58
+ if (editorType?.match(/ArrayCombo/)) {
59
+ setArrayComboValue(selectors, value);
60
+ } else
61
+ if (editorType?.match(/Combo/)) {
62
+ if (value?.value) {
63
+ // First test the CRUD operations of this combo
64
+ crudCombo(selectors, value.newData, value.editData, value.schema, value.ancillaryData, level);
65
+ value = value.value;
66
+ }
67
+ setComboValue(selectors, value);
68
+ } else
69
+ if (editorType === 'Date') {
70
+ setDateValue(selectors, value);
71
+ } else
72
+ if (editorType === 'Number') {
73
+ setNumberValue(selectors, value);
74
+ } else
75
+ if (editorType?.match(/Tag/)) {
76
+ if (value?.value) {
77
+ // First test the CRUD operations of this combo
78
+ crudTag(selectors, value.newData, value.editData, value.schema, value.ancillaryData, level);
79
+ value = value.value;
80
+ }
81
+ setTagValue(selectors, value);
82
+ } else
83
+ if (editorType === 'Toggle') {
84
+ setToggleValue(selectors, value);
85
+ } else
86
+ if (editorType?.match(/Checkbox/)) {
87
+ // setCheckboxValue(selectors, value);
88
+ } else
89
+ if (editorType?.match(/Radio/)) {
90
+ // setRadioValue(selectors, value);
91
+ } else
92
+ if (editorType === 'File') {
93
+ // setFileValue(selectors, value);
94
+ } else {
95
+ const editorFn = customFormFunctions.getCustomEditorSetFn(editorType);
96
+ if (editorFn) {
97
+ editorFn(selectors, value);
98
+ }
99
+ }
100
+ });
101
+ }
102
+
103
+
104
+
105
+ // _____ __ __
106
+ // / ___/___ / /_/ /____ __________
107
+ // \__ \/ _ \/ __/ __/ _ \/ ___/ ___/
108
+ // ___/ / __/ /_/ /_/ __/ / (__ )
109
+ // /____/\___/\__/\__/\___/_/ /____/
110
+
111
+ // export function setCheckboxValue(field, value) {
112
+ // }
113
+ // export function setRadioValue(field, value) {
114
+ // }
115
+ // export function setFileValue(field, value) {
116
+ // }
117
+ export function setArrayComboValue(selectors, value) {
118
+ getDomNode([...selectors, 'input']).then((field) => {
119
+ cy.get(field).clear({ force: true });
120
+ if (value) {
121
+ cy.get(field)
122
+ .type(value, { delay: 40, force: true }) // slow it down a bit, so React has time to re-render
123
+ .wait(1000) // allow time to load dropdown
124
+
125
+ .type('{enter}')
126
+ .wait(250); // allow time to register enter key
127
+ }
128
+ });
129
+ }
130
+ export function setComboValue(selectors, value) {
131
+ getDomNode([...selectors, 'input']).then((field) => {
132
+ cy.get(field).clear({ force: true });
133
+ if (value) {
134
+ cy.get(field)
135
+ .type(value, { delay: 40, force: true }) // slow it down a bit, so React has time to re-render
136
+ .wait(1000) // allow time to load dropdown
137
+
138
+ .type('{downarrow}')
139
+ .wait(250) // allow time for selection
140
+
141
+ .type('{enter}')
142
+ .wait(250); // allow time to register enter key
143
+ }
144
+ });
145
+ }
146
+ export function setTagValue(selectors, value) {
147
+ const values = !_.isEmpty(value) ? JSON.parse(value) : null;
148
+
149
+ // Clear any previously selected tags
150
+ function clickButtonsWithRemove(selector) {
151
+ // This function allows Cypress to click on multiple elements in one command,
152
+ // when clicking the elements removes them from the DOM.
153
+ cy.get('body').then((body) => {
154
+ if (body.find(selector).length === 0) {
155
+ return;
156
+ }
157
+ cy.get(selector).eq(0)
158
+ .click()
159
+ .then(() => {
160
+ clickButtonsWithRemove(selector); // Recursive call for the next element
161
+ });
162
+ });
163
+ }
164
+ clickButtonsWithRemove(getTestIdSelectors([...selectors, 'xBtn']));
165
+
166
+ // Now add the new tags
167
+ getDomNode([...selectors, 'input']).then((field) => {
168
+ cy.get(field).clear({ force: true });
169
+ if (!_.isEmpty(values)) {
170
+ _.each(values, (value) => {
171
+ const id = value.id;
172
+ cy.get(field)
173
+ .type('id:' + id, { delay: 40, force: true }) // slow it down a bit, so React has time to re-render
174
+ .wait(1000) // allow time to load dropdown
175
+
176
+ .type('{downarrow}')
177
+ .wait(250); // allow time for selection
178
+ });
179
+
180
+ // press trigger to hide dropdown
181
+ getDomNode([...selectors, 'trigger']).click({ force: true });
182
+ }
183
+ });
184
+ }
185
+ export function setDateValue(selectors, value) {
186
+ getDomNode(selectors).then((field) => {
187
+ cy.get(field).clear({ force: true });
188
+ if (value) {
189
+ cy.get(field)
190
+ .type(value, { force: true }) // slow it down a bit, so React has time to re-render
191
+ .type('{enter}');
192
+ }
193
+ });
194
+ }
195
+ export function setNumberValue(selectors, value) {
196
+ // setTextValue(selectors, value);
197
+
198
+ getDomNode(selectors).clear({ force: true });
199
+ if (value !== null && value !== '') {
200
+ getDomNode(selectors)
201
+ .type(value, { delay: 500 }) // WorkOrdersEditor was not working when there was no delay!
202
+ .type('{enter}');
203
+ }
204
+ }
205
+ export function setToggleValue(selectors, value) {
206
+ selectors.push('input[role="switch"]');
207
+ if (value) {
208
+ getToggleState(selectors).then((isYes) => {
209
+ if (!isYes) {
210
+ clickToggle(selectors, { force: true, metaKey: false });
211
+ }
212
+ });
213
+ } else if (value === false) {
214
+ getToggleState(selectors).then((isYes) => {
215
+ if (isYes) {
216
+ clickToggle(selectors, { force: true, metaKey: false });
217
+ }
218
+ });
219
+ } else if (_.isNil(value)) {
220
+ clickToggle(selectors, { force: true, metaKey: true });
221
+ }
222
+ }
223
+ export function getToggleState(selectors) {
224
+ return getDomNode(selectors).then((node) => {
225
+ if (!node.length) {
226
+ return null;
227
+ }
228
+ return !!node[0].checked;
229
+ });
230
+ }
231
+ export function clickToggle(selectors, options = {}) {
232
+ getDomNode(selectors).click(options);
233
+ }
234
+ export function setTextValue(selectors, value) {
235
+ getDomNode(selectors).clear({ force: true });
236
+ if (value !== null && value !== '') {
237
+ getDomNode(selectors)
238
+ .type(value)
239
+ .type('{enter}');
240
+ }
241
+ }
242
+ export function setTextAreaValue(selectors, value) {
243
+ getDomNode(selectors).clear({ force: true });
244
+ if (value !== null && value !== '') {
245
+ getDomNode(selectors)
246
+ .type(value);
247
+ }
248
+ }
249
+ export function setInputValue(selectors, value) {
250
+ setTextValue(selectors, value);
251
+ }
252
+
253
+
254
+ // /**
255
+ // * Given a form,
256
+ // * return a url-encoded string representing all keys and values
257
+ // *
258
+ // * @param {jQuery} form
259
+ // * @return {String}
260
+ // */
261
+ // export function formSerialize(form) {
262
+ // 'use strict';
263
+ // var i, j, len, jLen, formElement,
264
+ // q = [],
265
+ // theForm = form[0],
266
+ // varCounters = {};
267
+
268
+ // function addNameValue(name, value) { // create this function so I can use varCounters for
269
+ // var matches = name.match(/([\w\d]+)\[\]/i),
270
+ // varName,
271
+ // ix = 0;
272
+ // if (matches && matches[1]) {
273
+ // varName = matches[1];
274
+ // if (typeof varCounters[varName] === 'undefined') {
275
+ // varCounters[varName] = ix;
276
+ // } else {
277
+ // ix = ++varCounters[varName];
278
+ // }
279
+ // name = varName + '[' + ix + ']';
280
+ // }
281
+ // q.push(urlencode(name) + '=' + urlencode(value));
282
+ // }
283
+
284
+ // if (!theForm || !theForm.nodeName || theForm.nodeName.toLowerCase() !== 'form') {
285
+ // throw 'You must supply a form element';
286
+ // }
287
+ // for (i = 0, len = theForm.elements.length; i < len; i++) {
288
+ // formElement = theForm.elements[i];
289
+ // if (formElement.name === '' || formElement.disabled) {
290
+ // continue;
291
+ // }
292
+ // switch (formElement.nodeName.toLowerCase()) {
293
+ // case 'input':
294
+ // switch (formElement.type) {
295
+ // case 'text':
296
+ // case 'hidden':
297
+ // case 'password':
298
+ // case 'button': // Not submitted when submitting form manually, though jQuery does serialize this and it can be an HTML4 successful control
299
+ // case 'submit':
300
+ // addNameValue(formElement.name, formElement.value);
301
+ // break;
302
+ // case 'checkbox':
303
+ // case 'radio':
304
+ // if (formElement.checked) {
305
+ // addNameValue(formElement.name, formElement.value);
306
+ // } else if (formElement.value === '1') {
307
+ // addNameValue(formElement.name, '0'); // Submit actual value of zero for booleans, instead of no value at all
308
+ // }
309
+ // break;
310
+ // case 'file':
311
+ // // addNameValue(formElement.name, formElement.value); // Will work and part of HTML4 "successful controls", but not used in jQuery
312
+ // break;
313
+ // case 'reset':
314
+ // break;
315
+ // }
316
+ // break;
317
+ // case 'textarea':
318
+ // addNameValue(formElement.name, formElement.value);
319
+ // break;
320
+ // case 'select':
321
+ // switch (formElement.type) {
322
+ // case 'select-one':
323
+ // addNameValue(formElement.name, formElement.value);
324
+ // break;
325
+ // case 'select-multiple':
326
+ // for (j = 0, jLen = formElement.options.length; j < jLen; j++) {
327
+ // if (formElement.options[j].selected) {
328
+ // addNameValue(formElement.name, formElement.options[j].value);
329
+ // }
330
+ // }
331
+ // break;
332
+ // }
333
+ // break;
334
+ // case 'button': // jQuery does not submit these, though it is an HTML4 successful control
335
+ // switch (formElement.type) {
336
+ // case 'reset':
337
+ // case 'submit':
338
+ // case 'button':
339
+ // addNameValue(formElement.name, formElement.value);
340
+ // break;
341
+ // }
342
+ // break;
343
+ // }
344
+ // }
345
+ // return q.join('&');
346
+ // }
347
+
348
+
349
+
350
+ // ______ __ __
351
+ // / ____/__ / /_/ /____ __________
352
+ // / / __/ _ \/ __/ __/ _ \/ ___/ ___/
353
+ // / /_/ / __/ /_/ /_/ __/ / (__ )
354
+ // \____/\___/\__/\__/\___/_/ /____/
355
+
356
+ // /**
357
+ // * Get data from a form
358
+ // * @param {object} schema - fieldName/fieldType pairs
359
+ // * @returns {object} formValues - object of fieldName/value pairs
360
+ // */
361
+ // export function getFormValues(editor, schema) {
362
+ // const fields = editor.find('.x-form-field'),
363
+ // formValues = {};
364
+
365
+ // _.each(fields, (field) => {
366
+ // const fieldType = schema[field.name];
367
+ // switch(fieldType) {
368
+ // case 'checkbox':
369
+ // formValues[fieldName] = getCheckboxValue(fieldName);
370
+ // break;
371
+ // case 'combo':
372
+ // formValues[fieldName] = getComboValue(fieldName);
373
+ // break;
374
+ // case 'date':
375
+ // formValues[fieldName] = getDateValue(fieldName);
376
+ // break;
377
+ // case 'datetime':
378
+ // formValues[fieldName] = getDatetimeValue(fieldName);
379
+ // break;
380
+ // case 'file':
381
+ // formValues[fieldName] = getFileValue(fieldName);
382
+ // break;
383
+ // case 'number':
384
+ // formValues[fieldName] = getNumberValue(fieldName);
385
+ // break;
386
+ // case 'radio':
387
+ // formValues[fieldName] = getRadioValue(fieldName);
388
+ // break;
389
+ // case 'tag':
390
+ // formValues[fieldName] = getTagValue(fieldName);
391
+ // break;
392
+ // case 'text':
393
+ // case 'textarea':
394
+ // formValues[fieldName] = getTextValue(fieldName);
395
+ // break;
396
+ // case 'time':
397
+ // formValues[fieldName] = getTimeValue(fieldName);
398
+ // break;
399
+ // }
400
+ // });
401
+ // return formValues;
402
+ // }
403
+
404
+
405
+ // /**
406
+ // * Validate that form values match what they're supposed to
407
+ // */
408
+ // export function validateFormValues(data, schema) {
409
+ // cy.wrap().then(() => { // Wrap this in a Cypress promise, so it executes in correct order, relative to other Cypress promises
410
+
411
+ // const formValues = getFormValues(schema);
412
+ // let diff = deepDiffObj(formValues, data);
413
+
414
+ // // SPECIAL CASE: Omit password fields from diff
415
+ // const omitFields = [];
416
+ // _.each(diff, (value, key) => {
417
+ // if (key.match(/^password/i)) {
418
+ // omitFields.push(key);
419
+ // }
420
+ // });
421
+ // if (omitFields.length) {
422
+ // diff = _.omit(diff, omitFields);
423
+ // }
424
+
425
+ // // If there are still any differences, log them
426
+ // if (_.keys(diff).length > 0) {
427
+ // console.log('data', data);
428
+ // console.log('formValues', formValues);
429
+ // console.log('diff', diff);
430
+ // }
431
+
432
+ // expect(diff).to.deep.equal({});
433
+ // });
434
+ // }
435
+
436
+
437
+
438
+ // export function getCheckboxValue(fieldName) {
439
+
440
+ // }
441
+ // export function getComboValue(fieldName) {
442
+
443
+ // }
444
+ // export function getDateValue(fieldName) {
445
+
446
+ // }
447
+ // export function getDatetimeValue(fieldName) {
448
+
449
+ // }
450
+ // export function getFileValue(fieldName) {
451
+
452
+ // }
453
+ // export function getNumberValue(fieldName) {
454
+
455
+ // }
456
+ // export function getRadioValue(fieldName) {
457
+
458
+ // }
459
+ // export function getTagValue(fieldName) {
460
+
461
+ // }
462
+ // export function getTextValue(fieldName) {
463
+ // const value = $(field).val();
464
+ // return typeof value === 'undefined' || value === '' ? null : value;
465
+ // }
466
+ // export function getTimeValue(fieldName) {
467
+
468
+ // }
469
+
470
+ // // export function getTextareaValue(field) {
471
+ // // const value = $(field).val();
472
+ // // return typeof value === 'undefined' || value === '' ? null : value;
473
+ // // }
474
+ // // export function setSelectValue(fieldName, value) {
475
+ // // cy.get('select[name="' + fieldName + '"]')
476
+ // // .select(value);
477
+ // // }
478
+ // // export function getSelectValue(fieldName) {
479
+ // // const value = $('select[name="' + fieldName + '"]').val();
480
+ // // return typeof value === 'undefined' || value === '' ? null : value;
481
+ // // }
482
+ // // export function setCheckboxOrRadioValue(fieldName, value) {
483
+ // // cy.get(field)
484
+ // // .check(value);
485
+ // // }
486
+ // // export function getCheckboxOrRadioValue(fieldName) {
487
+ // // const value = $('input[name="' + fieldName + '"]:checked').val();
488
+ // // return typeof value === 'undefined' || value === '' ? null : value;
489
+ // // }
490
+ // // export function setCheckboxValues(fieldName, value) {
491
+ // // const values = value.split(',');
492
+ // // cy.get('input[name="' + fieldName + '[]"]')
493
+ // // .check(values);
494
+ // // }
495
+ // // export function getCheckboxValues(fieldName) {
496
+ // // const inputs = $('input[name="' + fieldName + '[]"]:checked'),
497
+ // // values = [];
498
+
499
+ // // _.each(inputs, (input) => {
500
+ // // values.push($(input).val());
501
+ // // });
502
+
503
+ // // values.sort(natsort());
504
+
505
+ // // const value = values.join(',');
506
+ // // return value === '' ? null : value;
507
+ // // }
508
+
509
+
510
+