@onehat/ui 0.4.32 → 0.4.34
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/package.json +1 -1
- package/src/Components/Editor/Editor.js +2 -2
- package/src/Components/Form/FieldSet.js +2 -0
- package/src/Components/Form/Form.js +8 -9
- package/src/Components/Hoc/withEditor.js +43 -15
- package/src/Components/Hoc/withPdfButtons.js +5 -0
- package/src/Components/Hoc/withSelection.js +20 -16
- package/src/Constants/Dates.js +1 -0
- package/src/Functions/Cypress/button_functions.js +8 -8
- package/src/Functions/Cypress/crud_functions.js +52 -3
- package/src/Functions/Cypress/dom_functions.js +35 -2
- package/src/Functions/Cypress/form_functions.js +4 -2
- package/src/Functions/Cypress/utilities.js +8 -1
- package/src/Functions/buildAdditionalButtons.js +11 -18
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ function Editor(props) {
|
|
|
15
15
|
onEditorSave: onSave,
|
|
16
16
|
onEditorClose: onClose,
|
|
17
17
|
onEditorDelete: onDelete,
|
|
18
|
-
|
|
18
|
+
getEditorMode,
|
|
19
19
|
onEditMode,
|
|
20
20
|
canRecordBeEdited,
|
|
21
21
|
_viewer = {},
|
|
@@ -46,7 +46,7 @@ function Editor(props) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Repository?.isRemotePhantomMode && selection.length === 1 &&
|
|
49
|
-
if (
|
|
49
|
+
if (getEditorMode() === EDITOR_MODE__VIEW || isEditorViewOnly || !canEdit) {
|
|
50
50
|
const record = selection[0];
|
|
51
51
|
if (record.isDestroyed) {
|
|
52
52
|
return null;
|
|
@@ -114,7 +114,7 @@ function Form(props) {
|
|
|
114
114
|
// withEditor
|
|
115
115
|
isEditorViewOnly = false,
|
|
116
116
|
isSaving = false,
|
|
117
|
-
|
|
117
|
+
getEditorMode,
|
|
118
118
|
onCancel,
|
|
119
119
|
onSave,
|
|
120
120
|
onClose,
|
|
@@ -782,7 +782,7 @@ function Form(props) {
|
|
|
782
782
|
if (message) {
|
|
783
783
|
message = <Text className="text-[#f00]">{message}</Text>;
|
|
784
784
|
}
|
|
785
|
-
element = <VStack className="Form-VStack4 flex-1
|
|
785
|
+
element = <VStack className="Form-VStack4 flex-1">
|
|
786
786
|
{element}
|
|
787
787
|
{message}
|
|
788
788
|
</VStack>;
|
|
@@ -790,7 +790,7 @@ function Form(props) {
|
|
|
790
790
|
if (item.additionalEditButtons) {
|
|
791
791
|
const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
|
|
792
792
|
if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
|
|
793
|
-
element = <HStack className="Form-HStack5 flex-1 flex-wrap">
|
|
793
|
+
element = <HStack className="Form-HStack5 flex-1 flex-wrap items-center">
|
|
794
794
|
{element}
|
|
795
795
|
{buttons}
|
|
796
796
|
</HStack>;
|
|
@@ -883,7 +883,6 @@ function Form(props) {
|
|
|
883
883
|
min-h-[50px]
|
|
884
884
|
w-full
|
|
885
885
|
flex-none
|
|
886
|
-
pb-2
|
|
887
886
|
${error ? 'bg-[#fdd]' : ''}
|
|
888
887
|
`}
|
|
889
888
|
>
|
|
@@ -1116,7 +1115,7 @@ function Form(props) {
|
|
|
1116
1115
|
additionalButtons = buildAdditionalButtons(additionalEditButtons);
|
|
1117
1116
|
|
|
1118
1117
|
if (inArray(editorType, [EDITOR_TYPE__SIDE, EDITOR_TYPE__SMART, EDITOR_TYPE__WINDOWED]) &&
|
|
1119
|
-
isSingle &&
|
|
1118
|
+
isSingle && getEditorMode() === EDITOR_MODE__EDIT &&
|
|
1120
1119
|
(onBack || (onViewMode && !disableView))) {
|
|
1121
1120
|
modeHeader = <Toolbar>
|
|
1122
1121
|
<HStack className="flex-1 items-center">
|
|
@@ -1152,7 +1151,7 @@ function Form(props) {
|
|
|
1152
1151
|
/>}
|
|
1153
1152
|
</Toolbar>;
|
|
1154
1153
|
}
|
|
1155
|
-
if (
|
|
1154
|
+
if (getEditorMode() === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
|
|
1156
1155
|
formButtons.push(<Toolbar key="additionalButtonsToolbar" className="justify-end flex-wrap">
|
|
1157
1156
|
{additionalButtons}
|
|
1158
1157
|
</Toolbar>)
|
|
@@ -1169,7 +1168,7 @@ function Form(props) {
|
|
|
1169
1168
|
if (_.isEmpty(formState.dirtyFields) && !isPhantom) {
|
|
1170
1169
|
isSaveDisabled = true;
|
|
1171
1170
|
}
|
|
1172
|
-
if (onDelete &&
|
|
1171
|
+
if (onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle) {
|
|
1173
1172
|
showDeleteBtn = true;
|
|
1174
1173
|
}
|
|
1175
1174
|
if (!isEditorViewOnly && !hideResetButton) {
|
|
@@ -1207,7 +1206,7 @@ function Form(props) {
|
|
|
1207
1206
|
}
|
|
1208
1207
|
footerButtons =
|
|
1209
1208
|
<>
|
|
1210
|
-
{onDelete &&
|
|
1209
|
+
{onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle &&
|
|
1211
1210
|
|
|
1212
1211
|
<HStack className="flex-1 justify-start">
|
|
1213
1212
|
<Button
|
|
@@ -1259,7 +1258,7 @@ function Form(props) {
|
|
|
1259
1258
|
onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
|
|
1260
1259
|
isDisabled={isSaveDisabled}
|
|
1261
1260
|
className="text-white"
|
|
1262
|
-
text={
|
|
1261
|
+
text={getEditorMode() === EDITOR_MODE__ADD ? 'Add' : 'Save'}
|
|
1263
1262
|
/>}
|
|
1264
1263
|
|
|
1265
1264
|
{showSubmitBtn &&
|
|
@@ -24,7 +24,6 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
24
24
|
return <WrappedComponent {...props} ref={ref} isTree={isTree} />;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
let [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
|
|
28
27
|
const {
|
|
29
28
|
userCanEdit = true, // not permissions, but capability
|
|
30
29
|
userCanView = true,
|
|
@@ -69,6 +68,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
69
68
|
|
|
70
69
|
// withSelection
|
|
71
70
|
selection,
|
|
71
|
+
getSelection,
|
|
72
72
|
setSelection,
|
|
73
73
|
|
|
74
74
|
// withAlert
|
|
@@ -79,13 +79,20 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
79
79
|
listeners = useRef({}),
|
|
80
80
|
editorStateRef = useRef(),
|
|
81
81
|
newEntityDisplayValueRef = useRef(),
|
|
82
|
+
editorModeRef = useRef(EDITOR_MODE__VIEW),
|
|
83
|
+
isIgnoreNextSelectionChangeRef = useRef(false),
|
|
82
84
|
[currentRecord, setCurrentRecord] = useState(null),
|
|
83
85
|
[isAdding, setIsAdding] = useState(false),
|
|
84
86
|
[isSaving, setIsSaving] = useState(false),
|
|
85
87
|
[isEditorShown, setIsEditorShownRaw] = useState(false),
|
|
86
88
|
[isEditorViewOnly, setIsEditorViewOnly] = useState(canEditorViewOnly), // current state of whether editor is in view-only mode
|
|
87
|
-
[isIgnoreNextSelectionChange, setIsIgnoreNextSelectionChange] = useState(false),
|
|
88
89
|
[lastSelection, setLastSelection] = useState(),
|
|
90
|
+
setIsIgnoreNextSelectionChange = (bool) => {
|
|
91
|
+
isIgnoreNextSelectionChangeRef.current = bool;
|
|
92
|
+
},
|
|
93
|
+
getIsIgnoreNextSelectionChange = () => {
|
|
94
|
+
return isIgnoreNextSelectionChangeRef.current;
|
|
95
|
+
},
|
|
89
96
|
setIsEditorShown = (bool) => {
|
|
90
97
|
setIsEditorShownRaw(bool);
|
|
91
98
|
if (!bool && onEditorClose) {
|
|
@@ -96,8 +103,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
96
103
|
function doIt() {
|
|
97
104
|
setSelection(newSelection);
|
|
98
105
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
106
|
+
const
|
|
107
|
+
formState = editorStateRef.current,
|
|
108
|
+
selection = getSelection();
|
|
109
|
+
if (!_.isEmpty(formState?.dirtyFields) && newSelection !== selection && getEditorMode() === EDITOR_MODE__EDIT) {
|
|
101
110
|
confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
|
|
102
111
|
} else if (selection && selection[0] && !selection[0].isDestroyed && (selection[0]?.isPhantom || selection[0]?.isRemotePhantom)) {
|
|
103
112
|
confirm('This new record is unsaved. Are you sure you want to cancel editing? Changes will be lost.', async () => {
|
|
@@ -115,6 +124,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
115
124
|
listeners.current = obj;
|
|
116
125
|
// forceUpdate(); // we don't want to get into an infinite loop of renders. Simply directly assign the listeners in every child render
|
|
117
126
|
},
|
|
127
|
+
getEditorMode = () => {
|
|
128
|
+
return editorModeRef.current;
|
|
129
|
+
},
|
|
130
|
+
setEditorMode = (mode) => {
|
|
131
|
+
editorModeRef.current = mode;
|
|
132
|
+
},
|
|
118
133
|
getNewEntityDisplayValue = () => {
|
|
119
134
|
return newEntityDisplayValueRef.current;
|
|
120
135
|
},
|
|
@@ -124,6 +139,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
124
139
|
return;
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
const selection = getSelection();
|
|
127
143
|
let addValues = values;
|
|
128
144
|
|
|
129
145
|
if (Repository?.isLoading) {
|
|
@@ -221,6 +237,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
221
237
|
showPermissionsError(EDIT);
|
|
222
238
|
return;
|
|
223
239
|
}
|
|
240
|
+
const selection = getSelection();
|
|
224
241
|
if (_.isEmpty(selection) || (_.isArray(selection) && (selection.length > 1 || selection[0]?.isDestroyed))) {
|
|
225
242
|
return;
|
|
226
243
|
}
|
|
@@ -243,6 +260,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
243
260
|
if (_.isFunction(args)) {
|
|
244
261
|
cb = args;
|
|
245
262
|
}
|
|
263
|
+
const selection = getSelection();
|
|
246
264
|
if (_.isEmpty(selection) || (_.isArray(selection) && (selection.length > 1 || selection[0]?.isDestroyed))) {
|
|
247
265
|
return;
|
|
248
266
|
}
|
|
@@ -301,6 +319,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
301
319
|
showPermissionsError(DELETE);
|
|
302
320
|
return;
|
|
303
321
|
}
|
|
322
|
+
const selection = getSelection();
|
|
304
323
|
if (getListeners().onBeforeDelete) {
|
|
305
324
|
const listenerResult = await getListeners().onBeforeDelete(selection);
|
|
306
325
|
if (listenerResult === false) {
|
|
@@ -341,6 +360,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
341
360
|
|
|
342
361
|
// check permissions for view
|
|
343
362
|
|
|
363
|
+
const selection = getSelection();
|
|
344
364
|
if (selection.length !== 1) {
|
|
345
365
|
return;
|
|
346
366
|
}
|
|
@@ -363,7 +383,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
363
383
|
|
|
364
384
|
// check permissions for duplicate
|
|
365
385
|
|
|
366
|
-
|
|
386
|
+
const selection = getSelection();
|
|
367
387
|
if (selection.length !== 1) {
|
|
368
388
|
return;
|
|
369
389
|
}
|
|
@@ -384,6 +404,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
384
404
|
},
|
|
385
405
|
onRemoteDuplicate = async () => {
|
|
386
406
|
const
|
|
407
|
+
selection = getSelection(),
|
|
387
408
|
entity = selection[0],
|
|
388
409
|
duplicateEntity = await Repository.remoteDuplicate(entity);
|
|
389
410
|
|
|
@@ -392,14 +413,16 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
392
413
|
doEdit();
|
|
393
414
|
},
|
|
394
415
|
doEditorSave = async (data, e) => {
|
|
395
|
-
let mode =
|
|
416
|
+
let mode = getEditorMode() === EDITOR_MODE__ADD ? ADD : EDIT;
|
|
396
417
|
if (canUser && !canUser(mode)) {
|
|
397
418
|
showPermissionsError(mode);
|
|
398
419
|
return;
|
|
399
420
|
}
|
|
400
421
|
|
|
401
422
|
// NOTE: The Form submits onSave for both adds (when not isAutoSsave) and edits.
|
|
402
|
-
const
|
|
423
|
+
const
|
|
424
|
+
selection = getSelection(),
|
|
425
|
+
isSingle = selection.length === 1;
|
|
403
426
|
let useStaged = false;
|
|
404
427
|
if (isSingle) {
|
|
405
428
|
// just update this one entity
|
|
@@ -446,7 +469,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
446
469
|
if (onChange) {
|
|
447
470
|
onChange(selection);
|
|
448
471
|
}
|
|
449
|
-
if (
|
|
472
|
+
if (getEditorMode() === EDITOR_MODE__ADD) {
|
|
450
473
|
if (onAdd) {
|
|
451
474
|
await onAdd(selection);
|
|
452
475
|
}
|
|
@@ -459,7 +482,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
459
482
|
} else {
|
|
460
483
|
setEditorMode(EDITOR_MODE__VIEW);
|
|
461
484
|
}
|
|
462
|
-
} else if (
|
|
485
|
+
} else if (getEditorMode() === EDITOR_MODE__EDIT) {
|
|
463
486
|
if (getListeners().onAfterEdit) {
|
|
464
487
|
await getListeners().onAfterEdit(selection);
|
|
465
488
|
}
|
|
@@ -475,6 +498,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
475
498
|
doEditorCancel = () => {
|
|
476
499
|
async function doIt() {
|
|
477
500
|
const
|
|
501
|
+
selection = getSelection(),
|
|
478
502
|
isSingle = selection.length === 1,
|
|
479
503
|
isPhantom = selection[0] && !selection[0]?.isDestroyed && selection[0].isPhantom;
|
|
480
504
|
if (isSingle && isPhantom) {
|
|
@@ -513,9 +537,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
513
537
|
setIsEditorShown(false);
|
|
514
538
|
});
|
|
515
539
|
},
|
|
516
|
-
calculateEditorMode = (
|
|
540
|
+
calculateEditorMode = () => {
|
|
517
541
|
|
|
518
|
-
let
|
|
542
|
+
let isIgnoreNextSelectionChange = getIsIgnoreNextSelectionChange(),
|
|
543
|
+
doStayInEditModeOnSelectionChange = stayInEditModeOnSelectionChange;
|
|
519
544
|
if (!_.isNil(UiGlobals.stayInEditModeOnSelectionChange)) {
|
|
520
545
|
// allow global override to for this property
|
|
521
546
|
doStayInEditModeOnSelectionChange = UiGlobals.stayInEditModeOnSelectionChange;
|
|
@@ -525,13 +550,14 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
525
550
|
}
|
|
526
551
|
|
|
527
552
|
// calculateEditorMode gets called only on selection changes
|
|
553
|
+
const selection = getSelection();
|
|
528
554
|
let mode;
|
|
529
555
|
if (editorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
|
|
530
556
|
// special case: side editor is always edit mode
|
|
531
557
|
mode = EDITOR_MODE__EDIT;
|
|
532
558
|
} else {
|
|
533
559
|
if (isIgnoreNextSelectionChange) {
|
|
534
|
-
mode =
|
|
560
|
+
mode = getEditorMode();
|
|
535
561
|
if (!canEditorViewOnly && userCanEdit) {
|
|
536
562
|
if (selection.length > 1) {
|
|
537
563
|
if (!disableEdit) {
|
|
@@ -577,7 +603,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
577
603
|
};
|
|
578
604
|
|
|
579
605
|
useEffect(() => {
|
|
580
|
-
setEditorMode(calculateEditorMode(
|
|
606
|
+
setEditorMode(calculateEditorMode());
|
|
581
607
|
|
|
582
608
|
setIsIgnoreNextSelectionChange(false);
|
|
583
609
|
setLastSelection(selection);
|
|
@@ -598,7 +624,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
598
624
|
// NOTE: If I don't calculate this on the fly for selection changes,
|
|
599
625
|
// we see a flash of the previous state, since useEffect hasn't yet run.
|
|
600
626
|
// (basically redo what's in the useEffect, above)
|
|
601
|
-
|
|
627
|
+
setEditorMode(calculateEditorMode());
|
|
602
628
|
}
|
|
603
629
|
|
|
604
630
|
return <WrappedComponent
|
|
@@ -611,11 +637,13 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
611
637
|
isEditorViewOnly={isEditorViewOnly}
|
|
612
638
|
isAdding={isAdding}
|
|
613
639
|
isSaving={isSaving}
|
|
614
|
-
editorMode={
|
|
640
|
+
editorMode={getEditorMode()}
|
|
641
|
+
getEditorMode={getEditorMode}
|
|
615
642
|
onEditMode={setEditMode}
|
|
616
643
|
onViewMode={setViewMode}
|
|
617
644
|
editorStateRef={editorStateRef}
|
|
618
645
|
setIsEditorShown={setIsEditorShown}
|
|
646
|
+
setIsIgnoreNextSelectionChange={setIsIgnoreNextSelectionChange}
|
|
619
647
|
onAdd={(!userCanEdit || disableAdd) ? null : doAdd}
|
|
620
648
|
onEdit={(!userCanEdit || disableEdit) ? null : doEdit}
|
|
621
649
|
onDelete={(!userCanEdit || disableDelete) ? null : doDelete}
|
|
@@ -11,6 +11,7 @@ import Form from '../Form/Form.js';
|
|
|
11
11
|
import Pdf from '../Icons/Pdf.js';
|
|
12
12
|
import UiGlobals from '../../UiGlobals.js';
|
|
13
13
|
import inArray from '../../Functions/inArray.js';
|
|
14
|
+
import testProps from '../../Functions/testProps.js';
|
|
14
15
|
import _ from 'lodash';
|
|
15
16
|
|
|
16
17
|
export default function withPdfButtons(WrappedComponent) {
|
|
@@ -206,6 +207,8 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
206
207
|
h: 800,
|
|
207
208
|
w: styles.FORM_STACK_ROW_THRESHOLD + 10,
|
|
208
209
|
body: <Form
|
|
210
|
+
parent={self}
|
|
211
|
+
reference="chooseFieldsForm"
|
|
209
212
|
editorType={EDITOR_TYPE__PLAIN}
|
|
210
213
|
alert={alert}
|
|
211
214
|
columnDefaults={{
|
|
@@ -244,6 +247,8 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
244
247
|
w: 510, // 510 so it's over the stack threshold
|
|
245
248
|
h: 500,
|
|
246
249
|
body: <Form
|
|
250
|
+
parent={self}
|
|
251
|
+
reference="chooseEmailAddressForm"
|
|
247
252
|
submitBtnLabel='Email PDF'
|
|
248
253
|
onSubmit={(values)=> {
|
|
249
254
|
hideModal();
|
|
@@ -48,14 +48,14 @@ export default function withSelection(WrappedComponent) {
|
|
|
48
48
|
usesWithValue = !!setValue,
|
|
49
49
|
initialSelection = selection || defaultSelection || [],
|
|
50
50
|
forceUpdate = useForceUpdate(),
|
|
51
|
-
|
|
51
|
+
selectionRef = useRef(initialSelection),
|
|
52
52
|
[isReady, setIsReady] = useState(selection || false), // if selection is already defined, or value is not null and we don't need to load repository, it's ready
|
|
53
53
|
setSelection = (selection) => {
|
|
54
|
-
if (_.isEqual(selection,
|
|
54
|
+
if (_.isEqual(selection, getSelection())) {
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
selectionRef.current = selection;
|
|
59
59
|
if (onChangeSelection) {
|
|
60
60
|
onChangeSelection(selection);
|
|
61
61
|
}
|
|
@@ -64,6 +64,9 @@ export default function withSelection(WrappedComponent) {
|
|
|
64
64
|
}
|
|
65
65
|
forceUpdate();
|
|
66
66
|
},
|
|
67
|
+
getSelection = () => {
|
|
68
|
+
return selectionRef.current;
|
|
69
|
+
}
|
|
67
70
|
selectPrev = () => {
|
|
68
71
|
selectDirection(SELECT_UP);
|
|
69
72
|
},
|
|
@@ -103,16 +106,16 @@ export default function withSelection(WrappedComponent) {
|
|
|
103
106
|
}
|
|
104
107
|
},
|
|
105
108
|
addToSelection = (item) => {
|
|
106
|
-
const newSelection = _.clone(
|
|
109
|
+
const newSelection = _.clone(getSelection()); // so we get a new object, so descendants rerender
|
|
107
110
|
newSelection.push(item);
|
|
108
111
|
setSelection(newSelection);
|
|
109
112
|
},
|
|
110
113
|
removeFromSelection = (item) => {
|
|
111
114
|
let newSelection = [];
|
|
112
115
|
if (Repository) {
|
|
113
|
-
newSelection = _.remove(
|
|
116
|
+
newSelection = _.remove(getSelection(), (sel) => sel !== item);
|
|
114
117
|
} else {
|
|
115
|
-
newSelection = _.remove(
|
|
118
|
+
newSelection = _.remove(getSelection(), (sel) => sel[idIx] !== item[idIx]);
|
|
116
119
|
}
|
|
117
120
|
setSelection(newSelection);
|
|
118
121
|
},
|
|
@@ -126,7 +129,7 @@ export default function withSelection(WrappedComponent) {
|
|
|
126
129
|
// That way, after a load event, we'll keep the same selection, if possible.
|
|
127
130
|
const
|
|
128
131
|
newSelection = [],
|
|
129
|
-
ids = _.map(
|
|
132
|
+
ids = _.map(getSelection(), (item) => item.id);
|
|
130
133
|
_.each(ids, (id) => {
|
|
131
134
|
const found = Repository.getById(id);
|
|
132
135
|
if (found) {
|
|
@@ -160,9 +163,9 @@ export default function withSelection(WrappedComponent) {
|
|
|
160
163
|
selectRangeTo = (item) => {
|
|
161
164
|
// Select above max or below min to this one
|
|
162
165
|
const
|
|
163
|
-
currentSelectionLength =
|
|
166
|
+
currentSelectionLength = getSelection().length,
|
|
164
167
|
index = getIndexOfSelectedItem(item);
|
|
165
|
-
let newSelection = _.clone(
|
|
168
|
+
let newSelection = _.clone(getSelection()); // so we get a new object, so descendants rerender
|
|
166
169
|
|
|
167
170
|
if (currentSelectionLength) {
|
|
168
171
|
const { items, max, min, } = getMaxMinSelectionIndices();
|
|
@@ -189,10 +192,10 @@ export default function withSelection(WrappedComponent) {
|
|
|
189
192
|
},
|
|
190
193
|
isInSelection = (item) => {
|
|
191
194
|
if (Repository) {
|
|
192
|
-
return inArray(item,
|
|
195
|
+
return inArray(item, getSelection());
|
|
193
196
|
}
|
|
194
197
|
|
|
195
|
-
const found = _.find(
|
|
198
|
+
const found = _.find(getSelection(), (selectedItem) => {
|
|
196
199
|
return selectedItem[idIx] === item[idIx];
|
|
197
200
|
});
|
|
198
201
|
return !!found;
|
|
@@ -214,10 +217,10 @@ export default function withSelection(WrappedComponent) {
|
|
|
214
217
|
return found;
|
|
215
218
|
},
|
|
216
219
|
getIdsFromLocalSelection = () => {
|
|
217
|
-
if (!
|
|
220
|
+
if (!getSelection()[0]) {
|
|
218
221
|
return null;
|
|
219
222
|
}
|
|
220
|
-
const values = _.map(
|
|
223
|
+
const values = _.map(getSelection(), (item) => {
|
|
221
224
|
if (Repository) {
|
|
222
225
|
return item.id;
|
|
223
226
|
}
|
|
@@ -301,7 +304,7 @@ export default function withSelection(WrappedComponent) {
|
|
|
301
304
|
}
|
|
302
305
|
}
|
|
303
306
|
|
|
304
|
-
if (!_.isEqual(newSelection,
|
|
307
|
+
if (!_.isEqual(newSelection, getSelection())) {
|
|
305
308
|
setSelection(newSelection);
|
|
306
309
|
}
|
|
307
310
|
};
|
|
@@ -352,7 +355,7 @@ export default function withSelection(WrappedComponent) {
|
|
|
352
355
|
}, [value]);
|
|
353
356
|
|
|
354
357
|
if (self) {
|
|
355
|
-
self.selection =
|
|
358
|
+
self.selection = getSelection();
|
|
356
359
|
self.setSelection = setSelection;
|
|
357
360
|
self.selectPrev = selectPrev;
|
|
358
361
|
self.selectNext = selectNext;
|
|
@@ -395,7 +398,8 @@ export default function withSelection(WrappedComponent) {
|
|
|
395
398
|
{...props}
|
|
396
399
|
ref={ref}
|
|
397
400
|
disableWithSelection={false}
|
|
398
|
-
selection={
|
|
401
|
+
selection={getSelection()}
|
|
402
|
+
getSelection={getSelection}
|
|
399
403
|
setSelection={setSelection}
|
|
400
404
|
selectionMode={selectionMode}
|
|
401
405
|
selectPrev={selectPrev}
|
package/src/Constants/Dates.js
CHANGED
|
@@ -14,3 +14,4 @@ export const ONE_YEAR_AGO = moment().add(-1, 'years');
|
|
|
14
14
|
export const MOMENT_DATE_FORMAT_1 = 'YYYY-MM-DD HH:mm:ss';
|
|
15
15
|
export const MOMENT_DATE_FORMAT_2 = 'MMMM Do YYYY, h:mm:ss a';
|
|
16
16
|
export const MOMENT_DATE_FORMAT_3 = 'h:mm A';
|
|
17
|
+
export const MOMENT_DATE_FORMAT_4 = 'YYYY-MM-DD';
|
|
@@ -61,7 +61,7 @@ export function clickXButton(parentSelectors) {
|
|
|
61
61
|
}
|
|
62
62
|
export function clickXButtonIfEnabled(parentSelectors) {
|
|
63
63
|
cy.log('clickXButtonIfEnabled');
|
|
64
|
-
return clickButtonIfEnabled(parentSelectors, 'xBtn'
|
|
64
|
+
return clickButtonIfEnabled(parentSelectors, 'xBtn');
|
|
65
65
|
}
|
|
66
66
|
export function clickTrigger(parentSelectors) {
|
|
67
67
|
cy.log('clickTrigger');
|
|
@@ -91,29 +91,29 @@ export function toSideMode(parentSelectors) {
|
|
|
91
91
|
cy.log('toSideMode');
|
|
92
92
|
return clickButtonIfEnabled(parentSelectors, 'sideModeBtn');
|
|
93
93
|
}
|
|
94
|
-
export function clickButton(parentSelectors, name) { // requires the button to be enabled
|
|
94
|
+
export function clickButton(parentSelectors, name, options) { // requires the button to be enabled
|
|
95
95
|
if (_.isString(parentSelectors)) {
|
|
96
96
|
parentSelectors = [parentSelectors];
|
|
97
97
|
}
|
|
98
98
|
cy.log('clickButton ' + name);
|
|
99
|
-
return getDomNode([...parentSelectors, name])
|
|
99
|
+
return getDomNode([...parentSelectors, name], options)
|
|
100
100
|
.should('not.have.attr', 'data-disabled', 'true') // Check that the element is not disabled
|
|
101
101
|
.click({ force: true });
|
|
102
102
|
}
|
|
103
|
-
export function clickButtonIfEnabled(parentSelectors, name) { // allows button to be disabled
|
|
103
|
+
export function clickButtonIfEnabled(parentSelectors, name, options) { // allows button to be disabled
|
|
104
104
|
if (_.isString(parentSelectors)) {
|
|
105
105
|
parentSelectors = [parentSelectors];
|
|
106
106
|
}
|
|
107
|
-
return getDomNode([...parentSelectors, name])
|
|
107
|
+
return getDomNode([...parentSelectors, name], options)
|
|
108
108
|
.click({ force: true });
|
|
109
109
|
}
|
|
110
|
-
export function clickButtonIfExists(parentSelectors, name) {
|
|
110
|
+
export function clickButtonIfExists(parentSelectors, name, options) {
|
|
111
111
|
if (_.isString(parentSelectors)) {
|
|
112
112
|
parentSelectors = [parentSelectors];
|
|
113
113
|
}
|
|
114
|
-
return getDomNode([...parentSelectors, name]).if().then((node) => { // NOTE if() is a cypress-if function
|
|
114
|
+
return getDomNode([...parentSelectors, name], options).if().then((node) => { // NOTE if() is a cypress-if function
|
|
115
115
|
if (node) {
|
|
116
|
-
node.click();
|
|
116
|
+
cy.get(node).click();
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
119
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
fixInflector,
|
|
3
3
|
getLastPartOfPath,
|
|
4
4
|
bootstrapRouteWaiters,
|
|
5
|
+
stubWindowOpen,
|
|
5
6
|
} from './utilities.js';
|
|
6
7
|
import {
|
|
7
8
|
login,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
import {
|
|
12
13
|
getDomNode,
|
|
13
14
|
getDomNodes,
|
|
15
|
+
ifExists,
|
|
14
16
|
} from './dom_functions.js';
|
|
15
17
|
import {
|
|
16
18
|
hasRowWithFieldValue,
|
|
@@ -116,6 +118,39 @@ export function crudJson(selector, newData, editData, schema, ancillaryData, lev
|
|
|
116
118
|
// do nothing for now
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
// Editor
|
|
122
|
+
export function viewPdf(editorSelector, formSelector) {
|
|
123
|
+
ifExists(formSelector, 'viewPdfBtn', (btn) => {
|
|
124
|
+
clickButton(formSelector, 'viewPdfBtn');
|
|
125
|
+
|
|
126
|
+
cy.wait(1000); // allow time for modal to render
|
|
127
|
+
const modalSelector = editorSelector + '/chooseFieldsForm';
|
|
128
|
+
clickButton(modalSelector, 'submitBtn');
|
|
129
|
+
cy.wait(1000); // allow time for pdf to download
|
|
130
|
+
cy.get('@windowOpen').should('be.calledWithMatch', /viewModelPdf/);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function emailPdf(editorSelector, formSelector) {
|
|
134
|
+
ifExists(formSelector, 'emailPdfBtn', (btn) => {
|
|
135
|
+
clickButton(formSelector, 'emailPdfBtn');
|
|
136
|
+
|
|
137
|
+
cy.wait(1000); // allow time for modal to render
|
|
138
|
+
const modalSelector = editorSelector + '/chooseFieldsForm';
|
|
139
|
+
clickButton(modalSelector, 'submitBtn');
|
|
140
|
+
cy.wait(1000); // allow time for new modal to render
|
|
141
|
+
|
|
142
|
+
// UI now shows the email modal
|
|
143
|
+
fillForm(modalSelector, { email: 'scott@onehat.com', message: 'Sample message', }, { email: 'Input', message: 'TextArea', });
|
|
144
|
+
clickButton(modalSelector, 'submitBtn');
|
|
145
|
+
cy.wait('@emailModelPdf');
|
|
146
|
+
|
|
147
|
+
getDomNode('InfoModal')
|
|
148
|
+
.should('exist')
|
|
149
|
+
.should('contain', 'Email sent successfully.');
|
|
150
|
+
clickButton('InfoModal', 'okBtn');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
119
154
|
|
|
120
155
|
// Grid
|
|
121
156
|
export function crudWindowedGridRecord(gridSelector, newData, editData, schema, ancillaryData, level = 0) {
|
|
@@ -313,7 +348,11 @@ export function editGridRecord(gridSelector, fieldValues, schema, id, level = 0,
|
|
|
313
348
|
|
|
314
349
|
verifyNoErrorBox();
|
|
315
350
|
// cy.wait(1000);
|
|
316
|
-
|
|
351
|
+
|
|
352
|
+
if (whichEditor !== INLINE) {
|
|
353
|
+
viewPdf(editorSelector, formSelector);
|
|
354
|
+
emailPdf(editorSelector, formSelector);
|
|
355
|
+
}
|
|
317
356
|
}
|
|
318
357
|
export function editWindowedGridRecord(gridSelector, fieldValues, schema, id, level = 0) {
|
|
319
358
|
// edits the record as normal, then closes the editor window
|
|
@@ -542,6 +581,11 @@ export function editTreeRecord(treeSelector, fieldValues, schema, id, level = 0,
|
|
|
542
581
|
|
|
543
582
|
verifyNoErrorBox();
|
|
544
583
|
// cy.wait(1000);
|
|
584
|
+
|
|
585
|
+
if (whichEditor !== INLINE) {
|
|
586
|
+
viewPdf(editorSelector, formSelector);
|
|
587
|
+
emailPdf(editorSelector, formSelector);
|
|
588
|
+
}
|
|
545
589
|
|
|
546
590
|
}
|
|
547
591
|
export function editWindowedTreeRecord(treeSelector, fieldValues, schema, id, level = 0) {
|
|
@@ -591,6 +635,7 @@ export function runClosureTreeControlledManagerScreenCrudTests(model, schema, ne
|
|
|
591
635
|
navigateViaTabOrHomeButtonTo(url);
|
|
592
636
|
}
|
|
593
637
|
});
|
|
638
|
+
stubWindowOpen();
|
|
594
639
|
});
|
|
595
640
|
|
|
596
641
|
afterEach(function () {
|
|
@@ -688,6 +733,7 @@ export function runClosureTreeManagerScreenCrudTests(args) {
|
|
|
688
733
|
navigateViaTabOrHomeButtonTo(url);
|
|
689
734
|
}
|
|
690
735
|
});
|
|
736
|
+
stubWindowOpen();
|
|
691
737
|
});
|
|
692
738
|
|
|
693
739
|
// afterEach(function () {
|
|
@@ -753,6 +799,7 @@ export function runManagerScreenCrudTests(args) {
|
|
|
753
799
|
navigateViaTabOrHomeButtonTo(url);
|
|
754
800
|
}
|
|
755
801
|
});
|
|
802
|
+
stubWindowOpen();
|
|
756
803
|
});
|
|
757
804
|
|
|
758
805
|
// afterEach(function () {
|
|
@@ -806,11 +853,13 @@ export function runReportsManagerTests(reportData) {
|
|
|
806
853
|
beforeEach(function () {
|
|
807
854
|
bootstrapRouteWaiters();
|
|
808
855
|
login();
|
|
856
|
+
cy.restoreLocalStorage();
|
|
809
857
|
cy.url().then((currentUrl) => {
|
|
810
858
|
if (!currentUrl.endsWith(url)) {
|
|
811
859
|
navigateViaTabOrHomeButtonTo(url);
|
|
812
860
|
}
|
|
813
861
|
});
|
|
862
|
+
stubWindowOpen();
|
|
814
863
|
});
|
|
815
864
|
|
|
816
865
|
_.each(reportData, (report) => {
|
|
@@ -828,14 +877,14 @@ export function runReportsManagerTests(reportData) {
|
|
|
828
877
|
|
|
829
878
|
// Press Excel button
|
|
830
879
|
clickButton(selector, 'excelBtn');
|
|
831
|
-
cy.wait('@
|
|
880
|
+
cy.wait('@getReportWaiter', { timeout: 10000 }).then((interception) => {
|
|
832
881
|
expect(interception.response.headers['content-type']).to.include('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
833
882
|
});
|
|
834
883
|
|
|
835
884
|
|
|
836
885
|
// Press PDF button
|
|
837
886
|
clickButton(selector, 'pdfBtn');
|
|
838
|
-
cy.wait('@
|
|
887
|
+
cy.wait('@postReportWaiter', { timeout: 10000 }).then((interception) => {
|
|
839
888
|
expect(interception.response.headers['content-type']).to.include('pdf');
|
|
840
889
|
});
|
|
841
890
|
|
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
* @return Cypress chainer
|
|
9
9
|
*/
|
|
10
10
|
export function getDomNode(selectors, options = {}) {
|
|
11
|
-
|
|
11
|
+
let useFirst;
|
|
12
|
+
if (options?.hasOwnProperty('first')) {
|
|
13
|
+
useFirst = options.first;
|
|
14
|
+
delete options.first;
|
|
15
|
+
} else {
|
|
16
|
+
useFirst = true;
|
|
17
|
+
}
|
|
18
|
+
return cy.get(getTestIdSelectors(selectors, useFirst), options);
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
/**
|
|
@@ -22,6 +29,21 @@ export function getDomNodes(selectors, options = {}) {
|
|
|
22
29
|
return cy.get(getTestIdSelectors(selectors), options);
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Verifies that an element with the given selectors does not exist in the DOM.
|
|
34
|
+
* @argument {string | string[]} selectors - data-testid attribute values
|
|
35
|
+
* If an array is given, these will be considered nested selectors.
|
|
36
|
+
* e.g. ['parent', 'child'] will be converted to '[data-testid="parent"] [data-testid="child"]'
|
|
37
|
+
*/
|
|
38
|
+
export function verifyElementDoesNotExist(selectors) {
|
|
39
|
+
cy.get('body').then(($body) => {
|
|
40
|
+
const selectorString = getTestIdSelectors(selectors);
|
|
41
|
+
if ($body.find(selectorString).length > 0) {
|
|
42
|
+
throw new Error(`Element with selectors ${selectorString} exists in the DOM`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
/**
|
|
26
48
|
* Builds selector string for data-testid attributes.
|
|
27
49
|
* It leaves classname, id, and attribute selectors unchanged.
|
|
@@ -79,4 +101,15 @@ export function drag(draggableSelectors, droppableSelectors, options = {}) {
|
|
|
79
101
|
.then((success) => {
|
|
80
102
|
debugger;
|
|
81
103
|
});
|
|
82
|
-
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function ifExists(parentSelectors, name, cb) {
|
|
107
|
+
if (_.isString(parentSelectors)) {
|
|
108
|
+
parentSelectors = [parentSelectors];
|
|
109
|
+
}
|
|
110
|
+
return getDomNode([...parentSelectors, name]).if().then((node) => { // NOTE if() is a cypress-if function
|
|
111
|
+
if (node) {
|
|
112
|
+
cb(node);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -167,7 +167,7 @@ export function setTagValue(selectors, value) {
|
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
169
|
cy.get(selector).eq(0)
|
|
170
|
-
.click()
|
|
170
|
+
.click({ force: true })
|
|
171
171
|
.then(() => {
|
|
172
172
|
clickButtonsWithRemove(selector); // Recursive call for the next element
|
|
173
173
|
});
|
|
@@ -188,7 +188,9 @@ export function setTagValue(selectors, value) {
|
|
|
188
188
|
cy.get(field)
|
|
189
189
|
.wait(1000) // render
|
|
190
190
|
.type('{downarrow}')
|
|
191
|
-
.wait(500)
|
|
191
|
+
.wait(500) // allow time for selection
|
|
192
|
+
.type('{enter}')
|
|
193
|
+
.wait(250); // allow time to register enter key
|
|
192
194
|
});
|
|
193
195
|
|
|
194
196
|
// press trigger to hide dropdown
|
|
@@ -12,7 +12,14 @@ export function bootstrapRouteWaiters() {
|
|
|
12
12
|
cy.intercept('POST', '**/add**').as('addWaiter');
|
|
13
13
|
cy.intercept('POST', '**/edit**').as('editWaiter');
|
|
14
14
|
cy.intercept('POST', '**/delete**').as('deleteWaiter');
|
|
15
|
-
cy.intercept('
|
|
15
|
+
cy.intercept('GET', '**/getReport**').as('getReportWaiter');
|
|
16
|
+
cy.intercept('POST', '**/getReport**').as('postReportWaiter');
|
|
17
|
+
cy.intercept('POST', '**/emailModelPdf**').as('emailModelPdf');
|
|
18
|
+
}
|
|
19
|
+
export function stubWindowOpen() { // This needs to be called after the page in question has loaded. See https://docs.cypress.io/api/commands/stub#Replace-built-in-window-methods-like-prompt
|
|
20
|
+
cy.window().then((win) => {
|
|
21
|
+
cy.stub(win, 'open').as('windowOpen');
|
|
22
|
+
});
|
|
16
23
|
}
|
|
17
24
|
export function fixInflector(str) {
|
|
18
25
|
// inflector-js doesn't handle pluralization of 'equipment' correctly
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Icon,
|
|
3
|
-
} from '@project-components/Gluestack';
|
|
4
1
|
import Button from '../Components/Buttons/Button.js';
|
|
5
2
|
import testProps from './testProps.js';
|
|
6
3
|
import _ from 'lodash';
|
|
@@ -14,32 +11,28 @@ export default function buildAdditionalButtons(configs, self, handlerArgs = {})
|
|
|
14
11
|
handler,
|
|
15
12
|
icon,
|
|
16
13
|
isDisabled,
|
|
14
|
+
tooltip,
|
|
17
15
|
color = '#fff',
|
|
18
16
|
} = config,
|
|
19
17
|
buttonProps = {
|
|
20
18
|
key,
|
|
19
|
+
parent: self,
|
|
21
20
|
reference: key,
|
|
21
|
+
text,
|
|
22
|
+
icon,
|
|
23
|
+
isDisabled,
|
|
22
24
|
className: 'ml-2',
|
|
25
|
+
tooltip,
|
|
26
|
+
color,
|
|
23
27
|
};
|
|
24
28
|
if (handler) {
|
|
25
29
|
buttonProps.onPress = () => handler(handlerArgs);
|
|
26
30
|
}
|
|
27
|
-
if (icon) {
|
|
28
|
-
buttonProps.leftIcon = <Icon as={icon} size="sm" className="text-[#fff]" />;
|
|
29
|
-
}
|
|
30
|
-
if (isDisabled) {
|
|
31
|
-
buttonProps.isDisabled = isDisabled;
|
|
32
|
-
}
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
reference={key}
|
|
39
|
-
text={text}
|
|
40
|
-
{...buttonProps}
|
|
41
|
-
/>;
|
|
42
|
-
additionalButtons.push(button);
|
|
32
|
+
additionalButtons.push(<Button
|
|
33
|
+
{...testProps(key)}
|
|
34
|
+
{...buttonProps}
|
|
35
|
+
/>);
|
|
43
36
|
});
|
|
44
37
|
return additionalButtons;
|
|
45
38
|
}
|