@widergy/mobile-ui 1.47.0 → 1.48.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 (83) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -0
  3. package/lib/components/CaptionLabel/README.md +30 -8
  4. package/lib/components/CaptionLabel/index.js +2 -1
  5. package/lib/components/CaptionLabel/propTypes.js +1 -0
  6. package/lib/components/Checkbox/README.md +93 -25
  7. package/lib/components/Checkbox/index.js +14 -1
  8. package/lib/components/Checkbox/propTypes.js +1 -0
  9. package/lib/components/Label/index.js +2 -1
  10. package/lib/components/Label/propTypes.js +1 -0
  11. package/lib/components/RadioGroup/components/RadioButton/index.js +29 -18
  12. package/lib/components/RadioGroup/index.js +19 -5
  13. package/lib/components/Touchable/index.js +3 -1
  14. package/lib/components/Touchable/propTypes.js +1 -0
  15. package/lib/components/UTBadge/index.js +3 -1
  16. package/lib/components/UTBaseInputField/README.md +41 -19
  17. package/lib/components/UTBaseInputField/components/ActionAdornment/index.js +10 -3
  18. package/lib/components/UTBaseInputField/components/BadgeAdornment/index.js +6 -1
  19. package/lib/components/UTBaseInputField/components/IconAdornment/index.js +8 -1
  20. package/lib/components/UTBaseInputField/components/PrefixAdornment/index.js +8 -2
  21. package/lib/components/UTBaseInputField/components/SuffixAdornment/index.js +6 -1
  22. package/lib/components/UTBaseInputField/components/TooltipAdornment/index.js +16 -3
  23. package/lib/components/UTBaseInputField/index.js +15 -4
  24. package/lib/components/UTBottomSheet/README.md +94 -23
  25. package/lib/components/UTBottomSheet/index.js +27 -4
  26. package/lib/components/UTButton/index.js +18 -4
  27. package/lib/components/UTButton/proptypes.js +1 -0
  28. package/lib/components/UTCheckBox/README.md +47 -0
  29. package/lib/components/UTCheckBox/index.js +24 -3
  30. package/lib/components/UTCheckBox/proptypes.js +1 -0
  31. package/lib/components/UTCheckList/README.MD +63 -0
  32. package/lib/components/UTCheckList/index.js +25 -2
  33. package/lib/components/UTCheckList/proptypes.js +1 -0
  34. package/lib/components/UTDetailDrawer/README.md +60 -10
  35. package/lib/components/UTDetailDrawer/index.js +11 -1
  36. package/lib/components/UTDetailDrawer/propTypes.js +1 -0
  37. package/lib/components/UTFieldLabel/README.md +99 -0
  38. package/lib/components/UTFieldLabel/index.js +19 -2
  39. package/lib/components/UTIcon/README.md +25 -2
  40. package/lib/components/UTIcon/index.js +3 -1
  41. package/lib/components/UTLabel/README.md +26 -0
  42. package/lib/components/UTLabel/constants.js +0 -10
  43. package/lib/components/UTLabel/index.js +2 -0
  44. package/lib/components/UTLabel/proptypes.js +1 -0
  45. package/lib/components/UTLabel/utils.js +4 -22
  46. package/lib/components/UTMenu/README.md +275 -0
  47. package/lib/components/UTMenu/components/ListView/index.js +5 -3
  48. package/lib/components/UTMenu/components/ListView/proptypes.js +2 -1
  49. package/lib/components/UTMenu/components/MenuOption/index.js +5 -3
  50. package/lib/components/UTMenu/index.js +18 -3
  51. package/lib/components/UTMenu/proptypes.js +2 -1
  52. package/lib/components/UTModal/README.md +193 -0
  53. package/lib/components/UTModal/index.js +22 -2
  54. package/lib/components/UTModal/proptypes.js +1 -0
  55. package/lib/components/UTPhoneInput/index.js +25 -2
  56. package/lib/components/UTRoundView/README.md +158 -0
  57. package/lib/components/UTRoundView/index.js +12 -1
  58. package/lib/components/UTRoundView/propTypes.js +4 -2
  59. package/lib/components/UTSearchField/README.md +64 -14
  60. package/lib/components/UTSearchField/index.js +3 -1
  61. package/lib/components/UTSearchField/proptypes.js +2 -1
  62. package/lib/components/UTSelect/versions/V0/README.md +216 -0
  63. package/lib/components/UTSelect/versions/V0/componentes/MultipleItem/index.js +4 -2
  64. package/lib/components/UTSelect/versions/V0/index.js +5 -2
  65. package/lib/components/UTSelect/versions/V0/proptypes.js +2 -1
  66. package/lib/components/UTSelect/versions/V1/README.md +94 -0
  67. package/lib/components/UTSelect/versions/V1/index.js +28 -6
  68. package/lib/components/UTSelect/versions/V1/proptypes.js +1 -0
  69. package/lib/components/UTSelectableCard/README.md +85 -0
  70. package/lib/components/UTSelectableCard/index.js +52 -4
  71. package/lib/components/UTTextInput/versions/V0/components/BaseInput/index.js +5 -1
  72. package/lib/components/UTTextInput/versions/V0/components/InputLabel/index.js +4 -1
  73. package/lib/components/UTTextInput/versions/V0/flavors/FilledInput/index.js +9 -1
  74. package/lib/components/UTTextInput/versions/V0/flavors/OutlinedInput/index.js +9 -1
  75. package/lib/components/UTTextInput/versions/V0/flavors/StandardInput/index.js +9 -1
  76. package/lib/components/UTTextInput/versions/V1/components/TextInputField/index.js +3 -0
  77. package/lib/components/UTTextInput/versions/V1/index.js +20 -3
  78. package/lib/components/UTTooltip/README.md +99 -0
  79. package/lib/components/UTTooltip/index.js +2 -0
  80. package/lib/components/UTTooltip/proptypes.js +2 -1
  81. package/lib/components/UTValidation/index.js +26 -4
  82. package/lib/constants/testIds.js +44 -0
  83. package/package.json +1 -1
