@terreno/ui 0.9.3 → 0.11.0

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 (94) hide show
  1. package/dist/Banner.js +2 -2
  2. package/dist/Banner.js.map +1 -1
  3. package/dist/Common.d.ts +6 -10
  4. package/dist/Common.js.map +1 -1
  5. package/dist/ConsentHistory.d.ts +2 -1
  6. package/dist/DataTable.js +4 -2
  7. package/dist/DataTable.js.map +1 -1
  8. package/dist/DateUtilities.js +16 -7
  9. package/dist/DateUtilities.js.map +1 -1
  10. package/dist/DraggableList.d.ts +5 -4
  11. package/dist/DraggableList.js.map +1 -1
  12. package/dist/Icon.js +0 -3
  13. package/dist/Icon.js.map +1 -1
  14. package/dist/PasswordField.d.ts +1 -1
  15. package/dist/PasswordField.js.map +1 -1
  16. package/dist/TextFieldNumberActionSheet.d.ts +1 -1
  17. package/dist/Toast.d.ts +1 -1
  18. package/dist/Toast.js +2 -2
  19. package/dist/Toast.js.map +1 -1
  20. package/dist/index.d.ts +2 -2
  21. package/package.json +2 -1
  22. package/src/ActionSheet.test.tsx +262 -3
  23. package/src/AddressField.test.tsx +50 -0
  24. package/src/Banner.test.tsx +22 -0
  25. package/src/Banner.tsx +2 -2
  26. package/src/Box.test.tsx +218 -0
  27. package/src/Button.test.tsx +71 -0
  28. package/src/Common.ts +11 -9
  29. package/src/ConsentFormScreen.test.tsx +167 -0
  30. package/src/ConsentHistory.tsx +1 -1
  31. package/src/ConsentNavigator.test.tsx +210 -4
  32. package/src/DataTable.tsx +15 -15
  33. package/src/DateUtilities.test.ts +34 -16
  34. package/src/DateUtilities.tsx +24 -13
  35. package/src/DecimalRangeActionSheet.test.tsx +53 -2
  36. package/src/DraggableList.tsx +5 -5
  37. package/src/EmailField.test.tsx +81 -0
  38. package/src/EmojiSelector.test.tsx +262 -1
  39. package/src/ErrorBoundary.test.tsx +52 -1
  40. package/src/HeightActionSheet.test.tsx +57 -2
  41. package/src/Icon.tsx +0 -3
  42. package/src/InfoModalIcon.test.tsx +16 -0
  43. package/src/InfoTooltipButton.test.tsx +53 -1
  44. package/src/MobileAddressAutoComplete.test.tsx +137 -7
  45. package/src/Modal.test.tsx +188 -0
  46. package/src/NumberPickerActionSheet.test.tsx +59 -2
  47. package/src/OpenAPIContext.test.tsx +184 -3
  48. package/src/Page.test.tsx +162 -1
  49. package/src/Pagination.test.tsx +16 -0
  50. package/src/PasswordField.tsx +1 -1
  51. package/src/PhoneNumberField.test.tsx +46 -9
  52. package/src/PickerSelect.test.tsx +230 -0
  53. package/src/SegmentedControl.test.tsx +38 -0
  54. package/src/SelectBadge.test.tsx +52 -1
  55. package/src/SideDrawer.test.tsx +69 -0
  56. package/src/Signature.test.tsx +42 -5
  57. package/src/SignatureField.test.tsx +35 -0
  58. package/src/Slider.test.tsx +59 -0
  59. package/src/Spinner.test.tsx +6 -0
  60. package/src/SplitPage.test.tsx +228 -2
  61. package/src/TapToEdit.test.tsx +171 -1
  62. package/src/TerrenoProvider.test.tsx +42 -2
  63. package/src/TextFieldNumberActionSheet.tsx +1 -1
  64. package/src/Theme.test.tsx +118 -28
  65. package/src/Toast.test.tsx +95 -2
  66. package/src/Toast.tsx +3 -3
  67. package/src/Tooltip.test.tsx +204 -1
  68. package/src/UnifiedAddressAutoComplete.test.tsx +38 -19
  69. package/src/UserInactivity.test.tsx +73 -1
  70. package/src/Utilities.test.tsx +190 -2
  71. package/src/WebAddressAutocomplete.test.tsx +148 -1
  72. package/src/__snapshots__/ActionSheet.test.tsx.snap +1736 -0
  73. package/src/__snapshots__/Button.test.tsx.snap +68 -0
  74. package/src/__snapshots__/EmojiSelector.test.tsx.snap +1363 -0
  75. package/src/__snapshots__/InfoTooltipButton.test.tsx.snap +72 -3
  76. package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +60 -9
  77. package/src/__snapshots__/Modal.test.tsx.snap +181 -0
  78. package/src/__snapshots__/Page.test.tsx.snap +48 -2
  79. package/src/__snapshots__/PhoneNumberField.test.tsx.snap +0 -93
  80. package/src/__snapshots__/PickerSelect.test.tsx.snap +706 -0
  81. package/src/__snapshots__/SideDrawer.test.tsx.snap +533 -1399
  82. package/src/__snapshots__/Signature.test.tsx.snap +0 -3
  83. package/src/__snapshots__/SplitPage.test.tsx.snap +970 -0
  84. package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +220 -4
  85. package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +93 -0
  86. package/src/bunSetup.ts +204 -121
  87. package/src/index.tsx +2 -2
  88. package/src/table/TableHeaderCell.test.tsx +142 -0
  89. package/src/table/TableRow.test.tsx +33 -0
  90. package/src/table/__snapshots__/TableRow.test.tsx.snap +403 -0
  91. package/src/table/tableContext.test.tsx +96 -0
  92. package/src/test-utils.tsx +1 -1
  93. package/src/useConsentForms.test.ts +130 -0
  94. package/src/useSubmitConsent.test.ts +64 -0
