@terreno/ui 0.10.0 → 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 (71) hide show
  1. package/dist/Banner.js +2 -2
  2. package/dist/Banner.js.map +1 -1
  3. package/dist/TextFieldNumberActionSheet.d.ts +1 -1
  4. package/dist/Toast.d.ts +1 -1
  5. package/dist/Toast.js +2 -2
  6. package/dist/Toast.js.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/package.json +2 -1
  9. package/src/ActionSheet.test.tsx +262 -3
  10. package/src/AddressField.test.tsx +50 -0
  11. package/src/Banner.test.tsx +22 -0
  12. package/src/Banner.tsx +2 -2
  13. package/src/Box.test.tsx +218 -0
  14. package/src/Button.test.tsx +71 -0
  15. package/src/ConsentFormScreen.test.tsx +167 -0
  16. package/src/ConsentNavigator.test.tsx +206 -0
  17. package/src/DecimalRangeActionSheet.test.tsx +53 -2
  18. package/src/EmailField.test.tsx +81 -0
  19. package/src/EmojiSelector.test.tsx +262 -1
  20. package/src/HeightActionSheet.test.tsx +57 -2
  21. package/src/InfoModalIcon.test.tsx +16 -0
  22. package/src/InfoTooltipButton.test.tsx +53 -1
  23. package/src/MobileAddressAutoComplete.test.tsx +137 -7
  24. package/src/Modal.test.tsx +188 -0
  25. package/src/NumberPickerActionSheet.test.tsx +59 -2
  26. package/src/Page.test.tsx +162 -1
  27. package/src/Pagination.test.tsx +16 -0
  28. package/src/PhoneNumberField.test.tsx +46 -9
  29. package/src/PickerSelect.test.tsx +230 -0
  30. package/src/SegmentedControl.test.tsx +38 -0
  31. package/src/SelectBadge.test.tsx +52 -1
  32. package/src/SideDrawer.test.tsx +69 -0
  33. package/src/Signature.test.tsx +42 -5
  34. package/src/SignatureField.test.tsx +35 -0
  35. package/src/Slider.test.tsx +59 -0
  36. package/src/Spinner.test.tsx +6 -0
  37. package/src/SplitPage.test.tsx +228 -2
  38. package/src/TapToEdit.test.tsx +171 -1
  39. package/src/TerrenoProvider.test.tsx +42 -2
  40. package/src/TextFieldNumberActionSheet.tsx +1 -1
  41. package/src/Theme.test.tsx +118 -28
  42. package/src/Toast.test.tsx +95 -2
  43. package/src/Toast.tsx +3 -3
  44. package/src/Tooltip.test.tsx +204 -1
  45. package/src/UnifiedAddressAutoComplete.test.tsx +38 -19
  46. package/src/UserInactivity.test.tsx +73 -1
  47. package/src/Utilities.test.tsx +190 -2
  48. package/src/WebAddressAutocomplete.test.tsx +148 -1
  49. package/src/__snapshots__/ActionSheet.test.tsx.snap +1736 -0
  50. package/src/__snapshots__/Button.test.tsx.snap +68 -0
  51. package/src/__snapshots__/EmojiSelector.test.tsx.snap +1363 -0
  52. package/src/__snapshots__/InfoTooltipButton.test.tsx.snap +72 -3
  53. package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +60 -9
  54. package/src/__snapshots__/Modal.test.tsx.snap +181 -0
  55. package/src/__snapshots__/Page.test.tsx.snap +48 -2
  56. package/src/__snapshots__/PhoneNumberField.test.tsx.snap +0 -93
  57. package/src/__snapshots__/PickerSelect.test.tsx.snap +706 -0
  58. package/src/__snapshots__/SideDrawer.test.tsx.snap +533 -1399
  59. package/src/__snapshots__/Signature.test.tsx.snap +0 -3
  60. package/src/__snapshots__/SplitPage.test.tsx.snap +970 -0
  61. package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +220 -4
  62. package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +93 -0
  63. package/src/bunSetup.ts +204 -121
  64. package/src/index.tsx +2 -2
  65. package/src/table/TableHeaderCell.test.tsx +142 -0
  66. package/src/table/TableRow.test.tsx +33 -0
  67. package/src/table/__snapshots__/TableRow.test.tsx.snap +403 -0
  68. package/src/table/tableContext.test.tsx +96 -0
  69. package/src/test-utils.tsx +1 -1
  70. package/src/useConsentForms.test.ts +130 -0
  71. package/src/useSubmitConsent.test.ts +64 -0
