@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.32",
3
+ "version": "0.4.34",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -15,7 +15,7 @@ function Editor(props) {
15
15
  onEditorSave: onSave,
16
16
  onEditorClose: onClose,
17
17
  onEditorDelete: onDelete,
18
- editorMode,
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 (editorMode === EDITOR_MODE__VIEW || isEditorViewOnly || !canEdit) {
49
+ if (getEditorMode() === EDITOR_MODE__VIEW || isEditorViewOnly || !canEdit) {
50
50
  const record = selection[0];
51
51
  if (record.isDestroyed) {
52
52
  return null;
@@ -81,6 +81,8 @@ export default function FieldSet(props) {
81
81
  mb-4
82
82
  mx-0
83
83
  p-1
84
+ pb-2
85
+ gap-2
84
86
  border
85
87
  border-grey-400
86
88
  ${styles.FORM_FIELDSET_BG}
@@ -114,7 +114,7 @@ function Form(props) {
114
114
  // withEditor
115
115
  isEditorViewOnly = false,
116
116
  isSaving = false,
117
- editorMode,
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 pt-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 && editorMode === EDITOR_MODE__EDIT &&
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 (editorMode === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
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 && editorMode === EDITOR_MODE__EDIT && isSingle) {
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 && editorMode === EDITOR_MODE__EDIT && isSingle &&
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={editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}
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 formState = editorStateRef.current;
100
- if (!_.isEmpty(formState?.dirtyFields) && newSelection !== selection && editorMode === EDITOR_MODE__EDIT) {
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 = editorMode === EDITOR_MODE__ADD ? ADD : EDIT;
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 isSingle = selection.length === 1;
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 (editorMode === EDITOR_MODE__ADD) {
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 (editorMode === EDITOR_MODE__EDIT) {
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 = (isIgnoreNextSelectionChange = false) => {
540
+ calculateEditorMode = () => {
517
541
 
518
- let doStayInEditModeOnSelectionChange = stayInEditModeOnSelectionChange;
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 = editorMode;
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(isIgnoreNextSelectionChange));
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
- editorMode = calculateEditorMode(isIgnoreNextSelectionChange);
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={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
- localSelection = useRef(initialSelection),
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, localSelection.current)) {
54
+ if (_.isEqual(selection, getSelection())) {
55
55
  return;
56
56
  }
57
57
 
58
- localSelection.current = selection;
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(localSelection.current); // so we get a new object, so descendants rerender
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(localSelection.current, (sel) => sel !== item);
116
+ newSelection = _.remove(getSelection(), (sel) => sel !== item);
114
117
  } else {
115
- newSelection = _.remove(localSelection.current, (sel) => sel[idIx] !== item[idIx]);
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(localSelection.current, (item) => item.id);
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 = localSelection.current.length,
166
+ currentSelectionLength = getSelection().length,
164
167
  index = getIndexOfSelectedItem(item);
165
- let newSelection = _.clone(localSelection.current); // so we get a new object, so descendants rerender
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, localSelection.current);
195
+ return inArray(item, getSelection());
193
196
  }
194
197
 
195
- const found = _.find(localSelection.current, (selectedItem) => {
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 (!localSelection.current[0]) {
220
+ if (!getSelection()[0]) {
218
221
  return null;
219
222
  }
220
- const values = _.map(localSelection.current, (item) => {
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, localSelection.current)) {
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 = localSelection.current;
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={localSelection.current}
401
+ selection={getSelection()}
402
+ getSelection={getSelection}
399
403
  setSelection={setSelection}
400
404
  selectionMode={selectionMode}
401
405
  selectPrev={selectPrev}
@@ -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', true);
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('@getWaiter', { timeout: 10000 }).then((interception) => {
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('@getReportWaiter', { timeout: 10000 }).then((interception) => {
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
- return cy.get(getTestIdSelectors(selectors, true), options);
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); // allow time for selection
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('POST', '**/getReport**').as('getReportWaiter');
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
- const button = <Button
35
- {...testProps('btn-' + key)}
36
- color={color}
37
- parent={self}
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
  }