@onehat/ui 0.3.267 → 0.3.269

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.3.267",
3
+ "version": "0.3.269",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  EDITOR_MODE__VIEW,
3
3
  } from '../../Constants/Editor.js';
4
+ import testProps from '../../Functions/testProps.js';
4
5
  import withComponent from '../Hoc/withComponent.js';
5
6
  import Form from '../Form/Form.js';
6
7
  import Viewer from '../Viewer/Viewer.js';
@@ -45,6 +46,7 @@ function Editor(props) {
45
46
  return null;
46
47
  }
47
48
  return <Viewer
49
+ {...testProps(self)}
48
50
  {...propsToPass}
49
51
  {..._viewer}
50
52
  record={record}
@@ -57,6 +59,7 @@ function Editor(props) {
57
59
  }
58
60
 
59
61
  return <Form
62
+ {...testProps(self)}
60
63
  {...propsToPass}
61
64
  {..._form}
62
65
  record={selection}
@@ -4,6 +4,7 @@ import {
4
4
  Text,
5
5
  } from 'native-base';
6
6
  import Date from '../Form/Field/Date.js';
7
+ import testProps from '../../Functions/testProps.js';
7
8
  import withTooltip from '../Hoc/withTooltip.js';
8
9
  import withValue from '../Hoc/withValue.js';
9
10
  import _ from 'lodash';
@@ -21,6 +22,7 @@ const
21
22
 
22
23
  minValue = 0,
23
24
  maxValue,
25
+ ...propsToPass
24
26
  } = props,
25
27
  [low, setLow] = useState(''),
26
28
  [high, setHigh] = useState(''),
@@ -75,8 +77,10 @@ const
75
77
  alignItems="center"
76
78
  flex={1}
77
79
  px={1}
80
+ {...propsToPass}
78
81
  >
79
82
  <Date
83
+ {...testProps('low')}
80
84
  value={low}
81
85
  onChangeValue={onChangeLow}
82
86
  mode={mode}
@@ -86,6 +90,7 @@ const
86
90
  />
87
91
  <Text px={2} userSelect="none">to</Text>
88
92
  <Date
93
+ {...testProps('high')}
89
94
  value={high}
90
95
  onChangeValue={onChangeHigh}
91
96
  mode={mode}
@@ -6,6 +6,7 @@ import {
6
6
  import Number from '../Form/Field/Number.js';
7
7
  import withTooltip from '../Hoc/withTooltip.js';
8
8
  import withValue from '../Hoc/withValue.js';
9
+ import testProps from '../../Functions/testProps.js';
9
10
  import _ from 'lodash';
10
11
 
11
12
  const
@@ -20,6 +21,7 @@ import _ from 'lodash';
20
21
 
21
22
  minValue = 0,
22
23
  maxValue,
24
+ ...propsToPass
23
25
  } = props,
24
26
  [low, setLow] = useState(''),
25
27
  [high, setHigh] = useState(''),
@@ -66,8 +68,10 @@ import _ from 'lodash';
66
68
  alignItems="center"
67
69
  flex={1}
68
70
  px={1}
71
+ {...propsToPass}
69
72
  >
70
73
  <Number
74
+ {...testProps('low')}
71
75
  value={low}
72
76
  onChangeValue={onChangeLow}
73
77
  startingValue={null}
@@ -78,6 +82,7 @@ import _ from 'lodash';
78
82
  />
79
83
  <Text px={2} userSelect="none">to</Text>
80
84
  <Number
85
+ {...testProps('high')}
81
86
  value={high}
82
87
  onChangeValue={onChangeHigh}
83
88
  startingValue={null}
@@ -8,12 +8,14 @@ const
8
8
  const {
9
9
  value,
10
10
  setValue,
11
+ testID = '',
11
12
  } = props,
12
13
  onToggle = () => {
13
14
  setValue(!value);
14
15
  };
15
16
 
16
17
  return <CheckboxButton
18
+ testID={testID}
17
19
  isChecked={value}
18
20
  onPress={onToggle}
