@xh/hoist 59.2.0 → 59.3.1

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +71 -14
  2. package/admin/AppComponent.ts +1 -1
  3. package/admin/tabs/activity/ActivityTab.ts +1 -1
  4. package/admin/tabs/general/GeneralTab.ts +2 -2
  5. package/admin/tabs/general/config/ConfigPanel.ts +1 -0
  6. package/admin/tabs/monitor/MonitorTab.ts +1 -1
  7. package/admin/tabs/server/ServerTab.ts +1 -1
  8. package/admin/tabs/userData/UserDataTab.ts +1 -1
  9. package/cmp/ag-grid/AgGrid.scss +51 -25
  10. package/cmp/ag-grid/AgGrid.ts +8 -2
  11. package/cmp/badge/Badge.ts +18 -5
  12. package/cmp/chart/Chart.ts +13 -11
  13. package/cmp/clock/Clock.ts +6 -5
  14. package/cmp/dataview/DataView.ts +5 -3
  15. package/cmp/form/Form.ts +25 -6
  16. package/cmp/grid/Grid.ts +41 -25
  17. package/cmp/grid/GridModel.ts +2 -2
  18. package/cmp/grid/Types.ts +1 -1
  19. package/cmp/grid/columns/Column.ts +45 -2
  20. package/cmp/grid/impl/GridHScrollbar.ts +140 -0
  21. package/cmp/grid/renderers/MultiFieldRenderer.ts +1 -1
  22. package/cmp/input/HoistInputModel.ts +4 -4
  23. package/cmp/input/HoistInputProps.ts +3 -1
  24. package/cmp/layout/Box.ts +4 -2
  25. package/cmp/relativetimestamp/RelativeTimestamp.ts +106 -40
  26. package/cmp/store/StoreFilterField.ts +2 -2
  27. package/cmp/tab/TabContainer.ts +1 -1
  28. package/cmp/zoneGrid/Types.ts +47 -0
  29. package/cmp/zoneGrid/ZoneGrid.ts +62 -0
  30. package/cmp/zoneGrid/ZoneGridModel.ts +666 -0
  31. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +143 -0
  32. package/cmp/zoneGrid/impl/ZoneMapperModel.ts +335 -0
  33. package/cmp/zoneGrid/index.ts +3 -0
  34. package/core/HoistComponent.ts +23 -10
  35. package/core/HoistProps.ts +25 -6
  36. package/core/XH.ts +49 -27
  37. package/core/elem.ts +11 -3
  38. package/core/exception/ExceptionHandler.ts +1 -0
  39. package/core/impl/InstanceManager.ts +24 -1
  40. package/core/model/HoistModel.ts +4 -4
  41. package/data/RecordAction.ts +7 -4
  42. package/data/StoreRecord.ts +8 -1
  43. package/desktop/appcontainer/AppContainer.ts +2 -0
  44. package/desktop/appcontainer/ExceptionDialog.ts +1 -1
  45. package/desktop/cmp/appbar/AppBar.ts +8 -6
  46. package/desktop/cmp/button/Button.ts +14 -3
  47. package/desktop/cmp/button/ButtonGroup.ts +14 -3
  48. package/desktop/cmp/button/ZoneMapperButton.ts +82 -0
  49. package/desktop/cmp/button/index.ts +1 -0
  50. package/desktop/cmp/dash/canvas/DashCanvas.ts +14 -4
  51. package/desktop/cmp/dash/container/DashContainer.ts +11 -4
  52. package/desktop/cmp/error/ErrorMessage.ts +9 -8
  53. package/desktop/cmp/form/FormField.ts +34 -10
  54. package/desktop/cmp/grid/columns/Actions.ts +2 -1
  55. package/desktop/cmp/grid/impl/colchooser/ColChooser.ts +3 -2
  56. package/desktop/cmp/grouping/GroupingChooser.ts +29 -29
  57. package/desktop/cmp/input/ButtonGroupInput.ts +1 -1
  58. package/desktop/cmp/input/Checkbox.ts +3 -3
  59. package/desktop/cmp/input/CodeInput.ts +2 -1
  60. package/desktop/cmp/input/DateInput.ts +128 -123
  61. package/desktop/cmp/input/JsonInput.ts +1 -1
  62. package/desktop/cmp/input/NumberInput.ts +3 -2
  63. package/desktop/cmp/input/RadioInput.ts +3 -1
  64. package/desktop/cmp/input/Select.ts +31 -4
  65. package/desktop/cmp/input/SwitchInput.ts +2 -1
  66. package/desktop/cmp/input/TextArea.ts +3 -3
  67. package/desktop/cmp/input/TextInput.ts +51 -47
  68. package/desktop/cmp/panel/Panel.ts +19 -15
  69. package/desktop/cmp/panel/impl/ResizeContainer.ts +3 -2
  70. package/desktop/cmp/pinpad/impl/PinPad.ts +4 -3
  71. package/desktop/cmp/record/RecordActionBar.ts +12 -3
  72. package/desktop/cmp/record/impl/RecordActionButton.ts +1 -0
  73. package/desktop/cmp/rest/Actions.ts +10 -5
  74. package/desktop/cmp/rest/RestGrid.ts +20 -6
  75. package/desktop/cmp/rest/impl/RestForm.ts +5 -4
  76. package/desktop/cmp/rest/impl/RestGridToolbar.ts +3 -2
  77. package/desktop/cmp/tab/TabSwitcher.ts +8 -3
  78. package/desktop/cmp/tab/impl/Tab.ts +2 -1
  79. package/desktop/cmp/tab/impl/TabContainer.ts +18 -15
  80. package/desktop/cmp/toolbar/Toolbar.ts +3 -1
  81. package/desktop/cmp/treemap/SplitTreeMap.ts +2 -1
  82. package/desktop/cmp/treemap/TreeMap.ts +5 -3
  83. package/desktop/cmp/zoneGrid/impl/ZoneMapper.scss +71 -0
  84. package/desktop/cmp/zoneGrid/impl/ZoneMapper.ts +232 -0
  85. package/desktop/cmp/zoneGrid/impl/ZoneMapperDialog.ts +35 -0
  86. package/dynamics/desktop.ts +2 -0
  87. package/dynamics/mobile.ts +2 -0
  88. package/inspector/instances/InstancesModel.ts +2 -2
  89. package/mobile/appcontainer/AppContainer.ts +2 -0
  90. package/mobile/cmp/button/ZoneMapperButton.ts +41 -0
  91. package/mobile/cmp/button/index.ts +1 -0
  92. package/mobile/cmp/error/ErrorMessage.ts +4 -4
  93. package/mobile/cmp/input/Select.scss +1 -0
  94. package/mobile/cmp/input/Select.ts +7 -0
  95. package/mobile/cmp/input/TextInput.ts +1 -0
  96. package/mobile/cmp/panel/DialogPanel.scss +18 -6
  97. package/mobile/cmp/panel/DialogPanel.ts +3 -1
  98. package/mobile/cmp/zoneGrid/impl/ZoneMapper.scss +67 -0
  99. package/mobile/cmp/zoneGrid/impl/ZoneMapper.ts +236 -0
  100. package/package.json +4 -3
  101. package/styles/vars.scss +3 -3
  102. package/svc/InspectorService.ts +1 -1
  103. package/utils/js/DomUtils.ts +10 -0
  104. package/utils/js/LangUtils.ts +10 -0
  105. package/utils/js/TestUtils.ts +9 -0
  106. package/utils/js/index.ts +1 -0