@@ -51,4 +51,20 @@ describe("InfoModalIcon", () => {
51
51
  fireEvent.press(infoIcon);
52
52
  expect(toJSON()).toMatchSnapshot();
53
53
  });
54
+
55
+ it("invokes primary Close button handler without throwing", () => {
56
+ const {getByTestId, getByText} = renderWithTheme(
57
+ <InfoModalIcon infoModalText="Dismissable text" infoModalTitle="Dismissable Title" />
58
+ );
59
+ fireEvent.press(getByTestId("info-icon"));
60
+ expect(() => fireEvent.press(getByText("Close"))).not.toThrow();
61
+ });
62
+
63
+ it("invokes onDismiss when the close (x) icon is pressed", () => {
64
+ const {getByTestId, getByLabelText} = renderWithTheme(
65
+ <InfoModalIcon infoModalText="Body" infoModalTitle="Close Me" />
66
+ );
67
+ fireEvent.press(getByTestId("info-icon"));
68
+ expect(() => fireEvent.press(getByLabelText("Close modal"))).not.toThrow();
69
+ });
54
70
  });
@@ -1,4 +1,38 @@
1
- import {describe, expect, it} from "bun:test";
1
+ import {afterAll, describe, expect, it, mock} from "bun:test";
2
+ import {fireEvent} from "@testing-library/react-native";
3
+ import {Pressable, Text as RNText} from "react-native";
4
+
5
+ // Override the IconButton mock so the inline onClick arrow fires when pressed.
6
+ mock.module("./IconButton", () => ({
7
+ IconButton: ({
8
+ accessibilityLabel,
9
+ accessibilityHint,
10
+ iconName,
11
+ onClick,
12
+ tooltipText,
13
+ }: {
14
+ accessibilityLabel?: string;
15
+ accessibilityHint?: string;
16
+ iconName: string;
17
+ onClick?: () => void;
18
+ tooltipText?: string;
19
+ }) => (
20
+ <Pressable
21
+ accessibilityHint={accessibilityHint}
22
+ accessibilityLabel={accessibilityLabel}
23
+ onPress={onClick}
24
+ testID={`icon-button-${iconName}`}
25
+ >
26
+ <RNText>{tooltipText}</RNText>
27
+ </Pressable>
28
+ ),
29
+ }));
30
+
31
+ afterAll(() => {
32
+ mock.module("./IconButton", () => ({
33
+ IconButton: mock(() => null),
34
+ }));
35
+ });
2
36
 