@@ -9,6 +9,7 @@
9
9
  | Name | Type | Default | Description |
10
10
  | -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
11
  | colorTheme | string | 'dark' | Color theme to style the `UTLabel`. It must be defined in the project theme. [Example](#colortheme). |
12
+ | dataTestId | string | | Test ID for automated testing. |
12
13
  | field | any | {} | Field configuration for advanced customization. |
13
14
  | markdownStyles | object | {} | Custom styles for rendering markdown content. This allows overriding default markdown styles like lists, links, etc. |
14
15
  | numberOfLines | number | | Limits the number of lines displayed in the label. When set, `ellipsizeMode` is automatically set to 'tail'. |
@@ -20,6 +21,20 @@
20
21
  | weight | string | 'regular' | Defines the font weight to be used. For a list of available options, check [here](#weights). |
21
22
  | withMarkdown | bool | false | Indicates whether or not to render text inside as markdown. |
22
23
 
24
+ ## Test IDs
25
+
26
+ When `dataTestId` is provided, the component applies the test ID to its element:
27
+
28
+ | Element | Test ID | Condition |
29
+ | ------- | --------------- | --------------------------------- |
30
+ | Label | `${dataTestId}` | Always when `dataTestId` provided |
31
+
32
+ ### Test ID Details
33
+
34
+ - **Label**: The main text element that displays the content
35
+
36
+ The UTLabel component applies the test ID directly to its root text element, making it easy to identify and interact with in automated tests.
37
+
23
38
  ## Color Themes
24
39
 
25
40
  The `color` prop must be one of these values:
@@ -97,3 +112,14 @@ For all others: `shade05`
97
112
  ```jsx
98
113
  <UTLabel withMarkdown>{`This is **bold** and this is *italic* text.`}</UTLabel>
99
114
  ```
115
+
116
+ ### Example with Test IDs
117
+
118
+ ```jsx
119
+ <UTLabel dataTestId="userWelcome" variant="title2" colorTheme="accent">
120
+ Welcome back, John!
121
+ </UTLabel>
122
+
123
+ // Generated test IDs:
124
+ // userWelcome (label element)
125
+ ```
@@ -35,13 +35,3 @@ export const WEIGHTS = {
35
35
  extrabold: 800,
36
36
  black: 900
37
37
  };
38
-
39
- export const MARKDOWN_FORMATTING = {
40
- BR_SEQUENCE_REGEX: /(<br\s*\/?>)+/gi,
41
- BR_TAG_REGEX: /<br\s*\/?>/gi,
42
- CARRIAGE_RETURN_REGEX: /\r\n|\r/g,
43
- HR_TAG_REGEX: /<\s*hr\s*\/?>/gi,
44
- LINE_BREAK_MARK: '\n\u200b',
45
- LINE_BREAKS_REGEX: /\n+/g,
46
- NORMALIZED_HR: '\n\n---'
47
- };
@@ -11,6 +11,7 @@ import { DEFAULT_PROPS, propTypes } from './proptypes';
11
11
  const UTLabel = ({
12
12
  children,
13
13
  colorTheme,
14
+ dataTestId,
14
15
  field,
15
16
  markdownStyles,
16
17
  numberOfLines,
@@ -38,6 +39,7 @@ const UTLabel = ({
38
39
  {...additionalProps}
39
40
  onLinkPress={onLinkPress}
40
41
  onTextLayout={onTextLayout}
42
+ testID={dataTestId}
41
43
  style={
42
44
  withMarkdown
43
45
  ? { ...markdownStyles, body: labelStyles, paragraph: { marginTop: 0, marginBottom: 0 } }
@@ -14,6 +14,7 @@ export const DEFAULT_PROPS = {
14
14
 
15
15
  export const propTypes = {
16
16
  colorTheme: string,
17
+ dataTestId: string,
17
18
  // eslint-disable-next-line react/forbid-prop-types
18
19
  field: any,
19
20
  markdownStyles: object,
@@ -1,23 +1,5 @@
1
- import { MARKDOWN_FORMATTING } from './constants';
2
-
3
- const {
4
- BR_SEQUENCE_REGEX,
5
- BR_TAG_REGEX,
6
- CARRIAGE_RETURN_REGEX,
7
- HR_TAG_REGEX,
8
- LINE_BREAK_MARK,
9
- LINE_BREAKS_REGEX,
10
- NORMALIZED_HR
11
- } = MARKDOWN_FORMATTING;
12
-
13
- export const markdownFormat = content => {
14
- return content
1
+ export const markdownFormat = content =>
2
+ content
15
3
  ?.toString()
16
- .replace(CARRIAGE_RETURN_REGEX, '\n')
17
- .replace(LINE_BREAKS_REGEX, match => LINE_BREAK_MARK.repeat(match.length))
18
- .replace(BR_SEQUENCE_REGEX, match => {
19
- const count = match.match(BR_TAG_REGEX)?.length || 0;
20
- return LINE_BREAK_MARK.repeat(count);
21
- })
22
- .replace(HR_TAG_REGEX, NORMALIZED_HR);
23
- };
4
+ .replace(/(<\s*br\s*\/?>|\n)/gi, '\n\n')
5
+ .replace(/(<\s*hr\s*\/?>)/gi, '\n\n---') || null;
@@ -0,0 +1,275 @@
1
+ # UTMenu
2
+
3
+ > **⚠️ AI-Generated Documentation Disclaimer**
4
+ > This documentation has been generated with AI assistance and has not been thoroughly reviewed by a developer. Please verify implementation details and usage examples before relying on them in production code.
5
+
6
+ A flexible dropdown menu component that provides a modal overlay with selectable options. Features customizable positioning, search functionality, Google attribution support, and comprehensive test ID support.
7
+
8
+ ## Props
9
+
10
+ | NAME | TYPE | REQUIRED | DESCRIPTION |
11
+ | ----------------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------- |
12
+ | options | array | Yes | Array of option objects. Each option must have `id`, `label`, and optionally `value`, `action` properties. |
13
+ | children | node | Yes | The trigger element that will open the menu when pressed. |
14
+ | onPress | func | No | Callback function called when an option is selected. Receives the selected option. |
15
+ | selectedOption | object | No | Currently selected option object (for single selection) or array of values (for multiple). |
16
+ | onOpen | func | No | Callback function called when the menu opens. |
17
+ | onClose | func | No | Callback function called when the menu closes. |
18
+ | disabled | bool | No | Whether the menu trigger is disabled. |
19
+ | fullWidth | bool | No | Whether the menu should match the width of the trigger element. |
20
+ | verticalOffset | number | No | Vertical offset for menu positioning relative to trigger. Default: 0. |
21
+ | horizontalOffset | number | No | Horizontal offset for menu positioning relative to trigger. Default: 0. |
22
+ | avoidOverlappingAnchor | bool | No | Whether to avoid overlapping the trigger element. Default: true. |
23
+ | withoutOpacity | bool | No | Whether to disable opacity change on trigger press. |
24
+ | isMultiple | bool | No | Enables multiple selection mode. |
25
+ | maxHeight | number | No | Maximum height for the options list. |
26
+ | withAutocomplete | bool | No | Enables search/filter functionality with text input. |
27
+ | autoCompletePlaceholder | string | No | Placeholder text for the search input. |
28
+ | onQueryChange | func | No | Callback function called when search query changes. |
29
+ | filterOptions | bool | No | Whether to filter options based on search query. Default: true. |
30
+ | MenuOptionComponent | component | No | Custom component to render menu options. Default: MenuOption. |
31
+ | ItemSeparatorComponent | component | No | Component to render between menu options. |
32
+ | ListEmptyComponent | component | No | Component to render when no options are available. |
33
+ | withGoogleAttribution | bool | No | Whether to show Google attribution (for location services). Default: false. |
34
+ | styles | object | No | Custom styles for menu components. |
35
+ | dataTestId | string | No | Unique identifier for testing purposes. Creates hierarchical test IDs for child elements. |
36
+
37
+ ## Option Object Structure
38
+
39
+ Each option in the `options` array should have the following structure:
40
+
41
+ ```js
42
+ {
43
+ id: string | number, // Unique identifier for the option
44
+ label: string, // Display text for the option
45
+ value?: any, // Optional value (defaults to id if not provided)
46
+ action?: function // Optional custom action function
47
+ }
48
+ ```
49
+
50
+ ## Usage Examples
51
+
52
+ ### Basic Menu
53
+
54
+ ```jsx
55
+ import React, { useState } from 'react';
56
+ import { Text, View } from 'react-native';
57
+ import UTMenu from '@widergy/mobile-ui/lib/components/UTMenu';
58
+
59
+ const BasicExample = () => {
60
+ const [selectedOption, setSelectedOption] = useState(null);
61
+
62
+ const options = [
63
+ { id: '1', label: 'Option 1' },
64
+ { id: '2', label: 'Option 2' },
65
+ { id: '3', label: 'Option 3' }
66
+ ];
67
+
68
+ return (
69
+ <UTMenu options={options} onPress={setSelectedOption} selectedOption={selectedOption?.id}>
70
+ <View style={{ padding: 10, backgroundColor: '#f0f0f0' }}>
71
+ <Text>{selectedOption?.label || 'Select an option'}</Text>
72
+ </View>
73
+ </UTMenu>
74
+ );
75
+ };
76
+ ```
77
+
78
+ ### Menu with Search
79
+
80
+ ```jsx
81
+ const SearchableMenu = () => {
82
+ const [selected, setSelected] = useState(null);
83
+ const [query, setQuery] = useState('');
84
+
85
+ const options = [
86
+ { id: 'apple', label: 'Apple' },
87
+ { id: 'banana', label: 'Banana' },
88
+ { id: 'cherry', label: 'Cherry' },
89
+ { id: 'date', label: 'Date' },
90
+ { id: 'elderberry', label: 'Elderberry' }
91
+ ];
92
+
93
+ return (
94
+ <UTMenu
95
+ options={options}
96
+ onPress={setSelected}
97
+ selectedOption={selected?.id}
98
+ withAutocomplete
99
+ autoCompletePlaceholder="Search fruits..."
100
+ onQueryChange={setQuery}
101
+ >
102
+ <View style={{ padding: 12, borderWidth: 1, borderColor: '#ccc' }}>
103
+ <Text>{selected?.label || 'Choose a fruit'}</Text>
104
+ </View>
105
+ </UTMenu>
106
+ );
107
+ };
108
+ ```
109
+
110
+ ### Multiple Selection Menu
111
+
112
+ ```jsx
113
+ const MultipleSelectionMenu = () => {
114
+ const [selectedValues, setSelectedValues] = useState([]);
115
+
116
+ const skillOptions = [
117
+ { id: 'react', label: 'React', value: 'react' },
118
+ { id: 'vue', label: 'Vue.js', value: 'vue' },
119
+ { id: 'angular', label: 'Angular', value: 'angular' },
120
+ { id: 'svelte', label: 'Svelte', value: 'svelte' }
121
+ ];
122
+
123
+ return (
124
+ <UTMenu
125
+ options={skillOptions}
126
+ onPress={option => {
127
+ const isSelected = selectedValues.includes(option.value);
128
+ if (isSelected) {
129
+ setSelectedValues(prev => prev.filter(val => val !== option.value));
130
+ } else {
131
+ setSelectedValues(prev => [...prev, option.value]);
132
+ }
133
+ }}
134
+ selectedOption={selectedValues}
135
+ isMultiple
136
+ >
137
+ <View style={{ padding: 12, borderWidth: 1 }}>
138
+ <Text>{selectedValues.length > 0 ? `Selected: ${selectedValues.join(', ')}` : 'Select skills'}</Text>
139
+ </View>
140
+ </UTMenu>
141
+ );
142
+ };
143
+ ```
144
+
145
+ ### Custom Positioning
146
+
147
+ ```jsx
148
+ const CustomPositionMenu = () => {
149
+ return (
150
+ <UTMenu
151
+ options={options}
152
+ onPress={handlePress}
153
+ verticalOffset={-5}
154
+ horizontalOffset={10}
155
+ fullWidth={false}
156
+ avoidOverlappingAnchor={false}
157
+ >
158
+ <YourTriggerComponent />
159
+ </UTMenu>
160
+ );
161
+ };
162
+ ```
163
+
164
+ ## Testing
165
+
166
+ The `UTMenu` component supports comprehensive test ID assignment through the `dataTestId` prop. When provided, it creates a hierarchical structure of test IDs for all interactive and meaningful elements.
167
+
168
+ ### Test ID Structure
169
+
170
+ When you provide `dataTestId="provided.testId"`, the following test IDs are automatically generated:
171
+
172
+ | Element | Test ID | When Available |
173
+ | ------------------- | ------------------------------------------- | ------------------------------------ |
174
+ | Menu anchor/trigger | `provided.testId.anchor` | Always (when dataTestId is provided) |
175
+ | Modal overlay | `provided.testId.modal` | When menu is open |
176
+ | Search input | `provided.testId.searchInput` | When withAutocomplete is true |
177
+ | Options list | `provided.testId.list` | When menu is open |
178
+ | Individual option | `provided.testId.list.option.{index}` | For each option when menu is open |
179
+ | Option label | `provided.testId.list.option.{index}.label` | For each option when menu is open |
180
+ | Attribution text | `provided.testId.attribution.text` | When withGoogleAttribution is true |
181
+ | Attribution logo | `provided.testId.attribution.logo` | When withGoogleAttribution is true |
182
+
183
+ **Note:** The search input test ID (`provided.testId.searchInput`) creates additional hierarchical test IDs based on the UTTextInput component's own test ID structure.
184
+
185
+ ### Test ID Examples
186
+
187
+ ```jsx
188
+ // Basic menu with test IDs
189
+ <UTMenu
190
+ dataTestId="actionMenu"
191
+ options={actionOptions}
192
+ onPress={handleAction}
193
+ >
194
+ <ActionButton />
195
+ </UTMenu>
196
+
197
+ // Creates test IDs:
198
+ // - actionMenu.anchor (trigger button)
199
+ // - actionMenu.modal (when open)
200
+ // - actionMenu.list (options container when open)
201
+ // - actionMenu.list.option.0 (first option when open)
202
+ // - actionMenu.list.option.0.label (first option text when open)
203
+ // - actionMenu.list.option.1 (second option when open)
204
+ // - actionMenu.list.option.1.label (second option text when open)
205
+
206
+ // Searchable menu with test IDs
207
+ <UTMenu
208
+ dataTestId="fruitSelector"
209
+ options={fruitOptions}
210
+ onPress={handleFruitSelection}
211
+ withAutocomplete
212
+ autoCompletePlaceholder="Search fruits..."
213
+ >
214
+ <FruitSelectorTrigger />
215
+ </UTMenu>
216
+
217
+ // Creates test IDs:
218
+ // - fruitSelector.anchor (trigger)
219
+ // - fruitSelector.modal (when open)
220
+ // - fruitSelector.searchInput (search input + UTTextInput sub-elements)
221
+ // - fruitSelector.list (options container)
222
+ // - fruitSelector.list.option.0 (first fruit option)
223
+ // - fruitSelector.list.option.0.label (first fruit option text)
224
+ // - fruitSelector.list.option.1 (second fruit option)
225
+ // - fruitSelector.list.option.1.label (second fruit option text)
226
+
227
+ // Menu with Google attribution and test IDs
228
+ <UTMenu
229
+ dataTestId="locationMenu"
230
+ options={locationOptions}
231
+ onPress={handleLocationSelect}
232
+ withGoogleAttribution
233
+ >
234
+ <LocationTrigger />
235
+ </UTMenu>
236
+
237
+ // Creates test IDs:
238
+ // - locationMenu.anchor (trigger)
239
+ // - locationMenu.modal (when open)
240
+ // - locationMenu.list (options container)
241
+ // - locationMenu.list.option.0 (first location option)
242
+ // - locationMenu.list.option.1 (second location option)
243
+ // - locationMenu.attribution.text (attribution text)
244
+ // - locationMenu.attribution.logo (Google logo)
245
+ ```
246
+
247
+ ## Custom Menu Option Components
248
+
249
+ You can provide a custom `MenuOptionComponent` that will receive the following props:
250
+
251
+ ```jsx
252
+ const CustomMenuOption = ({ onPress, label, selected, item, styles, dataTestId }) => {
253
+ return (
254
+ <TouchableOpacity
255
+ onPress={() => onPress(item)}
256
+ style={[customStyles.option, selected && customStyles.selected]}
257
+ testID={dataTestId}
258
+ >
259
+ <Text testID={dataTestId ? `${dataTestId}.label` : undefined}>{label}</Text>
260
+ </TouchableOpacity>
261
+ );
262
+ };
263
+
264
+ // Usage
265
+ <UTMenu
266
+ MenuOptionComponent={CustomMenuOption}
267
+ options={options}
268
+ onPress={handlePress}
269
+ dataTestId="customMenu"
270
+ >
271
+ <TriggerComponent />
272
+ </UTMenu>;
273
+ ```
274
+
275
+ **Note:** When implementing custom menu option components, make sure to handle the `dataTestId` prop properly to maintain the hierarchical test ID structure.
@@ -31,8 +31,8 @@ class ListView extends PureComponent {
31
31
  return handleOptionPress(item.action || (() => onPress(item)), item?.value);
32
32
  };
33
33
 
34
- renderItem = ({ item }) => {
35
- const { MenuOptionComponent, propStyles } = this.props;
34
+ renderItem = ({ item, index }) => {
35
+ const { MenuOptionComponent, propStyles, dataTestId } = this.props;
36
36
 
37
37
  return (
38
38
  <MenuOptionComponent
@@ -41,12 +41,13 @@ class ListView extends PureComponent {
41
41
  onPress={this.handleOnPress}
42
42
  styles={propStyles}
43
43
  item={item}
44
+ dataTestId={dataTestId ? `${dataTestId}.option.${index}` : undefined}
44
45
  />
45
46
  );
46
47
  };
47
48
 
48
49
  render() {
49
- const { filteredOptions, ItemSeparatorComponent, ListEmptyComponent } = this.props;
50
+ const { filteredOptions, ItemSeparatorComponent, ListEmptyComponent, dataTestId } = this.props;
50
51
  const { style } = this.state;
51
52
 
52
53
  return (
@@ -58,6 +59,7 @@ class ListView extends PureComponent {
58
59
  renderItem={this.renderItem}
59
60
  ItemSeparatorComponent={ItemSeparatorComponent}
60
61
  ListEmptyComponent={ListEmptyComponent}
62
+ testID={dataTestId}
61
63
  />
62
64
  );
63
65
  }
@@ -12,5 +12,6 @@ export default {
12
12
  maxHeight: number,
13
13
  windowHeight: number,
14
14
  usableWindowHeight: number,
15
- ItemSeparatorComponent: any
15
+ ItemSeparatorComponent: any,
16
+ dataTestId: string
16
17
  };
@@ -5,7 +5,7 @@ import { TouchableOpacity, Text } from 'react-native';
5
5
 
6
6
  import styles from './styles';
7
7
 
8
- const MenuOption = ({ onPress, label, selected, styles: propStyles, item }) => (
8
+ const MenuOption = ({ onPress, label, selected, styles: propStyles, item, dataTestId }) => (
9
9
  <TouchableOpacity
10
10
  onPress={() => onPress(item)}
11
11
  style={[
@@ -14,8 +14,9 @@ const MenuOption = ({ onPress, label, selected, styles: propStyles, item }) => (
14
14
  selected && styles.selected,
15
15
  selected && propStyles?.menuOptionSelected
16
16
  ]}
17
+ testID={dataTestId}
17
18
  >
18
- <Text>{label}</Text>
19
+ <Text testID={dataTestId ? `${dataTestId}.label` : undefined}>{label}</Text>
19
20
  </TouchableOpacity>
20
21
  );
21
22
 
@@ -25,7 +26,8 @@ MenuOption.propTypes = {
25
26
  selected: bool,
26
27
  styles: ViewPropTypes.style,
27
28
  // eslint-disable-next-line react/forbid-prop-types
28
- item: shape(any)
29
+ item: shape(any),
30
+ dataTestId: string
29
31
  };
30
32
 
31
33
  export default memo(MenuOption);
@@ -13,6 +13,7 @@ import useKeyboardHeight from '../../hooks/useKeyboardHeight';
13
13
  import UTTextInput from '../UTTextInput';
14
14
  import UTLabel from '../UTLabel';
15
15
  import Surface from '../Surface';
16
+ import { TEST_ID_CONSTANTS } from '../../constants/testIds';
16
17
 
17
18
  import MenuOption from './components/MenuOption';
18
19
  import ListView from './components/ListView';
@@ -21,6 +22,8 @@ import styles from './styles';
21
22
  import GoogleLogo from './assets/google_on_white.png';
22
23
  import { ATTRIBUTION } from './constants';
23
24
 
25
+ const { anchor, modal, searchInput, list, attribution, text, logo } = TEST_ID_CONSTANTS;
26
+
24
27
  const UTMenu = ({
25
28
  autoCompletePlaceholder,
26
29
  avoidOverlappingAnchor = true,
@@ -44,7 +47,8 @@ const UTMenu = ({
44
47
  verticalOffset = 0,
45
48
  withAutocomplete,
46
49
  withoutOpacity,
47
- withGoogleAttribution = false
50
+ withGoogleAttribution = false,
51
+ dataTestId
48
52
  }) => {
49
53
  const [isOpen, setIsOpen] = useState(false);
50
54
  const anchorRef = useRef(null);
@@ -138,6 +142,7 @@ const UTMenu = ({
138
142
  activeOpacity={withoutOpacity && 1}
139
143
  disabled={disabled}
140
144
  onPress={isOpen ? closeMenu : openMenu}
145
+ testID={dataTestId ? `${dataTestId}.${anchor}` : undefined}
141
146
  >
142
147
  <View onLayout={handleAnchorLayout} pointerEvents="box-only" ref={anchorRef}>
143
148
  {children}
@@ -149,6 +154,7 @@ const UTMenu = ({
149
154
  onShow={focusSearchInput}
150
155
  transparent
151
156
  visible={isOpen}
157
+ testID={dataTestId ? `${dataTestId}.${modal}` : undefined}
152
158
  >
153
159
  <TouchableOpacity onPress={closeMenu} style={styles.overlayTouchable}>
154
160
  <View style={styles.overlay} />
@@ -170,6 +176,7 @@ const UTMenu = ({
170
176
  }
171
177
  placeholder={autoCompletePlaceholder}
172
178
  value={query}
179
+ dataTestId={dataTestId ? `${dataTestId}.${searchInput}` : undefined}
173
180
  />
174
181
  </View>
175
182
  )}
@@ -187,13 +194,21 @@ const UTMenu = ({
187
194
  selectedOption={selectedOption}
188
195
  usableWindowHeight={usableWindowHeight}
189
196
  windowHeight={windowHeight}
197
+ dataTestId={dataTestId ? `${dataTestId}.${list}` : undefined}
190
198
  />
191
199
  {withGoogleAttribution && (
192
200
  <View style={styles.attribution}>
193
- <UTLabel colorTheme="gray" variant="small">
201
+ <UTLabel
202
+ colorTheme="gray"
203
+ variant="small"
204
+ dataTestId={dataTestId ? `${dataTestId}.${attribution}.${text}` : undefined}
205
+ >
194
206
  {ATTRIBUTION}
195
207
  </UTLabel>
196
- <Image source={GoogleLogo} />
208
+ <Image
209
+ source={GoogleLogo}
210
+ testID={dataTestId ? `${dataTestId}.${attribution}.${logo}` : undefined}
211
+ />
197
212
  </View>
198
213
  )}
199
214
  </KeyboardAvoidingView>
@@ -17,5 +17,6 @@ export default {
17
17
  horizontalOffset: number,
18
18
  onPress: func,
19
19
  disabled: bool,
20
- withoutOpacity: bool
20
+ withoutOpacity: bool,
21
+ dataTestId: string
21
22
  };