@@ -1,5 +1,5 @@
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
4
 
5
5
  import type {ActionSheet} from "./ActionSheet";
@@ -43,4 +43,55 @@ describe("DecimalRangeActionSheet", () => {
43
43
  );
44
44
  expect(toJSON()).toMatchSnapshot();
45
45
  });
46
+
47
+ it("invokes onChange when whole picker changes", () => {
48
+ const actionSheetRef = createRef<ActionSheet>();
49
+ const handleChange = mock((_val: string) => {});
50
+ const {UNSAFE_getAllByProps} = render(
51
+ <ThemeProvider>
52
+ <DecimalRangeActionSheet
53
+ actionSheetRef={actionSheetRef}
54
+ max={10}
55
+ min={0}
56
+ onChange={handleChange}
57
+ value="5.5"
58
+ />
59
+ </ThemeProvider>
60
+ );
61
+ // The first Picker is the whole number one (selectedValue: "5")
62
+ const wholePickers = UNSAFE_getAllByProps({selectedValue: "5"});
63
+ const whole = wholePickers.find((p) => typeof p.props.onValueChange === "function");
64
+ act(() => {
65
+ if (whole) {
66
+ whole.props.onValueChange(7);
67
+ }
68
+ });
69
+ expect(handleChange).toHaveBeenCalled();
70
+ });
71
+
72
+ it("invokes onChange when decimal picker changes", () => {
73
+ const actionSheetRef = createRef<ActionSheet>();
74
+ const handleChange = mock((_val: string) => {});
75
+ const {UNSAFE_getAllByProps} = render(
76
+ <ThemeProvider>
77
+ <DecimalRangeActionSheet
78
+ actionSheetRef={actionSheetRef}
79
+ max={10}
80
+ min={0}
81
+ onChange={handleChange}
82
+ value="5.5"
83
+ />
84
+ </ThemeProvider>
85
+ );
86
+ // The second Picker is the decimal one (selectedValue: "5")
87
+ const decimalPickers = UNSAFE_getAllByProps({selectedValue: "5"});
88
+ // Use the last one (second picker)
89
+ const decimal = decimalPickers[decimalPickers.length - 1];
90
+ act(() => {
91
+ if (decimal && typeof decimal.props.onValueChange === "function") {
92
+ decimal.props.onValueChange(3);
93
+ }
94
+ });
95
+ expect(handleChange).toHaveBeenCalled();
96
+ });
46
97
  });
@@ -103,4 +103,85 @@ describe("EmailField", () => {
103
103
  );
104
104
  expect(toJSON()).toMatchSnapshot();
105
105
  });