3
37
  import {InfoTooltipButton} from "./InfoTooltipButton";
4
38
  import {renderWithTheme} from "./test-utils";
@@ -19,4 +53,22 @@ describe("InfoTooltipButton", () => {
19
53
  // The component renders an IconButton with exclamation icon
20
54
  expect(toJSON()).toMatchSnapshot();
21
55
  });
56
+
57
+ it("is defined and is a function component", () => {
58
+ expect(InfoTooltipButton).toBeDefined();
59
+ expect(typeof InfoTooltipButton).toBe("function");
60
+ });
61
+
62
+ it("accepts a text prop without throwing", () => {
63
+ expect(() =>
64
+ renderWithTheme(<InfoTooltipButton text="Some details that explain the field" />)
65
+ ).not.toThrow();
66
+ });
67
+
68
+ it("fires the inline onClick handler when the IconButton is pressed", () => {
69
+ const {getByTestId} = renderWithTheme(
70
+ <InfoTooltipButton text="Some details that explain the field" />
71
+ );
72
+ expect(() => fireEvent.press(getByTestId("icon-button-exclamation"))).not.toThrow();
73
+ });
22
74
  });
@@ -1,17 +1,79 @@
1
1
  import {describe, expect, it, mock} from "bun:test";
2
- import {forwardRef} from "react";
3
- import {Text, View} from "react-native";
2
+ import {fireEvent} from "@testing-library/react-native";
3
+ import {forwardRef, useImperativeHandle, useRef} from "react";
4
+ import {Pressable, Text, View} from "react-native";
4
5
 
5
6
  import {MobileAddressAutocomplete} from "./MobileAddressAutoComplete";
6
7
  import {renderWithTheme} from "./test-utils";
7
8
 
9
+ // Capture the props passed to GooglePlacesAutocomplete so we can exercise the inline
10
+ // callbacks (onPress, onFocus, onBlur, onChange, textInputProps, etc.)
11
+ interface CapturedGooglePlacesProps {
12
+ placeholder?: string;
13
+ textInputProps?: {
14
+ onFocus?: () => void;
15
+ onBlur?: () => void;
16
+ onChange?: (event: {nativeEvent: {text: string}}) => void;
17
+ };
18
+ onPress?: (
19
+ data: {description: string},
20
+ details: {
21
+ address_components: {
22
+ long_name: string;
23
+ short_name: string;
24
+ types: string[];
25
+ }[];
26
+ }
27
+ ) => void;
28
+ }
29
+
30
+ let lastGooglePlacesProps: CapturedGooglePlacesProps | null = null;
31
+ const setAddressTextSpy = mock(() => {});
32
+
8
33
  // Mock react-native-google-places-autocomplete