19
21
  _icon={{
@@ -4,6 +4,7 @@ import {
4
4
  Checkbox,
5
5
  Row,
6
6
  } from 'native-base';
7
+ import testProps from '../../../../Functions/testProps.js';
7
8
  import withData from '../../../Hoc/withData.js';
8
9
  import withValue from '../../../Hoc/withValue.js';
9
10
  import withTooltip from '../../../Hoc/withTooltip.js';
@@ -32,6 +33,7 @@ const
32
33
  const entities = Repository.getEntitiesOnPage();
33
34
  checkboxes = _.map(entities, (entity, ix) => {
34
35
  return <Checkbox
36
+ {...testProps('checkbox-' + entity.id)}
35
37
  key={ix}
36
38
  value={entity.id}
37
39
  {...checkboxProps}
@@ -40,6 +42,7 @@ const
40
42
  } else {
41
43
  checkboxes = _.map(data, (datum, ix) => {
42
44
  return <Checkbox
45
+ {...testProps('checkbox-' + datum[idIx])}
43
46
  key={ix}
44
47
  value={datum[idIx]}
45
48
  {...checkboxProps}
@@ -22,6 +22,7 @@ import withComponent from '../../../Hoc/withComponent.js';
22
22
  import withData from '../../../Hoc/withData.js';
23
23
  import withValue from '../../../Hoc/withValue.js';
24
24
  import emptyFn from '../../../../Functions/emptyFn.js';
25
+ import testProps from '../../../../Functions/testProps.js';
25
26
  import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
26
27
  import IconButton from '../../../Buttons/IconButton.js';
27
28
  import CaretDown from '../../../Icons/CaretDown.js';
@@ -495,6 +496,7 @@ export function ComboComponent(props) {
495
496
 
496
497
  if (showXButton && !_.isNil(value)) {
497
498
  xButton = <IconButton
499
+ {...testProps('xBtn')}
498
500
  _icon={{
499
501
  as: Xmark,
500
502
  color: 'trueGray.600',
@@ -512,6 +514,7 @@ export function ComboComponent(props) {
512
514
  }
513
515
  if (showEyeButton && Editor && !_.isNil(value)) {
514
516
  eyeButton = <IconButton
517
+ {...testProps('eyeBtn')}
515
518
  _icon={{
516
519
  as: Eye,
517
520
  color: 'trueGray.600',
@@ -532,6 +535,7 @@ export function ComboComponent(props) {
532
535
  inputAndTrigger = <>
533
536
  {disableDirectEntry ?
534
537
  <Pressable
538
+ {...testProps('toggleMenuBtn')}
535
539
  onPress={toggleMenu}
536
540
  flex={1}
537
541
  h="100%"
@@ -558,6 +562,7 @@ export function ComboComponent(props) {
558
562
  >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
559
563
  </Pressable> :
560
564
  <Input
565
+ {...testProps('input')}
561
566
  ref={inputRef}
562
567
  reference="ComboInput"
563
568
  value={textInputValue}
@@ -583,6 +588,7 @@ export function ComboComponent(props) {
583
588
  {..._input}
584
589
  />}
585
590
  <IconButton
591
+ {...testProps('trigger')}
586
592
  ref={triggerRef}
587
593
  _icon={{
588
594
  as: CaretDown,
@@ -612,6 +618,7 @@ export function ComboComponent(props) {
612
618
  const displayValue = getDisplayValue();
613
619
  inputAndTrigger = <>
614
620
  <Pressable
621
+ {...testProps('showMenuBtn')}
615
622
  onPress={showMenu}
616
623
  flex={1}
617
624
  flexDirection="row"
@@ -637,6 +644,7 @@ export function ComboComponent(props) {
637
644
  >{_.isEmpty(displayValue) ? placeholder : displayValue}</Text>
638
645
  </Pressable>
639
646
  <IconButton
647
+ {...testProps('trigger')}
640
648
  ref={triggerRef}
641
649
  _icon={{
642
650
  as: CaretDown,
@@ -837,6 +845,7 @@ export function ComboComponent(props) {
837
845
  if (isEditor) {
838
846
  // in RN, an editor has no way to accept the selection of the grid, so we need to add a check button to do this
839
847
  checkButton = <IconButton
848
+ {...testProps('checkBtn')}
840
849
  _icon={{
841
850
  as: Check,
842
851
  color: 'trueGray.600',
@@ -878,6 +887,7 @@ export function ComboComponent(props) {
878
887
  }}
879
888
  >{textInputValue}</Text> :
880
889
  <Input
890
+ {...testProps('input')}
881
891
  ref={inputRef}
882
892
  reference="ComboInput"
883
893
  value={textInputValue}
@@ -903,6 +913,7 @@ export function ComboComponent(props) {
903
913
  {..._input}
904
914
  />}
905
915
  <IconButton
916
+ {...testProps('hideMenuBtn')}
906
917
  _icon={{
907
918
  as: CaretDown,
908
919
  color: 'primary.800',
@@ -27,8 +27,9 @@ import Xmark from '../../Icons/Xmark.js';
27
27
  import withComponent from '../../Hoc/withComponent.js';
28
28
  import withValue from '../../Hoc/withValue.js';
29
29
  import emptyFn from '../../../Functions/emptyFn.js';
30
- import Calendar from '../../Icons/Calendar.js';
30
+ import testProps from '../../../Functions/testProps.js';
31
31
  import getComponentFromType from '../../../Functions/getComponentFromType.js';
32
+ import Calendar from '../../Icons/Calendar.js';
32
33
  import moment from 'moment';
33
34
  import _ from 'lodash';
34
35
 
@@ -288,6 +289,7 @@ export function DateElement(props) {
288
289
 
289
290
  if (showXButton && !_.isNil(value)) {
290
291
  xButton = <IconButton
292
+ {...testProps('xBtn')}
291
293
  _icon={{
292
294
  as: Xmark,
293
295
  color: 'trueGray.600',
@@ -306,6 +308,7 @@ export function DateElement(props) {
306
308
  if (UiGlobals.mode === UI_MODE_WEB) {
307
309
  inputAndTrigger = <>
308
310
  <IconButton
311
+ {...testProps('trigger')}
309
312
  ref={triggerRef}
310
313
  _icon={{
311
314
  as: Calendar,
@@ -328,6 +331,7 @@ export function DateElement(props) {
328
331
  />
329
332
  {disableDirectEntry ?
330
333
  <Pressable
334
+ {...testProps('togglePickerBtn')}
331
335
  onPress={togglePicker}
332
336
  flex={1}
333
337
  h="100%"
@@ -354,6 +358,7 @@ export function DateElement(props) {
354
358
  >{_.isEmpty(textInputValue) ? placeholder : textInputValue}</Text>
355
359
  </Pressable> :
356
360
  <Input
361
+ {...testProps('input')}
357
362
  ref={inputRef}
358
363
  value={textInputValue}
359
364
  // setValue={onInputSetValue}
@@ -394,6 +399,7 @@ export function DateElement(props) {
394
399
  // The just show the current value and open the menu
395
400
  inputAndTrigger = <>
396
401
  <IconButton
402
+ {...testProps('trigger')}
397
403
  ref={triggerRef}
398
404
  _icon={{
399
405
  as: Calendar,
@@ -416,6 +422,7 @@ export function DateElement(props) {
416
422
  }}
417
423
  />
418
424
  <Pressable
425
+ {...testProps('togglePickerBtn')}
419
426
  onPress={togglePicker}
420
427
  flex={1}
421
428
  >
@@ -488,6 +495,7 @@ export function DateElement(props) {
488
495
  p={0}
489
496
  >
490
497
  <Datetime
498
+ {...testProps('picker')}
491
499
  open={true}
492
500
  input={false}
493
501
  closeOnClickOutside={false}
@@ -504,6 +512,7 @@ export function DateElement(props) {
504
512
  const inputAndTriggerClone = // for RN, this is the actual input and trigger, as we need them to appear up above in the modal
505
513
  <Row h={10}>
506
514
  <IconButton
515
+ {...testProps('hidePickerBtn')}
507
516
  _icon={{
508
517
  as: Calendar,
509
518
  color: styles.FORM_DATE_ICON_COLOR,
@@ -545,6 +554,7 @@ export function DateElement(props) {
545
554
  }}
546
555
  >{textInputValue}</Text> :
547
556
  <Input
557
+ {...testProps('input')}
548
558
  ref={inputRef}
549
559
  value={textInputValue}
550
560
  autoSubmit={true}
@@ -592,6 +602,7 @@ export function DateElement(props) {
592
602
  /> */}
593
603
  <Box bg="#fff">
594
604
  <Datetime
605
+ {...testProps('picker')}
595
606
  initialDate={moment(value).toDate()}
596
607
  selectedStartDate={moment(value).toDate()}
597
608
  onDateChange={onPickerChange}
@@ -7,6 +7,7 @@ import {
7
7
  } from 'native-base';
8
8
  import UiGlobals from '../../../UiGlobals.js';
9
9
  import IconButton from '../../Buttons/IconButton.js';
10
+ import testProps from '../../../Functions/testProps.js';
10
11
  import withComponent from '../../Hoc/withComponent.js';
11
12
  import withTooltip from '../../Hoc/withTooltip.js';
12
13
  import withValue from '../../Hoc/withValue.js';
@@ -120,6 +121,7 @@ function NumberElement(props) {
120
121
 
121
122
  return <Row flex={1} h="100%" p={0} borderWidth={1} borderColor="trueGray.400" borderRadius={6} {...props}>
122
123
  <IconButton
124
+ {...testProps('decrementBtn')}
123
125
  icon={<Icon as={Minus} color={(isDecrementDisabled || isDisabled) ? 'disabled' : 'trueGray.500'} />}
124
126
  onPress={onDecrement}
125
127
  isDisabled={isDecrementDisabled || isDisabled}
@@ -133,6 +135,7 @@ function NumberElement(props) {
133
135
  zIndex={10}
134
136
  />
135
137
  <InputWithTooltip
138
+ {...testProps('input')}
136
139
  value={inputValue}
137
140
  onChangeText={onChangeText}
138
141
  onKeyPress={onInputKeyPress}
@@ -150,6 +153,7 @@ function NumberElement(props) {
150
153
  {...props._input}
151
154
  />
152
155
  <IconButton
156
+ {...testProps('incrementBtn')}
153
157
  icon={<Icon as={Plus} color={(isIncrementDisabled || isDisabled) ? 'disabled' : 'trueGray.500'} />}
154
158
  onPress={onIncrement}
155
159
  isDisabled={isIncrementDisabled || isDisabled}
@@ -4,6 +4,7 @@ import {
4
4
  Radio,
5
5
  Row,
6
6
  } from 'native-base';
7
+ import testProps from '../../../../Functions/testProps.js';
7
8
  import withComponent from '../../../Hoc/withComponent.js';
8
9
  import withData from '../../../Hoc/withData.js';
9
10
  import withValue from '../../../Hoc/withValue.js';
@@ -34,6 +35,7 @@ const
34
35
  const entities = Repository.getEntitiesOnPage();
35
36
  radios = _.map(entities, (entity, ix) => {
36
37
  return <Radio
38
+ {...testProps('radio-' + entity.id)}
37
39
  key={ix}
38
40
  value={entity.id}
39
41
  {...radioProps}
@@ -42,6 +44,7 @@ const
42
44
  } else {
43
45
  radios = _.map(data, (datum, ix) => {
44
46
  return <Radio
47
+ {...testProps('radio-' + datum[idIx])}
45
48
  key={ix}
46
49
  value={datum[idIx]}
47
50
  {...radioProps}
@@ -16,8 +16,6 @@ import Combo, { ComboEditor } from '../Combo/Combo.js';
16
16
  import UiGlobals from '../../../../UiGlobals.js';
17
17
  import _ from 'lodash';
18
18
 
19
-
20
-
21
19
  function TagComponent(props) {
22
20
 
23
21
  const {
@@ -2,6 +2,7 @@ import {
2
2
  Row,
3
3
  Text,
4
4
  } from 'native-base';
5
+ import testProps from '../../../../Functions/testProps.js';
5
6
  import IconButton from '../../../Buttons/IconButton.js';
6
7
  import Eye from '../../../Icons/Eye.js';
7
8
  import Xmark from '../../../Icons/Xmark.js';
@@ -16,6 +17,7 @@ export default function ValueBox(props) {
16
17
  } = props,
17
18
  styles = UiGlobals.styles;
18
19
  return <Row
20
+ {...testProps('valueBox-' + text)}
19
21
  borderWidth={1}
20
22
  borderColor="trueGray.400"
21
23
  borderRadius="md"
@@ -25,6 +27,7 @@ export default function ValueBox(props) {
25
27
  maxWidth="100%"
26
28
  >
27
29
  <IconButton
30
+ {...testProps('eyeBtn')}
28
31
  _icon={{
29
32
  as: Eye,
30
33
  color: 'trueGray.600',
@@ -40,6 +43,7 @@ export default function ValueBox(props) {
40
43
  >{text}</Text>
41
44
  {onDelete &&
42
45
  <IconButton
46
+ {...testProps('xBtn')}
43
47
  _icon={{
44
48
  as: Xmark,
45
49
  color: 'trueGray.600',
@@ -9,6 +9,7 @@ import {
9
9
  import UiGlobals from '../../../UiGlobals.js';
10
10
  import IconButton from '../../Buttons/IconButton.js';
11
11
  import Na from '../../Icons/Na.js';
12
+ import testProps from '../../../Functions/testProps.js';
12
13
  import withComponent from '../../Hoc/withComponent.js';
13
14
  import withTooltip from '../../Hoc/withTooltip.js';
14
15
  import withValue from '../../Hoc/withValue.js';
@@ -43,6 +44,7 @@ const
43
44
 
44
45
  if (_.isNil(value)) {
45
46
  return <IconButton
47
+ {...testProps('naBtn')}
46
48
  ref={props.outerRef}
47
49
  icon={<Icon as={Na} color="trueGray.400" />}
48
50
  onPress={onToggle}
@@ -52,7 +54,10 @@ const
52
54
  }
53
55
 
54
56
  return <Row alignItems="center">
55
- <Pressable onPress={onNullify}>
57
+ <Pressable
58
+ {...testProps('nullifyBtn')}
59
+ onPress={onNullify}
60
+ >
56
61
  <Switch
57
62
  ref={props.outerRef}
58
63
  onToggle={onToggle}
@@ -68,8 +73,15 @@ const
68
73
  {...propsToPass}
69
74
  />
70
75
  </Pressable>
71
- <Pressable onPress={onToggle}>
72
- <Text ml={2} fontSize={styles.FORM_TOGGLE_FONTSIZE}>{_.isNil(value) ? 'N/A' : (!!value ? 'Yes' : 'No')}</Text>
76
+ <Pressable
77
+ {...testProps('readoutBtn')}
78
+ onPress={onToggle}
79
+ >
80
+ <Text
81
+ {...testProps('readout')}
82
+ ml={2}
83
+ fontSize={styles.FORM_TOGGLE_FONTSIZE}
84
+ >{_.isNil(value) ? 'N/A' : (!!value ? 'Yes' : 'No')}</Text>
73
85
  </Pressable>
74
86
  </Row>;
75
87
  },
@@ -7,6 +7,7 @@ import {
7
7
  } from 'native-base';
8
8
  import FieldSetContext from '../../Contexts/FieldSetContext.js';
9
9
  import useForceUpdate from '../../Hooks/useForceUpdate.js';
10
+ import testProps from '../../Functions/testProps.js';
10
11
  import UiGlobals from '../../UiGlobals.js';
11
12
  import IconButton from '../Buttons/IconButton.js';
12
13
  import CheckboxButton from '../Buttons/CheckboxButton.js';
@@ -111,6 +112,7 @@ export default function FieldSet(props) {
111
112
  numberOfLines={1}
112
113
  >Toggle All?</Text>
113
114
  <CheckboxButton
115
+ {...testProps('toggleAllBtn')}
114
116
  isChecked={getIsAllChecked()}
115
117
  onPress={onToggleAllChecked}
116
118
  _icon={{
@@ -119,6 +121,7 @@ export default function FieldSet(props) {
119
121
  />
120
122
  </Row>}
121
123
  {isCollapsible && <IconButton
124
+ {...testProps('toggleCollapseBtn')}
122
125
  _icon={{
123
126
  as: isLocalCollapsed ? <CaretDown /> : <CaretUp />,
124
127
  size: 'sm',
@@ -1,12 +1,3 @@
1
- import { useState, useEffect, } from 'react';
2
- import {
3
- Button,
4
- Column,
5
- Icon,
6
- Row,
7
- ScrollView,
8
- Text,
9
- } from 'native-base';
10
1
  import Form from './Form.js';
11
2
  import _ from 'lodash';
12
3
 
@@ -30,6 +30,7 @@ import withPdfButton from '../Hoc/withPdfButton.js';
30
30
  import inArray from '../../Functions/inArray.js';
31
31
  import getComponentFromType from '../../Functions/getComponentFromType.js';
32
32
  import buildAdditionalButtons from '../../Functions/buildAdditionalButtons.js';
33
+ import testProps from '../../Functions/testProps.js';
33
34
  import Button from '../Buttons/Button.js';
34
35
  import IconButton from '../Buttons/IconButton.js';
35
36
  import AngleLeft from '../Icons/AngleLeft.js';
@@ -286,6 +287,7 @@ function Form(props) {
286
287
  }
287
288
 
288
289
  let element = <Element
290
+ {...testProps('field-' + fieldName)}
289
291
  value={value}
290
292
  parent={self}
291
293
  reference={fieldName}
@@ -345,6 +347,7 @@ function Form(props) {
345
347
  }
346
348
 
347
349
  let element = <Element
350
+ {...testProps('field-' + name)}
348
351
  name={name}
349
352
  value={value}
350
353
  onChangeValue={(newValue) => {
@@ -499,6 +502,7 @@ function Form(props) {
499
502
  }
500
503
 
501
504
  let element = <Element
505
+ {...testProps('field-' + name)}
502
506
  value={value}
503
507
  parent={self}
504
508
  reference={name}
@@ -582,6 +586,7 @@ function Form(props) {
582
586
  dynamicProps = getDynamicProps({ fieldState, formSetValue, formGetValues, formState });
583
587
  }
584
588
  let element = <Element
589
+ {...testProps('field-' + name)}
585
590
  name={name}
586
591
  value={value}
587
592
  onChangeValue={(newValue) => {
@@ -700,6 +705,7 @@ function Form(props) {
700
705
  const
701
706
  Element = getComponentFromType(type),
702
707
  element = <Element
708
+ {...testProps('ancillary-' + type)}
703
709
  selectorId={selectorId}
704
710
  selectorSelected={selectorSelected || record}
705
711
  flex={1}
@@ -885,6 +891,7 @@ function Form(props) {
885
891
  formButtons.push(<Row key="buttonsRow" px={4} pt={4} alignItems="center" justifyContent="flex-end">
886
892
  {isSingle && editorMode === EDITOR_MODE__EDIT && onBack &&
887
893
  <Button
894
+ {...testProps('backBtn')}
888
895
  key="backBtn"
889
896
  onPress={onBack}
890
897
  leftIcon={<Icon as={AngleLeft} color="#fff" size="sm" />}
@@ -892,6 +899,7 @@ function Form(props) {
892
899
  >Back</Button>}
893
900
  {isSingle && editorMode === EDITOR_MODE__EDIT && onViewMode && !disableView &&
894
901
  <Button
902
+ {...testProps('viewBtn')}
895
903
  key="viewBtn"
896
904
  onPress={onViewMode}
897
905
  leftIcon={<Icon as={Eye} color="#fff" size="sm" />}
@@ -985,6 +993,7 @@ function Form(props) {
985
993
 
986
994
  <Row flex={1} justifyContent="flex-start">
987
995
  <Button
996
+ {...testProps('deleteBtn')}
988
997
  key="deleteBtn"
989
998
  onPress={onDelete}
990
999
  bg="warning"
@@ -997,6 +1006,7 @@ function Form(props) {
997
1006
 
998
1007
  {showResetBtn &&
999
1008
  <IconButton
1009
+ {...testProps('resetBtn')}
1000
1010
  key="resetBtn"
1001
1011
  onPress={() => doReset()}
1002
1012
  icon={Rotate}
@@ -1009,6 +1019,7 @@ function Form(props) {
1009
1019
 
1010
1020
  {showCancelBtn &&
1011
1021
  <Button
1022
+ {...testProps('cancelBtn')}
1012
1023
  key="cancelBtn"
1013
1024
  variant="ghost"
1014
1025
  onPress={onCancel}
@@ -1017,6 +1028,7 @@ function Form(props) {
1017
1028
 
1018
1029
  {showCloseBtn &&
1019
1030
  <Button
1031
+ {...testProps('closeBtn')}
1020
1032
  key="closeBtn"
1021
1033
  variant="ghost"
1022
1034
  onPress={onClose}
@@ -1025,6 +1037,7 @@ function Form(props) {
1025
1037
 
1026
1038
  {showSaveBtn &&
1027
1039
  <Button
1040
+ {...testProps('saveBtn')}
1028
1041
  key="saveBtn"
1029
1042
  onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
1030
1043
  isDisabled={isSaveDisabled}
@@ -1033,6 +1046,7 @@ function Form(props) {
1033
1046
 
1034
1047
  {showSubmitBtn &&
1035
1048
  <Button
1049
+ {...testProps('submitBtn')}
1036
1050
  key="submitBtn"
1037
1051
  onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
1038
1052
  isDisabled={isSubmitDisabled}
@@ -1041,6 +1055,7 @@ function Form(props) {
1041
1055
 
1042
1056
  {additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
1043
1057
  return <Button
1058
+ {...testProps('additionalFooterBtn-' + props.key)}
1044
1059
  {...props}
1045
1060
  onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
1046
1061
  >{props.text}</Button>;
@@ -47,6 +47,7 @@ import setSaved from '../../Functions/setSaved.js';
47
47
  import getIconButtonFromConfig from '../../Functions/getIconButtonFromConfig.js';
48
48
  import testProps from '../../Functions/testProps.js';
49
49
  import nbToRgb from '../../Functions/nbToRgb.js';
50
+ import testProps from '../../Functions/testProps.js';
50
51
  import Loading from '../Messages/Loading.js';
51
52
  import GridHeaderRow from './GridHeaderRow.js';
52
53
  import GridRow, { DragSourceDropTargetGridRow, DragSourceGridRow, DropTargetGridRow } from './GridRow.js';
@@ -336,7 +337,7 @@ function GridComponent(props) {
336
337
 
337
338
  let rowComponent =
338
339
  <Pressable
339
- // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
340
+ {...testProps((Repository ? Repository.schema.name : 'GridRow') + '-' + item?.id)}
340
341
  onPress={(e) => {
341
342
  if (e.preventDefault && e.cancelable) {
342
343
  e.preventDefault();
@@ -979,7 +980,7 @@ function GridComponent(props) {
979
980
  }
980
981
 
981
982
  let grid = <FlatList
982
- testID="flatlist"
983
+ {...testProps('flatlist')}
983
984
  ref={gridRef}
984
985
  scrollEnabled={CURRENT_MODE === UI_MODE_WEB}
985
986
  nestedScrollEnabled={true}
@@ -1019,7 +1020,7 @@ function GridComponent(props) {
1019
1020
  if (Repository?.isLoading) {
1020
1021
  grid = <Loading isScreen={true} />;
1021
1022
  } else {
1022
- grid = <NoRecordsFound text={noneFoundText} onRefresh={onRefresh} />;
1023
+ grid = <NoRecordsFound text={noneFoundText} onRefresh={onRefresh} testID="NoRecordsFound" />;
1023
1024
  }
1024
1025
  }
1025
1026
 
@@ -1060,8 +1061,8 @@ function GridComponent(props) {
1060
1061
  }
1061
1062
 
1062
1063
  grid = <Column
1063
- {...testProps('Grid')}
1064
- testID="outerContainer"
1064
+ {...testProps(self)}
1065
+ // testID="outerContainer"
1065
1066
  ref={containerRef}
1066
1067
  tabIndex={0}
1067
1068
  onKeyDown={onGridKeyDown}
@@ -1075,7 +1076,7 @@ function GridComponent(props) {
1075
1076
  {topToolbar}
1076
1077
 
1077
1078
  <Column
1078
- testID="gridContainer"
1079
+ {...testProps('gridContainer')}
1079
1080
  ref={gridContainerRef}
1080
1081
  w="100%"
1081
1082
  flex={1}
@@ -1098,6 +1099,7 @@ function GridComponent(props) {
1098
1099
 
1099
1100
  if (isDropTarget) {
1100
1101
  grid = <Box
1102
+ {...testProps('dropTarget')}
1101
1103
  ref={dropTargetRef}
1102
1104
  borderWidth={canDrop && isOver ? 4 : 0}
1103
1105
  borderColor="#0ff"
@@ -18,6 +18,7 @@ import {
18
18
  } from '../../Constants/UiModes.js';
19
19
  import UiGlobals from '../../UiGlobals.js';
20
20
  import useBlocking from '../../Hooks/useBlocking.js';
21
+ import testProps from '../../Functions/testProps.js';
21
22
  import AngleRight from '../Icons/AngleRight.js';
22
23
  import HeaderReorderHandle from './HeaderReorderHandle.js';
23
24
  import HeaderResizeHandle from './HeaderResizeHandle.js';
@@ -342,6 +343,7 @@ export default function GridHeaderRow(props) {
342
343
  textProps.textOverflow = 'ellipsis';
343
344
  }
344
345
  return <Pressable
346
+ {...testProps('Header-' + fieldName)}
345
347
  key={ix}
346
348
  onPress={(e) => {
347
349
  if (e.preventDefault) {
@@ -10,6 +10,7 @@ import {
10
10
  import getComponentFromType from '../../Functions/getComponentFromType.js';
11
11
  import UiGlobals from '../../UiGlobals.js';
12
12
  import { withDragSource, withDropTarget } from '../Hoc/withDnd.js';
13
+ import testProps from '../../Functions/testProps.js';
13
14
  import AngleRight from '../Icons/AngleRight.js';
14
15
  import RowDragHandle from './RowDragHandle.js';
15
16
  import _ from 'lodash';
@@ -102,7 +103,12 @@ function GridRow(props) {
102
103
  userSelect: 'none',
103
104
  };
104
105
 
105
- return <Row key={key} {...propsToPass} {...extraProps}>{config.renderer(item)}</Row>;
106
+ return <Row
107
+ key={key}
108
+ {...testProps('rendererCol-' + key)}
109
+ {...propsToPass}
110
+ {...extraProps}
111
+ >{config.renderer(item)}</Row>;
106
112
  }
107
113
  if (config.fieldName) {
108
114
  if (item?.properties && item.properties[config.fieldName]) {
@@ -120,6 +126,7 @@ function GridRow(props) {
120
126
  elementProps.isViewOnly = true; // TODO: this won't work for InlineGridEditor, bc that Grid can't use isViewOnly when actually editing
121
127
  }
122
128
  return <Element
129
+ {...testProps('fieldname-' + config.fieldName)}
123
130
  value={value}
124
131
  key={key}
125
132
  overflow="hidden"
@@ -160,6 +167,7 @@ function GridRow(props) {
160
167
  elementProps.textOverflow = 'ellipsis';
161
168
  }
162
169
  return <Text
170
+ {...testProps('fieldname-' + config.fieldName)}
163
171
  key={key}
164
172
  overflow="hidden"
165
173
  alignSelf="center"
@@ -409,6 +409,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
409
409
  secondarySetIsEditorShown(false);
410
410
  }
411
411
  const formState = secondaryEditorStateRef.current;
412
+ if (!formState) {
413
+ setIsAdding(false);
414
+ secondarySetIsEditorShown(false);
415
+ return;
416
+ }
412
417
  if (!_.isEmpty(formState.dirtyFields)) {
413
418
  confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
414
419
  } else {
@@ -14,6 +14,7 @@ import {
14
14
  UI_MODE_WEB,
15
15
  CURRENT_MODE,
16
16
  } from '../../Constants/UiModes.js';
17
+ import testProps from '../../Functions/testProps.js';
17
18
  import _ from 'lodash';
18
19
 
19
20
  const CONTEXT_MENU_WIDTH = 180;
@@ -104,6 +105,7 @@ export default function withContextMenu(WrappedComponent) {
104
105
  // </div>
105
106
 
106
107
  return <Pressable
108
+ {...testProps('contextMenuBtn-' + text)}
107
109
  key={ix}
108
110
  onPress={() => {
109
111
  setIsContextMenuShown(false);
@@ -408,6 +408,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
408
408
  setIsEditorShown(false);
409
409
  }
410
410
  const formState = editorStateRef.current;
411
+ if (!formState) {
412
+ setIsAdding(false);
413
+ setIsEditorShown(false);
414
+ return;
415
+ }
411
416
  if (!_.isEmpty(formState.dirtyFields)) {
412
417
  confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
413
418
  } else {
@@ -14,6 +14,7 @@ import {
14
14
  } from '../../Constants/Filters.js';
15
15
  import Inflector from 'inflector-js';
16
16
  import inArray from '../../Functions/inArray.js';
17
+ import testProps from '../../Functions/testProps.js';
17
18
  import getComponentFromType from '../../Functions/getComponentFromType.js';
18
19
  import IconButton from '../Buttons/IconButton.js';
19
20
  import FormPanel from '../Panel/FormPanel.js';
@@ -299,7 +300,6 @@ export default function withFilters(WrappedComponent) {
299
300
  }
300
301
  }
301
302
  if (!Element) {
302
- debugger;
303
303
  return; // to protect against errors
304
304
  }
305
305
  if (field === 'q') {
@@ -309,6 +309,7 @@ export default function withFilters(WrappedComponent) {
309
309
 
310
310
  const tooltip = filter.tooltip || title;
311
311
  let filterElement = <Element
312
+ {...testProps('filter-' + field)}
312
313
  key={'filter-' + field}
313
314
  tooltip={tooltip}
314
315
  placeholder={tooltip}
@@ -434,30 +435,34 @@ export default function withFilters(WrappedComponent) {
434
435
  </ScrollView>
435
436
  </Row>
436
437
  <Row flex={hasFilters ? null : 1} alignItems="center" alignSelf="flex-end">
437
- {showClearFiltersButton && <IconButton
438
- key="clearFiltersBtn"
439
- _icon={{
440
- as: Ban,
441
- }}
442
- ml={1}
443
- onPress={onClearFilters}
444
- tooltip="Clear all filters"
445
- />}
446
- {showFilterSelector && !isUsingCustomFilters && <IconButton
447
- key="gear"
448
- _icon={{
449
- as: Gear,
450
- }}
451
- ml={1}
452
- onPress={() => {
453
- const f = filters;
454
- const s = slots;
455
- setModalFilters(filters);
456
- setModalSlots(slots);
457
- setIsFilterSelectorShown(true);
458
- }}
459
- tooltip="Swap filters"
460
- />}
438
+ {showClearFiltersButton &&
439
+ <IconButton
440
+ {...testProps('clearFiltersBtn')}
441
+ key="clearFiltersBtn"
442
+ _icon={{
443
+ as: Ban,
444
+ }}
445
+ ml={1}
446
+ onPress={onClearFilters}
447
+ tooltip="Clear all filters"
448
+ />}
449
+ {showFilterSelector && !isUsingCustomFilters &&
450
+ <IconButton
451
+ {...testProps('swapFiltersBtn')}
452
+ key="swapFiltersBtn"
453
+ _icon={{
454
+ as: Gear,
455
+ }}
456
+ ml={1}
457
+ onPress={() => {
458
+ const f = filters;
459
+ const s = slots;
460
+ setModalFilters(filters);
461
+ setModalSlots(slots);
462
+ setIsFilterSelectorShown(true);
463
+ }}
464
+ tooltip="Swap filters"
465
+ />}
461
466
  </Row>
462
467
  </Toolbar>;
463
468
 
@@ -14,6 +14,7 @@ import {
14
14
  UI_MODE_REACT_NATIVE,
15
15
  } from '../../Constants/UiModes.js';
16
16
  import UiGlobals from '../../UiGlobals.js';
17
+ import testProps from '../../Functions/testProps.js';
17
18
  import Form from '../Form/Form.js';
18
19
  import withEditor from './withEditor.js';
19
20
  import _ from 'lodash';
@@ -115,7 +116,6 @@ export default function withInlineEditor(WrappedComponent, skipWrappers = false)
115
116
  inlineEditor = <>
116
117
  {isEditorShown && <Box
117
118
  ref={maskRef}
118
- testID="mask"
119
119
  position="fixed"
120
120
  w="100vw"
121
121
  h="100vh"
@@ -140,7 +140,7 @@ export default function withInlineEditor(WrappedComponent, skipWrappers = false)
140
140
  ref={inlineEditorRef}
141
141
  position="absolute"
142
142
  zIndex={10}
143
- testID="inline-editor"
143
+ {...testProps('inlineEditor')}
144
144
  h={isEditorShown ? '100px' : 0}
145
145
  minWidth="100%"
146
146
  display="inline-block"
@@ -808,7 +808,7 @@ function TreeComponent(props) {
808
808
  let nodeProps = getNodeProps ? getNodeProps(item) : {},
809
809
  isSelected = isInSelection(item);
810
810
  return <Pressable
811
- // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
811
+ {...testProps((Repository ? Repository.schema.name : 'TreeNode') + '-' + item?.id)}
812
812
  key={item.hash}
813
813
  onPress={(e) => {
814
814
  if (e.preventDefault && e.cancelable) {
@@ -818,6 +818,7 @@ function TreeComponent(props) {
818
818
  return
819
819
  }
820
820
  switch (e.detail) {
821
+ case 0: // simulated click
821
822
  case 1: // single click
822
823
  onNodeClick(item, e); // sets selection
823
824
  break;
@@ -1175,7 +1176,7 @@ function TreeComponent(props) {
1175
1176
 
1176
1177
  return <>
1177
1178
  <Column
1178
- {...testProps('Tree')}
1179
+ {...testProps(self)}
1179
1180
  flex={1}
1180
1181
  w="100%"
1181
1182
  >
@@ -9,6 +9,7 @@ import {
9
9
  import UiGlobals from '../../UiGlobals.js';
10
10
  import withDraggable from '../Hoc/withDraggable.js';
11
11
  import IconButton from '../Buttons/IconButton.js';
12
+ import testProps from '../../Functions/testProps.js';
12
13
  import _ from 'lodash';
13
14
 
14
15
  // This was broken out from Tree simply so we can memoize it
@@ -53,7 +54,7 @@ export default function TreeNode(props) {
53
54
 
54
55
  {isLoading ?
55
56
  <Spinner px={2} /> :
56
- (hasChildren && !isDragMode ? <IconButton icon={icon} onPress={() => onToggle(datum)} /> : <Icon as={icon} px={2} />)}
57
+ (hasChildren && !isDragMode ? <IconButton icon={icon} onPress={() => onToggle(datum)} {...testProps('TreeNodeExpandBtn-' + item?.id)} /> : <Icon as={icon} px={2} />)}
57
58
 
58
59
  <Text
59
60
  overflow="hidden"
@@ -15,6 +15,7 @@ import withPdfButton from '../Hoc/withPdfButton.js';
15
15
  import inArray from '../../Functions/inArray.js';
16
16
  import getComponentFromType from '../../Functions/getComponentFromType.js';
17
17
  import buildAdditionalButtons from '../../Functions/buildAdditionalButtons.js';
18
+ import testProps from '../../Functions/testProps.js';
18
19
  import Button from '../Buttons/Button.js';
19
20
  import Label from '../Form/Label.js';
20
21
  import Pencil from '../Icons/Pencil.js';
@@ -152,6 +153,7 @@ function Viewer(props) {
152
153
  }
153
154
 
154
155
  let element = <Element
156
+ {...testProps('field-' + name)}
155
157
  value={value}
156
158
  isEditable={false}
157
159
  parent={self}
@@ -195,6 +197,7 @@ function Viewer(props) {
195
197
  const
196
198
  Element = getComponentFromType(type),
197
199
  element = <Element
200
+ {...testProps('ancillary-' + type)}
198
201
  selectorId={selectorId}
199
202
  selectorSelected={selectorSelected || record}
200
203
  flex={1}
@@ -253,6 +256,7 @@ function Viewer(props) {
253
256
  {canEdit && onEditMode &&
254
257
  <Row px={4} pt={4} alignItems="center" justifyContent="flex-end">
255
258
  <Button
259
+ {...testProps('editBtn')}
256
260
  key="editBtn"
257
261
  onPress={onEditMode}
258
262
  leftIcon={<Icon as={Pencil} color="#fff" size="sm" />}
@@ -274,6 +278,7 @@ function Viewer(props) {
274
278
  {showDeleteBtn &&
275
279
  <Row flex={1} justifyContent="flex-start">
276
280
  <Button
281
+ {...testProps('deleteBtn')}
277
282
  key="deleteBtn"
278
283
  onPress={onDelete}
279
284
  bg="warning"
@@ -285,6 +290,7 @@ function Viewer(props) {
285
290
  </Row>}
286
291
  {onClose && showCloseBtn &&
287
292
  <Button
293
+ {...testProps('closeBtn')}
288
294
  key="closeBtn"
289
295
  onPress={onClose}
290
296
  color="#fff"
@@ -2,6 +2,7 @@ import {
2
2
  Icon,
3
3
  } from 'native-base';
4
4
  import Button from '../Components/Buttons/Button.js';
5
+ import testProps from './testProps.js';
5
6
  import _ from 'lodash';
6
7
 
7
8
  export default function buildAdditionalButtons(configs, self, handlerArgs = {}) {
@@ -30,6 +31,7 @@ export default function buildAdditionalButtons(configs, self, handlerArgs = {})
30
31
  }
31
32
 
32
33
  const button = <Button
34
+ {...testProps('btn-' + key)}
33
35
  color={color}
34
36
  ml={2}
35
37
  // mb={2}
@@ -1,8 +1,18 @@
1
1
  import { Platform } from "react-native";
2
2
 
3
+ // This adds a data-testid attribute to the DOM node,
4
+ // which can be quried in Cypress by: document.querySelector(`[data-testid='MyTestId']`);
5
+
3
6
  export default function testProps(id) {
4
- if (typeof __DEV__ !== 'undefined' && !__DEV__) {
5
- return {};
7
+ if (typeof __DEV__ === 'undefined' || !__DEV__) {
8
+ return {}; // don't add test props in production
9
+ }
10
+ if (id?.reference) {
11
+ // id is actually 'self' object
12
+ id = id.reference;
13
+ }
14
+ if (id.match(/\s/g)) {
15
+ id = id.replace(/\s/g, '_'); // convert any spaces to underscores
6
16
  }
7
17
  if (!window && Platform.OS === 'android') {
8
18
  return {