106
+
107
+ it("calls onBlur with valid email", async () => {
108
+ const handleBlur = mock((_value: string) => {});
109
+ const {getByDisplayValue} = renderWithTheme(
110
+ <EmailField label="Email" onBlur={handleBlur} onChange={() => {}} value="valid@email.com" />
111
+ );
112
+
113
+ const input = getByDisplayValue("valid@email.com");
114
+ await act(async () => {
115
+ fireEvent(input, "blur", {nativeEvent: {text: "valid@email.com"}});
116
+ });
117
+
118
+ await waitFor(() => {
119
+ expect(handleBlur).toHaveBeenCalledWith("valid@email.com");
120
+ });
121
+ });
122
+
123
+ it("does not call onBlur with invalid email", async () => {
124
+ const handleBlur = mock((_value: string) => {});
125
+ const {getByDisplayValue} = renderWithTheme(
126
+ <EmailField label="Email" onBlur={handleBlur} onChange={() => {}} value="" />
127
+ );
128
+
129
+ const input = getByDisplayValue("");
130
+ await act(async () => {
131
+ fireEvent.changeText(input, "invalid-email");
132
+ });
133
+ await act(async () => {
134
+ fireEvent(input, "blur", {nativeEvent: {text: "invalid-email"}});
135
+ });
136
+
137
+ expect(handleBlur).not.toHaveBeenCalled();
138
+ });
139
+
140
+ it("clears local error when typing a valid email after invalid", async () => {
141
+ const handleChange = mock((_value: string) => {});
142
+ const {getByDisplayValue, queryByText} = renderWithTheme(
143
+ <EmailField label="Email" onChange={handleChange} value="" />
144
+ );
145
+
146
+ const input = getByDisplayValue("");
147
+
148
+ // Type invalid email first
149
+ await act(async () => {
150
+ fireEvent.changeText(input, "bad");
151
+ });
152
+ // Trigger blur to set the error
153
+ await act(async () => {
154
+ fireEvent(input, "blur", {nativeEvent: {text: "bad"}});
155
+ });
156
+
157
+ await waitFor(() => {
158
+ expect(queryByText("Invalid email address format")).toBeTruthy();
159
+ });
160
+
161
+ // Now type a valid email to clear the error
162
+ await act(async () => {
163
+ fireEvent.changeText(input, "good@email.com");
164
+ });
165
+
166
+ await waitFor(() => {
167
+ expect(queryByText("Invalid email address format")).toBeFalsy();
168
+ expect(handleChange).toHaveBeenCalledWith("good@email.com");
169
+ });
170
+ });
171
+
172
+ it("validates empty string as valid on blur", async () => {
173
+ const handleBlur = mock((_value: string) => {});
174
+ const {getByDisplayValue} = renderWithTheme(
175
+ <EmailField label="Email" onBlur={handleBlur} onChange={() => {}} value="" />
176
+ );
177
+
178
+ const input = getByDisplayValue("");
179
+ await act(async () => {
180
+ fireEvent(input, "blur", {nativeEvent: {text: ""}});
181
+ });
182
+
183
+ await waitFor(() => {
184
+ expect(handleBlur).toHaveBeenCalledWith("");
185
+ });
186
+ });
106
187
  });
@@ -1,8 +1,21 @@
1
1
  import {describe, expect, it, mock} from "bun:test";
2
+ import {act, fireEvent} from "@testing-library/react-native";
2
3
 
3
- import EmojiSelector, {Categories} from "./EmojiSelector";
4
+ import EmojiSelector, {Categories, charFromEmojiObject} from "./EmojiSelector";
4
5
  import {renderWithTheme} from "./test-utils";
5
6
 