9
34
  mock.module("react-native-google-places-autocomplete", () => ({
10
- GooglePlacesAutocomplete: forwardRef(({placeholder}: any, ref) => (
11
- <View ref={ref as any} testID="google-places-autocomplete">
12
- <Text>{placeholder}</Text>
13
- </View>
14
- )),
35
+ GooglePlacesAutocomplete: forwardRef((props: CapturedGooglePlacesProps, ref) => {
36
+ lastGooglePlacesProps = props;
37
+ const innerRef = useRef<Record<string, unknown>>({});
38
+ useImperativeHandle(ref, () => ({
39
+ setAddressText: setAddressTextSpy,
40
+ ...innerRef.current,
41
+ }));
42
+ return (
43
+ <View testID="google-places-autocomplete">
44
+ <Text>{props.placeholder}</Text>
45
+ <Pressable
46
+ onPress={() =>
47
+ props.onPress?.(
48
+ {description: "123 Main St"},
49
+ {
50
+ address_components: [
51
+ {long_name: "123", short_name: "123", types: ["street_number"]},
52
+ {long_name: "Main St", short_name: "Main St", types: ["route"]},
53
+ {long_name: "San Francisco", short_name: "SF", types: ["locality"]},
54
+ {
55
+ long_name: "California",
56
+ short_name: "CA",
57
+ types: ["administrative_area_level_1"],
58
+ },
59
+ {
60
+ long_name: "San Francisco County",
61
+ short_name: "SF County",
62
+ types: ["administrative_area_level_2"],
63
+ },
64
+ {long_name: "United States", short_name: "US", types: ["country"]},
65
+ {long_name: "94105", short_name: "94105", types: ["postal_code"]},
66
+ ],
67
+ }
68
+ )
69
+ }
70
+ testID="mock-google-places-select"
71
+ >
72
+ <Text>Select</Text>
73
+ </Pressable>
74
+ </View>
75
+ );
76
+ }),
15
77
  }));
16
78
 
17
79
  describe("MobileAddressAutocomplete", () => {
@@ -55,4 +117,72 @@ describe("MobileAddressAutocomplete", () => {
55
117
  );
56
118
  expect(toJSON()).toMatchSnapshot();
57
119
  });
120
+
121
+ it("invokes handleAutoCompleteChange with processed address components", () => {
122
+ const handleAutoCompleteChange = mock(() => {});
123
+ const handleAddressChange = mock(() => {});
124
+ setAddressTextSpy.mockClear();
125
+ const {getByTestId} = renderWithTheme(
126
+ <MobileAddressAutocomplete
127
+ googleMapsApiKey="test-api-key"
128
+ handleAddressChange={handleAddressChange}
129
+ handleAutoCompleteChange={handleAutoCompleteChange}
130
+ includeCounty
131
+ inputValue=""
132
+ />
133
+ );
134
+ fireEvent.press(getByTestId("mock-google-places-select"));
135
+ expect(handleAutoCompleteChange).toHaveBeenCalled();
136
+ const payload = handleAutoCompleteChange.mock.calls[0][0] as {address1?: string};
137
+ expect(payload.address1).toContain("Main St");
138
+ expect(setAddressTextSpy).toHaveBeenCalled();
139
+ });
140
+
141
+ it("fires onFocus, onBlur and onChange via textInputProps callbacks", () => {
142
+ const handleAddressChange = mock(() => {});
143
+ renderWithTheme(
144
+ <MobileAddressAutocomplete
145
+ googleMapsApiKey="test-api-key"
146
+ handleAddressChange={handleAddressChange}
147
+ handleAutoCompleteChange={() => {}}
148
+ inputValue=""
149
+ />
150
+ );
151
+ const tip = lastGooglePlacesProps?.textInputProps;
152
+ expect(typeof tip?.onFocus).toBe("function");
153
+ expect(typeof tip?.onBlur).toBe("function");
154
+ expect(typeof tip?.onChange).toBe("function");
155
+ tip?.onFocus?.();
156
+ tip?.onBlur?.();
157
+ tip?.onChange?.({nativeEvent: {text: "456 Oak Ave"}});
158
+ expect(handleAddressChange).toHaveBeenCalledWith("456 Oak Ave");
159
+ });
160
+
161
+ it("falls back to the TextField and propagates its onChange without an API key", () => {
162
+ const handleAddressChange = mock(() => {});
163
+ const {UNSAFE_root} = renderWithTheme(
164
+ <MobileAddressAutocomplete
165
+ handleAddressChange={handleAddressChange}
166
+ handleAutoCompleteChange={() => {}}
167
+ inputValue=""
168
+ testID="mobile-fallback"
169
+ />
170
+ );
171
+ expect(UNSAFE_root).toBeTruthy();
172
+ });
173
+
174
+ it("wrapping TouchableOpacity clears focus when pressed", () => {
175
+ const {UNSAFE_getAllByType} = renderWithTheme(
176
+ <MobileAddressAutocomplete
177
+ googleMapsApiKey="test-api-key"
178
+ handleAddressChange={() => {}}
179
+ handleAutoCompleteChange={() => {}}
180
+ inputValue=""
181
+ />
182
+ );
183
+ const {TouchableOpacity} = require("react-native");
184
+ const [wrapper] = UNSAFE_getAllByType(TouchableOpacity);
185
+ expect(wrapper).toBeTruthy();
186
+ expect(() => wrapper.props.onPress?.()).not.toThrow();
187
+ });
58
188
  });