@@ -5,16 +5,15 @@
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {div, filler, frame, hbox, p} from '@xh/hoist/cmp/layout';
8
- import {hoistCmp, HoistProps} from '@xh/hoist/core';
8
+ import {BoxProps, hoistCmp, HoistProps} from '@xh/hoist/core';
9
9
  import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
10
10
  import '@xh/hoist/desktop/register';
11
11
  import {isNil, isString} from 'lodash';
12
12
  import {isValidElement, ReactNode} from 'react';
13
-
14
13
  import './ErrorMessage.scss';
15
14
  import {Icon} from '@xh/hoist/icon';
16
15
 
17
- export interface ErrorMessageProps extends HoistProps {
16
+ export interface ErrorMessageProps extends HoistProps, Omit<BoxProps, 'title'> {
18
17
  /**
19
18
  * If provided, will render a "Retry" button that calls this function.
20
19
  * Use `actionButtonProps` for further control over this button.
@@ -69,7 +68,8 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
69
68
  actionFn,
70
69
  actionButtonProps,
71
70
  detailsFn,
72
- detailsButtonProps
71
+ detailsButtonProps,
72
+ ...rest
73
73
  } = props;
74
74
 
75
75
  if (isNil(error)) return null;
@@ -77,17 +77,17 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
77
77
  if (!message) {
78
78
  if (isString(error)) {
79
79
  message = error;
80
- } else if (error.message) {
81
- message = error.message;
80
+ } else {
81
+ message = error.message || error.name || 'Unknown Error';
82
82
  }
83
83
  }
84
84
 
85
85
  if (actionFn) {
86
- actionButtonProps = {...actionButtonProps, onClick: error => actionFn(error)};
86
+ actionButtonProps = {...actionButtonProps, onClick: () => actionFn(error)};
87
87
  }
88
88
 
89
89
  if (detailsFn) {
90
- detailsButtonProps = {...detailsButtonProps, onClick: error => detailsFn(error)};
90
+ detailsButtonProps = {...detailsButtonProps, onClick: () => detailsFn(error)};
91
91
  }
92
92
 
93
93
  let buttons = [],
@@ -103,6 +103,7 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
103
103
  return frame({
104
104
  ref,
105
105
  className,
106
+ ...rest,
106
107
  item: div({
107
108
  className: 'xh-error-message__inner',
108
109
  items: [titleCmp({title}), messageCmp({message, error}), buttonBar]
@@ -4,22 +4,23 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {PopoverPosition, PopperBoundary} from '@blueprintjs/core';
7
8
  import composeRefs from '@seznam/compose-react-refs/composeRefs';
8
- import {FieldModel, FormContext, FormContextType, BaseFormFieldProps} from '@xh/hoist/cmp/form';
9
+ import {BaseFormFieldProps, FieldModel, FormContext, FormContextType} from '@xh/hoist/cmp/form';
9
10
  import {box, div, label as labelEl, li, span, ul} from '@xh/hoist/cmp/layout';
10
11
  import {DefaultHoistProps, hoistCmp, HSide, uses, XH} from '@xh/hoist/core';
11
12
  import '@xh/hoist/desktop/register';
13
+ import {instanceManager} from '@xh/hoist/core/impl/InstanceManager';
12
14
  import {fmtDate, fmtDateTime, fmtJson, fmtNumber} from '@xh/hoist/format';
13
15
  import {Icon} from '@xh/hoist/icon';
14
16
  import {tooltip} from '@xh/hoist/kit/blueprint';
15
17
  import {isLocalDate} from '@xh/hoist/utils/datetime';
16
- import {errorIf, throwIf, withDefault} from '@xh/hoist/utils/js';
17
- import {getLayoutProps, getReactElementName} from '@xh/hoist/utils/react';
18
+ import {errorIf, getTestId, TEST_ID, throwIf, withDefault} from '@xh/hoist/utils/js';
19
+ import {getLayoutProps, getReactElementName, useOnMount, useOnUnmount} from '@xh/hoist/utils/react';
18
20
  import classNames from 'classnames';
19
21
  import {isBoolean, isDate, isEmpty, isFinite, isNil, isUndefined, kebabCase} from 'lodash';
20
22
  import {Children, cloneElement, ReactElement, ReactNode, useContext, useState} from 'react';
21
23
  import './FormField.scss';
22
- import {PopoverPosition, PopperBoundary} from '@blueprintjs/core';
23
24
 
24
25
  export interface FormFieldProps extends BaseFormFieldProps {
25
26
  /**
@@ -162,10 +163,18 @@ export const [FormField, formField] = hoistCmp.withFactory<FormFieldProps>({
162
163
  if (disabled) classes.push('xh-form-field-disabled');
163
164
  if (displayNotValid) classes.push('xh-form-field-invalid');
164
165
 
166
+ const testId = getFormFieldTestId(props, formContext, model.name);
167
+ useOnMount(() => instanceManager.registerModelWithTestId(testId, model));
168
+ useOnUnmount(() => instanceManager.unregisterModelWithTestId(testId));
169
+
165
170
  // generate actual element child to render
166
171
  let childEl: ReactElement =
167
172
  !child || readonly
168
- ? readonlyChild({model, readonlyRenderer})
173
+ ? readonlyChild({
174
+ model,
175
+ readonlyRenderer,
176
+ testId: getTestId(testId, 'readonly-display')
177
+ })
169
178
  : editableChild({
170
179
  model,
171
180
  child,
@@ -174,7 +183,8 @@ export const [FormField, formField] = hoistCmp.withFactory<FormFieldProps>({
174
183
  disabled,
175
184
  displayNotValid,
176
185
  leftErrorIcon,
177
- commitOnChange
186
+ commitOnChange,
187
+ testId: getTestId(testId, 'input')
178
188
  });
179
189
 
180
190
  if (minimal) {
@@ -195,6 +205,7 @@ export const [FormField, formField] = hoistCmp.withFactory<FormFieldProps>({
195
205
  key: model?.xhId,
196
206
  className: classNames(className, classes),
197
207
  ...getLayoutProps(props),
208
+ testId,
198
209
  items: [
199
210
  labelEl({
200
211
  omit: !label,
@@ -236,9 +247,13 @@ export const [FormField, formField] = hoistCmp.withFactory<FormFieldProps>({
236
247
  const readonlyChild = hoistCmp.factory({
237
248
  model: false,
238
249
 
239
- render({model, readonlyRenderer}) {
250
+ render({model, readonlyRenderer, testId}) {
240
251
  const value = model ? model['value'] : null;
241
- return div({className: 'xh-form-field-readonly-display', item: readonlyRenderer(value)});
252
+ return div({
253
+ className: 'xh-form-field-readonly-display',
254
+ [TEST_ID]: testId,
255
+ item: readonlyRenderer(value)
256
+ });
242
257
  }
243
258
  });
244
259
 
@@ -253,7 +268,8 @@ const editableChild = hoistCmp.factory<FieldModel>({
253
268
  disabled,
254
269
  displayNotValid,
255
270
  leftErrorIcon,
256
- commitOnChange
271
+ commitOnChange,
272
+ testId
257
273
  }) {
258
274
  const {props} = child;
259
275
 
@@ -263,7 +279,8 @@ const editableChild = hoistCmp.factory<FieldModel>({
263
279
  bind: 'value',
264
280
  id: childId,
265
281
  disabled: props.disabled || disabled,
266
- ref: composeRefs(model?.boundInputRef, child.ref)
282
+ ref: composeRefs(model?.boundInputRef, child.ref),
283
+ testId: props.testId ?? testId
267
284
  };
268
285
 
269
286
  // If a sizeable child input doesn't specify its own dimensions,
@@ -347,3 +364,10 @@ function defaultProp(
347
364
  const fieldDefault = formContext.fieldDefaults ? formContext.fieldDefaults[name] : null;
348
365
  return withDefault(props[name], fieldDefault, defaultVal);
349
366
  }
367
+ function getFormFieldTestId(
368
+ props: Partial<FormFieldProps>,
369
+ formContext: FormContextType,
370
+ fieldName: string
371
+ ): string {
372
+ return props.testId ?? (formContext.testId ? `${formContext.testId}-${fieldName}` : undefined);
373
+ }
@@ -8,7 +8,7 @@ import {ColumnSpec} from '@xh/hoist/cmp/grid/columns';
8
8
  import {RecordAction} from '@xh/hoist/data';
9
9
  import {button, buttonGroup} from '@xh/hoist/desktop/cmp/button';
10
10
  import '@xh/hoist/desktop/register';
11
- import {throwIf} from '@xh/hoist/utils/js';
11
+ import {getTestId, throwIf} from '@xh/hoist/utils/js';
12
12
  import classNames from 'classnames';
13
13
  import {isEmpty} from 'lodash';
14
14
 
@@ -64,6 +64,7 @@ export const actionCol: ColumnSpec = {
64
64
  if (hidden) return null;
65
65
 
66
66
  return button({
67
+ testId: getTestId(action.testId, `${record.id}`),
67
68
  icon,
68
69
  disabled,
69
70
  tooltip,
@@ -43,7 +43,7 @@ export const colChooser = hoistCmp.factory<ColChooserProps>({
43
43
  filler(),
44
44
  button({
45
45
  omit: !showRestoreDefaults,
46
- text: 'Restore Grid Defaults',
46
+ text: 'Restore Defaults',
47
47
  icon: Icon.undo({className: 'xh-red'}),
48
48
  onClick: () => model.restoreDefaultsAsync()
49
49
  }),
@@ -57,7 +57,8 @@ export const colChooser = hoistCmp.factory<ColChooserProps>({
57
57
  button({
58
58
  omit: commitOnChange,
59
59
  text: 'Save',
60
- icon: Icon.check({className: 'xh-green'}),
60
+ icon: Icon.check(),
61
+ intent: 'success',
61
62
  onClick: () => {
62
63
  model.commit();
63
64
  model.close();
@@ -8,12 +8,13 @@ import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
8
8
  import {box, div, filler, fragment, hbox, vbox} from '@xh/hoist/cmp/layout';
9
9
  import {hoistCmp, uses} from '@xh/hoist/core';
10
10
  import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
11
- import {select, MENU_PORTAL_ID} from '@xh/hoist/desktop/cmp/input';
11
+ import {select} from '@xh/hoist/desktop/cmp/input';
12
12
  import {panel} from '@xh/hoist/desktop/cmp/panel';
13
13
  import '@xh/hoist/desktop/register';
14
14
  import {Icon} from '@xh/hoist/icon';
15
15
  import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
16
16
  import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
17
+ import {elemWithin, getTestId, TEST_ID} from '@xh/hoist/utils/js';
17
18
  import {splitLayoutProps} from '@xh/hoist/utils/react';
18
19
  import classNames from 'classnames';
19
20
  import {compact, isEmpty, sortBy} from 'lodash';
@@ -58,6 +59,7 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
58
59
  popoverTitle = 'Group By',
59
60
  popoverPosition = 'bottom',
60
61
  styleButtonAsInput = true,
62
+ testId,
61
63
  ...rest
62
64
  },
63
65
  ref
@@ -65,7 +67,10 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
65
67
  const {editorIsOpen, favoritesIsOpen, persistFavorites, value, allowEmpty} = model,
66
68
  isOpen = editorIsOpen || favoritesIsOpen,
67
69
  label = isEmpty(value) && allowEmpty ? emptyText : model.getValueLabel(value),
68
- [layoutProps, buttonProps] = splitLayoutProps(rest);
70
+ [layoutProps, buttonProps] = splitLayoutProps(rest),
71
+ favoritesMenuTestId = getTestId(testId, 'favorites-menu'),
72
+ favoritesIconTestId = getTestId(testId, 'favorites-icon'),
73
+ editorTestId = getTestId(testId, 'editor');
69
74
 
70
75
  return box({
71
76
  ref,
@@ -91,21 +96,28 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
91
96
  ),
92
97
  minimal: styleButtonAsInput,
93
98
  ...buttonProps,
94
- onClick: () => model.toggleEditor()
99
+ onClick: () => model.toggleEditor(),
100
+ testId
95
101
  }),
96
- favoritesIcon()
102
+ favoritesIcon({testId: favoritesIconTestId})
97
103
  ),
98
104
  content: favoritesIsOpen
99
- ? favoritesMenu()
105
+ ? favoritesMenu({testId: favoritesMenuTestId})
100
106
  : editorIsOpen
101
- ? editor({popoverWidth, popoverMinHeight, popoverTitle, emptyText})
107
+ ? editor({
108
+ popoverWidth,
109
+ popoverMinHeight,
110
+ popoverTitle,
111
+ emptyText,
112
+ testId: editorTestId
113
+ })
102
114
  : null,
103
115
  onInteraction: (nextOpenState, e) => {
104
116
  if (
105
117
  isOpen &&
106
118
  nextOpenState === false &&
107
119
  e?.target &&
108
- !targetIsControlButtonOrPortal(e.target)
120
+ !elemWithin(e.target, 'xh-grouping-chooser-button--with-favorites')
109
121
  ) {
110
122
  model.commitPendingValueAndClose();
111
123
  }
@@ -119,7 +131,7 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
119
131
  // Editor
120
132
  //------------------
121
133
  const editor = hoistCmp.factory<GroupingChooserModel>({
122
- render({popoverWidth, popoverMinHeight, popoverTitle, emptyText}) {
134
+ render({popoverWidth, popoverMinHeight, popoverTitle, emptyText, testId}) {
123
135
  return panel({
124
136
  width: popoverWidth,
125
137
  minHeight: popoverMinHeight,
@@ -128,7 +140,8 @@ const editor = hoistCmp.factory<GroupingChooserModel>({
128
140
  dimensionList({emptyText}),
129
141
  addDimensionControl(),
130
142
  filler()
131
- ]
143
+ ],
144
+ testId
132
145
  });
133
146
  }
134
147
  });
@@ -292,32 +305,16 @@ function getDimOptions(dims, model) {
292
305
  return sortBy(ret, 'label');
293
306
  }
294
307
 
295
- function targetIsControlButtonOrPortal(target) {
296
- const selectPortal = document.getElementById(MENU_PORTAL_ID)?.contains(target),
297
- selectClick = targetWithin(target, 'xh-select__single-value'),
298
- editorClick = targetWithin(target, 'xh-grouping-chooser-button--with-favorites');
299
- return selectPortal || selectClick || editorClick;
300
- }
301
-
302
- /**
303
- * Determines whether any of the target's parents have a specific class name
304
- */
305
- function targetWithin(target, className): boolean {
306
- for (let elem = target; elem; elem = elem.parentElement) {
307
- if (elem.classList.contains(className)) return true;
308
- }
309
- return false;
310
- }
311
-
312
308
  //------------------
313
309
  // Favorites
314
310
  //------------------
315
311
  const favoritesIcon = hoistCmp.factory<GroupingChooserModel>({
316
- render({model}) {
312
+ render({model, testId}) {
317
313
  if (!model.persistFavorites) return null;
318
314
  return div({
319
315
  item: Icon.favorite(),
320
316
  className: 'xh-grouping-chooser__favorite-icon',
317
+ [TEST_ID]: testId,
321
318
  onClick: e => {
322
319
  model.toggleFavoritesMenu();
323
320
  e.stopPropagation();
@@ -327,7 +324,7 @@ const favoritesIcon = hoistCmp.factory<GroupingChooserModel>({
327
324
  });
328
325
 
329
326
  const favoritesMenu = hoistCmp.factory<GroupingChooserModel>({
330
- render({model}) {
327
+ render({model, testId}) {
331
328
  const options = model.favoritesOptions,
332
329
  isFavorite = model.isFavorite(model.value),
333
330
  omitAdd = isEmpty(model.value) || isFavorite,
@@ -349,7 +346,10 @@ const favoritesMenu = hoistCmp.factory<GroupingChooserModel>({
349
346
  })
350
347
  );
351
348
 
352
- return vbox(div({className: 'xh-popup__title', item: 'Favorites'}), menu({items}));
349
+ return vbox({
350
+ testId,
351
+ items: [div({className: 'xh-popup__title', item: 'Favorites'}), menu({items})]
352
+ });
353
353
  }
354
354
  });
355
355
 
@@ -10,7 +10,7 @@ import {Button, buttonGroup, ButtonGroupProps} from '@xh/hoist/desktop/cmp/butto
10
10
  import '@xh/hoist/desktop/register';
11
11
  import {throwIf, warnIf, withDefault} from '@xh/hoist/utils/js';
12
12
  import {getLayoutProps, getNonLayoutProps} from '@xh/hoist/utils/react';
13
- import {isEmpty, filter, without, castArray} from 'lodash';
13
+ import {castArray, filter, isEmpty, without} from 'lodash';
14
14
  import {Children, cloneElement, isValidElement} from 'react';
15
15
 
16
16
  export interface ButtonGroupInputProps
@@ -5,10 +5,10 @@
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {HoistInputModel, HoistInputProps, useHoistInputModel} from '@xh/hoist/cmp/input';
8
- import {hoistCmp, StyleProps, HoistProps, HSide} from '@xh/hoist/core';
8
+ import {hoistCmp, HoistProps, HSide, StyleProps} from '@xh/hoist/core';
9
9
  import '@xh/hoist/desktop/register';
10
10
  import {checkbox as bpCheckbox} from '@xh/hoist/kit/blueprint';
11
- import {withDefault} from '@xh/hoist/utils/js';
11
+ import {TEST_ID, withDefault} from '@xh/hoist/utils/js';
12
12
  import {isNil} from 'lodash';
13
13
  import {ReactNode} from 'react';
14
14
 
@@ -73,8 +73,8 @@ const cmp = hoistCmp.factory<CheckboxInputModel>(({model, className, ...props},
73
73
  inline: withDefault(props.inline, true),
74
74
  label: props.label,
75
75
  tabIndex: props.tabIndex,
76
-
77
76
  id: props.id,
77
+ [TEST_ID]: props.testId,
78
78
  className,
79
79
  style: props.style,
80
80
 
@@ -35,8 +35,8 @@ import 'codemirror/addon/selection/mark-selection.js';
35
35
  import 'codemirror/lib/codemirror.css';
36
36
  import 'codemirror/theme/dracula.css';
37
37
  import {compact, defaultsDeep, isEqual, isFunction} from 'lodash';
38
- import {findDOMNode} from 'react-dom';
39
38
  import {ReactElement} from 'react';
39
+ import {findDOMNode} from 'react-dom';
40
40
  import './CodeInput.scss';
41
41
 
42
42
  export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps {
@@ -455,6 +455,7 @@ const cmp = hoistCmp.factory<CodeInputModel>(({model, className, ...props}, ref)
455
455
  item: modalSupport({
456
456
  model: model.modalSupportModel,
457
457
  item: inputCmp({
458
+ testId: props.testId,
458
459
  width: '100%',
459
460
  height: '100%',
460
461
  className,