7
+ interface LayoutEvent {
8
+ nativeEvent: {layout: {height: number; width: number; x: number; y: number}};
9
+ }
10
+
11
+ interface LayoutRoot {
12
+ props: {onLayout?: (event: LayoutEvent) => void};
13
+ }
14
+
15
+ interface RenderItemCell {
16
+ props: {onPress?: () => void};
17
+ }
18
+
6
19
  describe("EmojiSelector", () => {
7
20
  it("renders search bar when showSearchBar is true", () => {
8
21
  const {getByPlaceholderText} = renderWithTheme(
@@ -58,4 +71,252 @@ describe("EmojiSelector", () => {
58
71
 
59
72
  expect(tree.toJSON()).toMatchSnapshot();
60
73
  });
74
+
75
+ it("renders without tabs", () => {
76
+ const {toJSON} = renderWithTheme(
77
+ <EmojiSelector
78
+ category={Categories.people}
79
+ columns={6}
80
+ onEmojiSelected={mock(() => {})}
81
+ placeholder="Search emojis"
82
+ showHistory={false}
83
+ showSearchBar
84
+ showSectionTitles={false}
85
+ showTabs={false}
86
+ theme="#007AFF"
87
+ />
88
+ );
89
+ expect(toJSON()).toMatchSnapshot();
90
+ });
91
+
92
+ it("renders without search bar", () => {
93
+ const {queryByPlaceholderText} = renderWithTheme(
94
+ <EmojiSelector
95
+ category={Categories.people}
96
+ columns={6}
97
+ onEmojiSelected={mock(() => {})}
98
+ placeholder="Search emojis"
99
+ showHistory={false}
100
+ showSearchBar={false}
101
+ showSectionTitles
102
+ showTabs
103
+ theme="#007AFF"
104
+ />
105
+ );
106
+ expect(queryByPlaceholderText("Search emojis")).toBeNull();
107
+ });
108
+
109
+ it("renders with history enabled", () => {
110
+ const {toJSON} = renderWithTheme(
111
+ <EmojiSelector
112
+ category={Categories.people}
113
+ columns={6}
114
+ onEmojiSelected={mock(() => {})}
115
+ placeholder="Search emojis"
116
+ showHistory
117
+ showSearchBar
118
+ showSectionTitles
119
+ showTabs
120
+ theme="#007AFF"
121
+ />
122
+ );
123
+ expect(toJSON()).toMatchSnapshot();
124
+ });
125
+
126
+ it("renders with all category and shouldInclude filter", () => {
127
+ const {toJSON} = renderWithTheme(
128
+ <EmojiSelector
129
+ category={Categories.all}
130
+ columns={8}
131
+ onEmojiSelected={mock(() => {})}
132
+ placeholder="Search"
133
+ shouldInclude={(emoji) => emoji.category === "Smileys & Emotion"}
134
+ showHistory={false}
135
+ showSearchBar
136
+ showSectionTitles
137
+ showTabs
138
+ theme="#007AFF"
139
+ />
140
+ );
141
+ expect(toJSON()).toMatchSnapshot();
142
+ });
143
+
144
+ it("renders with history category", () => {
145
+ const {toJSON} = renderWithTheme(
146
+ <EmojiSelector
147
+ category={Categories.history}
148
+ columns={6}
149
+ onEmojiSelected={mock(() => {})}
150
+ placeholder="Search"
151
+ showHistory
152
+ showSearchBar
153
+ showSectionTitles
154
+ showTabs
155
+ theme="#007AFF"
156
+ />
157
+ );
158
+ expect(toJSON()).toMatchSnapshot();
159
+ });
160
+
161
+ it("updates search query when user types in search bar", async () => {
162
+ const {getByPlaceholderText} = renderWithTheme(
163
+ <EmojiSelector
164
+ category={Categories.all}
165
+ columns={6}
166
+ onEmojiSelected={mock(() => {})}
167
+ placeholder="Search emojis"
168
+ showHistory={false}
169
+ showSearchBar
170
+ showSectionTitles
171
+ showTabs
172
+ theme="#007AFF"
173
+ />
174
+ );
175
+ const input = getByPlaceholderText("Search emojis");
176
+ await act(async () => {
177
+ fireEvent.changeText(input, "smile");
178
+ });
179
+ expect(input.props.value).toBe("smile");
180
+ });
181
+
182
+ it("charFromEmojiObject returns a string for a valid emoji", () => {
183
+ const smiley = {
184
+ category: "Smileys & Emotion",
185
+ short_names: ["smiley"],
186
+ sort_order: 1,
187
+ unified: "1F603",
188
+ };
189
+ expect(charFromEmojiObject(smiley)).toBe("😃");
190
+ });
191
+
192
+ it("exports Categories object with all expected keys", () => {
193
+ expect(Categories.all).toBeDefined();
194
+ expect(Categories.emotion).toBeDefined();
195
+ expect(Categories.people).toBeDefined();
196
+ expect(Categories.history).toBeDefined();
197
+ expect(Categories.nature).toBeDefined();
198
+ expect(Categories.food).toBeDefined();
199
+ expect(Categories.activities).toBeDefined();
200
+ expect(Categories.places).toBeDefined();
201
+ expect(Categories.objects).toBeDefined();
202
+ expect(Categories.symbols).toBeDefined();
203
+ expect(Categories.flags).toBeDefined();
204
+ });
205
+
206
+ it("handles layout event to compute emoji list and set ready state", async () => {
207
+ const {toJSON, root} = renderWithTheme(
208
+ <EmojiSelector
209
+ category={Categories.people}
210
+ columns={6}
211
+ onEmojiSelected={mock(() => {})}
212
+ placeholder="Search"
213
+ showHistory={false}
214
+ showSearchBar
215
+ showSectionTitles
216
+ showTabs
217
+ theme="#007AFF"
218
+ />
219
+ );
220
+ // Trigger the onLayout callback so state moves to ready.
221
+ await act(async () => {
222
+ (root as LayoutRoot).props.onLayout?.({
223
+ nativeEvent: {layout: {height: 600, width: 360, x: 0, y: 0}},
224
+ });
225
+ });
226
+ expect(toJSON()).toBeTruthy();
227
+ });
228
+
229
+ it("switches categories when a tab is pressed after layout", async () => {
230
+ const {root, UNSAFE_getAllByType} = renderWithTheme(
231
+ <EmojiSelector
232
+ category={Categories.people}
233
+ columns={6}
234
+ onEmojiSelected={mock(() => {})}
235
+ placeholder="Search"
236
+ showHistory={false}
237
+ showSearchBar
238
+ showSectionTitles
239
+ showTabs
240
+ theme="#007AFF"
241
+ />
242
+ );
243
+ await act(async () => {
244
+ (root as LayoutRoot).props.onLayout?.({
245
+ nativeEvent: {layout: {height: 600, width: 360, x: 0, y: 0}},
246
+ });
247
+ });
248
+ const {TouchableOpacity} = require("react-native");
249
+ const tabs = UNSAFE_getAllByType(TouchableOpacity);
250
+ expect(tabs.length).toBeGreaterThan(0);
251
+ // Press the first tab (e.g., "Smileys & Emotion") to exercise handleTabSelect.
252
+ await act(async () => {
253
+ tabs[0].props.onPress?.();
254
+ });
255
+ });
256
+
257
+ it("invokes onEmojiSelected when an emoji cell is pressed", async () => {
258
+ const onEmojiSelected = mock(() => {});
259
+ const {root, UNSAFE_getAllByType} = renderWithTheme(
260
+ <EmojiSelector
261
+ category={Categories.emotion}
262
+ columns={6}
263
+ onEmojiSelected={onEmojiSelected}
264
+ placeholder="Search"
265
+ showHistory
266
+ showSearchBar={false}
267
+ showSectionTitles
268
+ showTabs={false}
269
+ theme="#007AFF"
270
+ />
271
+ );
272
+ await act(async () => {
273
+ (root as LayoutRoot).props.onLayout?.({
274
+ nativeEvent: {layout: {height: 600, width: 360, x: 0, y: 0}},
275
+ });
276
+ });
277
+
278
+ const {FlatList} = require("react-native");
279
+ const [list] = UNSAFE_getAllByType(FlatList);
280
+ expect(list).toBeTruthy();
281
+ const data = list.props.data ?? [];
282
+ expect(data.length).toBeGreaterThan(0);
283
+ const first = data[0];
284
+ const cell = list.props.renderItem({index: 0, item: first});
285
+ // Invoke the emoji cell onPress to exercise handleEmojiSelect + addToHistoryAsync.
286
+ (cell.props as RenderItemCell["props"]).onPress?.();
287
+ await act(async () => {
288
+ await new Promise((resolve) => setTimeout(resolve, 20));
289
+ });
290
+ expect(onEmojiSelected).toHaveBeenCalled();
291
+ });
292
+
293
+ it("filters the emoji list when a search query is entered", async () => {
294
+ const {getByPlaceholderText, UNSAFE_getAllByType, root} = renderWithTheme(
295
+ <EmojiSelector
296
+ category={Categories.all}
297
+ columns={6}
298
+ onEmojiSelected={mock(() => {})}
299
+ placeholder="Search emojis"
300
+ showHistory={false}
301
+ showSearchBar
302
+ showSectionTitles
303
+ showTabs
304
+ theme="#007AFF"
305
+ />
306
+ );
307
+ await act(async () => {
308
+ (root as LayoutRoot).props.onLayout?.({
309
+ nativeEvent: {layout: {height: 600, width: 360, x: 0, y: 0}},
310
+ });
311
+ });
312
+
313
+ const input = getByPlaceholderText("Search emojis");
314
+ await act(async () => {
315
+ fireEvent.changeText(input, "smile");
316
+ });
317
+
318
+ const {FlatList} = require("react-native");
319
+ const [list] = UNSAFE_getAllByType(FlatList);
320
+ expect(list.props.data.length).toBeGreaterThan(0);
321
+ });
61
322
  });
@@ -1,5 +1,5 @@
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
4
 
5
5
  import type {ActionSheet} from "./ActionSheet";
@@ -31,4 +31,59 @@ describe("HeightActionSheet", () => {
31
31
  );
32
32
  expect(toJSON()).toMatchSnapshot();
33
33
  });
34
+
35
+ it("renders with title and min/max values", () => {
36
+ const actionSheetRef = createRef<ActionSheet>();
37
+ const {getByText} = render(
38
+ <ThemeProvider>
39
+ <HeightActionSheet
40
+ actionSheetRef={actionSheetRef}
41
+ max={84}
42
+ min={36}
43
+ onChange={() => {}}
44
+ title="Select Height"
45
+ value="60"
46
+ />
47
+ </ThemeProvider>
48
+ );
49
+ expect(getByText("Select Height")).toBeTruthy();
50
+ });
51
+
52
+ it("invokes onChange when feet picker changes", () => {
53
+ const actionSheetRef = createRef<ActionSheet>();
54
+ const handleChange = mock((_val: string) => {});
55
+ const {UNSAFE_getAllByProps} = render(
56
+ <ThemeProvider>
57
+ <HeightActionSheet actionSheetRef={actionSheetRef} onChange={handleChange} value="72" />
58
+ </ThemeProvider>
59
+ );
60
+ // feet picker has selectedValue "6" (72 / 12)
61
+ const feetPickers = UNSAFE_getAllByProps({selectedValue: "6"});
62
+ const feetPicker = feetPickers.find((p) => typeof p.props.onValueChange === "function");
63
+ act(() => {
64
+ if (feetPicker) {
65
+ feetPicker.props.onValueChange(5);
66
+ }
67
+ });
68
+ expect(handleChange).toHaveBeenCalled();
69
+ });
70
+
71
+ it("invokes onChange when inches picker changes", () => {
72
+ const actionSheetRef = createRef<ActionSheet>();
73
+ const handleChange = mock((_val: string) => {});
74
+ const {UNSAFE_getAllByProps} = render(
75
+ <ThemeProvider>
76
+ <HeightActionSheet actionSheetRef={actionSheetRef} onChange={handleChange} value="73" />
77
+ </ThemeProvider>
78
+ );
79
+ // inches picker has selectedValue "1" (73 % 12)
80
+ const inchPickers = UNSAFE_getAllByProps({selectedValue: "1"});
81
+ const inchPicker = inchPickers.find((p) => typeof p.props.onValueChange === "function");
82
+ act(() => {
83
+ if (inchPicker) {
84
+ inchPicker.props.onValueChange(5);
85
+ }
86
+ });
87
+ expect(handleChange).toHaveBeenCalled();
88
+ });
34
89
  });
@@ -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
  });