@@ -5,6 +5,16 @@ import {Modal} from "./Modal";
5
5
  import {Text} from "./Text";
6
6
  import {renderWithTheme} from "./test-utils";
7
7
 
8
+ // Minimal shape of a test instance returned by UNSAFE_getAllByType that we rely on here.
9
+ interface PressableTestInstance {
10
+ props: {
11
+ style?:
12
+ | {backgroundColor?: string; cursor?: string}
13
+ | {backgroundColor?: string; cursor?: string}[];
14
+ onPress?: (event?: {stopPropagation?: () => void}) => void;
15
+ };
16
+ }
17
+
8
18
  describe("Modal", () => {
9
19
  it("renders correctly when visible", () => {
10
20
  const {toJSON} = renderWithTheme(
@@ -166,4 +176,182 @@ describe("Modal", () => {
166
176
  );
167
177
  expect(toJSON()).toMatchSnapshot();
168
178
  });
179
+
180
+ it("renders primary button with click handler", () => {
181
+ const handlePrimary = mock(() => {});
182
+ const {getByText} = renderWithTheme(
183
+ <Modal
184
+ onDismiss={() => {}}
185
+ primaryButtonOnClick={handlePrimary}
186
+ primaryButtonText="Confirm"
187
+ title="Title"
188
+ visible
189
+ >
190
+ <Text>Content</Text>
191
+ </Modal>
192
+ );
193
+ expect(getByText("Confirm")).toBeTruthy();
194
+ });
195
+
196
+ it("renders secondary button with click handler", () => {
197
+ const handleSecondary = mock(() => {});
198
+ const {getByText} = renderWithTheme(
199
+ <Modal
200
+ onDismiss={() => {}}
201
+ secondaryButtonOnClick={handleSecondary}
202
+ secondaryButtonText="Cancel"
203
+ title="Title"
204
+ visible
205
+ >
206
+ <Text>Content</Text>
207
+ </Modal>
208
+ );
209
+ expect(getByText("Cancel")).toBeTruthy();
210
+ });
211
+
212
+ it("does not call primaryButtonOnClick when not visible", () => {
213
+ const handlePrimary = mock(() => {});
214
+ renderWithTheme(
215
+ <Modal
216
+ onDismiss={() => {}}
217
+ primaryButtonOnClick={handlePrimary}
218
+ primaryButtonText="Confirm"
219
+ title="Title"
220
+ visible={false}
221
+ >
222
+ <Text>Content</Text>
223
+ </Modal>
224
+ );
225
+ expect(handlePrimary).not.toHaveBeenCalled();
226
+ });
227
+
228
+ it("renders with persistOnBackgroundClick", () => {
229
+ const {toJSON} = renderWithTheme(
230
+ <Modal onDismiss={() => {}} persistOnBackgroundClick title="Persistent" visible>
231
+ <Text>Content</Text>
232
+ </Modal>
233
+ );
234
+ expect(toJSON()).toMatchSnapshot();
235
+ });
236
+
237
+ it("does not call onDismiss when visible is false and close is pressed", () => {
238
+ const handleDismiss = mock(() => {});
239
+ renderWithTheme(
240
+ <Modal onDismiss={handleDismiss} title="Hidden" visible={false}>
241
+ <Text>Content</Text>
242
+ </Modal>
243
+ );
244
+ expect(handleDismiss).not.toHaveBeenCalled();
245
+ });
246
+
247
+ it("renders transitioning from hidden to visible", () => {
248
+ const {rerender, toJSON} = renderWithTheme(
249
+ <Modal onDismiss={() => {}} title="Toggle" visible={false}>
250
+ <Text>Content</Text>
251
+ </Modal>
252
+ );
253
+ rerender(
254
+ <Modal onDismiss={() => {}} title="Toggle" visible>
255
+ <Text>Content</Text>
256
+ </Modal>
257
+ );
258
+ expect(toJSON()).toBeTruthy();
259
+ });
260
+
261
+ it("invokes primaryButtonOnClick when primary button pressed while visible", async () => {
262
+ const handlePrimary = mock(() => {});
263
+ const {getByText} = renderWithTheme(
264
+ <Modal
265
+ onDismiss={() => {}}
266
+ primaryButtonOnClick={handlePrimary}
267
+ primaryButtonText="Submit"
268
+ title="Title"
269
+ visible
270
+ >
271
+ <Text>Content</Text>
272
+ </Modal>
273
+ );
274
+
275
+ await new Promise((resolve) => {
276
+ fireEvent.press(getByText("Submit"));
277
+ setTimeout(resolve, 600);
278
+ });
279
+
280
+ expect(handlePrimary).toHaveBeenCalled();
281
+ });
282
+
283
+ it("invokes secondaryButtonOnClick when secondary button pressed while visible", async () => {
284
+ const handleSecondary = mock(() => {});
285
+ const {getByText} = renderWithTheme(
286
+ <Modal
287
+ onDismiss={() => {}}
288
+ secondaryButtonOnClick={handleSecondary}
289
+ secondaryButtonText="Cancel"
290
+ title="Title"
291
+ visible
292
+ >
293
+ <Text>Content</Text>
294
+ </Modal>
295
+ );
296
+
297
+ await new Promise((resolve) => {
298
+ fireEvent.press(getByText("Cancel"));
299
+ setTimeout(resolve, 600);
300
+ });
301
+
302
+ expect(handleSecondary).toHaveBeenCalled();
303
+ });
304
+
305
+ it("dismisses when the backdrop is pressed and persistOnBackgroundClick is false", () => {
306
+ const handleDismiss = mock(() => {});
307
+ const {UNSAFE_getAllByType} = renderWithTheme(
308
+ <Modal onDismiss={handleDismiss} title="Title" visible>
309
+ <Text>Content</Text>
310
+ </Modal>
311
+ );
312
+ // Find the backdrop Pressable (first Pressable in tree with a style that includes backgroundColor).
313
+ const {Pressable} = require("react-native");
314
+ const pressables: PressableTestInstance[] = UNSAFE_getAllByType(Pressable);
315
+ const backdrop = pressables.find((node) => {
316
+ const style = node.props.style;
317
+ if (Array.isArray(style)) {
318
+ return style.some((s) => s?.backgroundColor?.includes?.("rgba"));
319
+ }
320
+ return style?.backgroundColor?.includes?.("rgba");
321
+ });
322
+ expect(backdrop).toBeTruthy();
323
+ backdrop?.props.onPress?.();
324
+ expect(handleDismiss).toHaveBeenCalled();
325
+ });
326
+
327
+ it("stops propagation on the inner backdrop wrapper press", () => {
328
+ const stopPropagation = mock(() => {});
329
+ const {UNSAFE_getAllByType} = renderWithTheme(
330
+ <Modal onDismiss={() => {}} title="Title" visible>
331
+ <Text>Content</Text>
332
+ </Modal>
333
+ );
334
+ const {Pressable} = require("react-native");
335
+ const pressables: PressableTestInstance[] = UNSAFE_getAllByType(Pressable);
336
+ // Inner wrapper is the pressable with style {cursor: "auto"}.
337
+ const inner = pressables.find((node) => node.props.style?.cursor === "auto");
338
+ expect(inner).toBeTruthy();
339
+ inner?.props.onPress?.({stopPropagation});
340
+ expect(stopPropagation).toHaveBeenCalled();
341
+ });
342
+
343
+ it("does not stop propagation on the inner wrapper when persistOnBackgroundClick is true", () => {
344
+ const stopPropagation = mock(() => {});
345
+ const {UNSAFE_getAllByType} = renderWithTheme(
346
+ <Modal onDismiss={() => {}} persistOnBackgroundClick title="Title" visible>
347
+ <Text>Content</Text>
348
+ </Modal>
349
+ );
350
+ const {Pressable} = require("react-native");
351
+ const pressables: PressableTestInstance[] = UNSAFE_getAllByType(Pressable);
352
+ const inner = pressables.find((node) => node.props.style?.cursor === "auto");
353
+ expect(inner).toBeTruthy();
354
+ inner?.props.onPress?.({stopPropagation});
355
+ expect(stopPropagation).not.toHaveBeenCalled();
356
+ });
169
357
  });
@@ -1,6 +1,7 @@
1
- import {describe, expect, it} from "bun:test";
2
- import {render} from "@testing-library/react-native";
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {act, render} from "@testing-library/react-native";
3
3
  import {createRef} from "react";
4
+ import type {ReactTestInstance} from "react-test-renderer";
4
5
 
5
6
  import type {ActionSheet} from "./ActionSheet";
6
7
  import {NumberPickerActionSheet} from "./NumberPickerActionSheet";
@@ -43,4 +44,60 @@ describe("NumberPickerActionSheet", () => {
43
44
  );
44
45
  expect(toJSON()).toMatchSnapshot();
45
46
  });
47
+
48
+ it("invokes onChange when picker value changes", () => {
49
+ const actionSheetRef = createRef<ActionSheet>();
50
+ const handleChange = mock((_val: string) => {});
51
+ const {UNSAFE_getAllByProps} = render(
52
+ <ThemeProvider>
53
+ <NumberPickerActionSheet
54
+ actionSheetRef={actionSheetRef}
55
+ max={10}
56
+ min={0}
57
+ onChange={handleChange}
58
+ value="5"
59
+ />
60
+ </ThemeProvider>
61
+ );
62
+ const pickers = UNSAFE_getAllByProps({selectedValue: "5"});
63
+ const picker = pickers.find(
64
+ (p: ReactTestInstance) => typeof p.props.onValueChange === "function"
65
+ );
66
+ act(() => {
67
+ if (picker) {
68
+ picker.props.onValueChange(7);
69
+ }
70
+ });
71
+ expect(handleChange).toHaveBeenCalledWith("7");
72
+ });
73
+
74
+ it("closes the action sheet when Close button is pressed", () => {
75
+ const setModalVisible = mock((_v: boolean) => {});
76
+ const actionSheetRef = createRef<ActionSheet>();
77
+ const {UNSAFE_getAllByProps} = render(
78
+ <ThemeProvider>
79
+ <NumberPickerActionSheet
80
+ actionSheetRef={actionSheetRef}
81
+ max={10}
82
+ min={0}
83
+ onChange={() => {}}
84
+ value="5"
85
+ />
86
+ </ThemeProvider>
87
+ );
88
+ // Replace the ref target with a mock after mount so the Button's onClick
89
+ // invokes our spy instead of the real ActionSheet instance.
90
+ (actionSheetRef as {current: {setModalVisible: typeof setModalVisible}}).current = {
91
+ setModalVisible,
92
+ };
93
+ const closeButtons = UNSAFE_getAllByProps({text: "Close"});
94
+ const closeButton = closeButtons.find(
95
+ (b: ReactTestInstance) => typeof b.props.onClick === "function"
96
+ );
97
+ expect(closeButton).toBeDefined();
98
+ act(() => {
99
+ closeButton?.props.onClick();
100
+ });
101
+ expect(setModalVisible).toHaveBeenCalledWith(false);
102
+ });
46
103
  });