@onehat/ui 0.3.283 → 0.3.285

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