@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.
- package/dist/Banner.js +2 -2
- package/dist/Banner.js.map +1 -1
- package/dist/TextFieldNumberActionSheet.d.ts +1 -1
- package/dist/Toast.d.ts +1 -1
- package/dist/Toast.js +2 -2
- package/dist/Toast.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/package.json +2 -1
- package/src/ActionSheet.test.tsx +262 -3
- package/src/AddressField.test.tsx +50 -0
- package/src/Banner.test.tsx +22 -0
- package/src/Banner.tsx +2 -2
- package/src/Box.test.tsx +218 -0
- package/src/Button.test.tsx +71 -0
- package/src/ConsentFormScreen.test.tsx +167 -0
- package/src/ConsentNavigator.test.tsx +206 -0
- package/src/DecimalRangeActionSheet.test.tsx +53 -2
- package/src/EmailField.test.tsx +81 -0
- package/src/EmojiSelector.test.tsx +262 -1
- package/src/HeightActionSheet.test.tsx +57 -2
- package/src/InfoModalIcon.test.tsx +16 -0
- package/src/InfoTooltipButton.test.tsx +53 -1
- package/src/MobileAddressAutoComplete.test.tsx +137 -7
- package/src/Modal.test.tsx +188 -0
- package/src/NumberPickerActionSheet.test.tsx +59 -2
- package/src/Page.test.tsx +162 -1
- package/src/Pagination.test.tsx +16 -0
- package/src/PhoneNumberField.test.tsx +46 -9
- package/src/PickerSelect.test.tsx +230 -0
- package/src/SegmentedControl.test.tsx +38 -0
- package/src/SelectBadge.test.tsx +52 -1
- package/src/SideDrawer.test.tsx +69 -0
- package/src/Signature.test.tsx +42 -5
- package/src/SignatureField.test.tsx +35 -0
- package/src/Slider.test.tsx +59 -0
- package/src/Spinner.test.tsx +6 -0
- package/src/SplitPage.test.tsx +228 -2
- package/src/TapToEdit.test.tsx +171 -1
- package/src/TerrenoProvider.test.tsx +42 -2
- package/src/TextFieldNumberActionSheet.tsx +1 -1
- package/src/Theme.test.tsx +118 -28
- package/src/Toast.test.tsx +95 -2
- package/src/Toast.tsx +3 -3
- package/src/Tooltip.test.tsx +204 -1
- package/src/UnifiedAddressAutoComplete.test.tsx +38 -19
- package/src/UserInactivity.test.tsx +73 -1
- package/src/Utilities.test.tsx +190 -2
- package/src/WebAddressAutocomplete.test.tsx +148 -1
- package/src/__snapshots__/ActionSheet.test.tsx.snap +1736 -0
- package/src/__snapshots__/Button.test.tsx.snap +68 -0
- package/src/__snapshots__/EmojiSelector.test.tsx.snap +1363 -0
- package/src/__snapshots__/InfoTooltipButton.test.tsx.snap +72 -3
- package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +60 -9
- package/src/__snapshots__/Modal.test.tsx.snap +181 -0
- package/src/__snapshots__/Page.test.tsx.snap +48 -2
- package/src/__snapshots__/PhoneNumberField.test.tsx.snap +0 -93
- package/src/__snapshots__/PickerSelect.test.tsx.snap +706 -0
- package/src/__snapshots__/SideDrawer.test.tsx.snap +533 -1399
- package/src/__snapshots__/Signature.test.tsx.snap +0 -3
- package/src/__snapshots__/SplitPage.test.tsx.snap +970 -0
- package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +220 -4
- package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +93 -0
- package/src/bunSetup.ts +204 -121
- package/src/index.tsx +2 -2
- package/src/table/TableHeaderCell.test.tsx +142 -0
- package/src/table/TableRow.test.tsx +33 -0
- package/src/table/__snapshots__/TableRow.test.tsx.snap +403 -0
- package/src/table/tableContext.test.tsx +96 -0
- package/src/test-utils.tsx +1 -1
- package/src/useConsentForms.test.ts +130 -0
- package/src/useSubmitConsent.test.ts +64 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {act, fireEvent} from "@testing-library/react-native";
|
|
2
3
|
|
|
3
4
|
import {RNPickerSelect} from "./PickerSelect";
|
|
4
5
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -41,6 +42,50 @@ describe("PickerSelect", () => {
|
|
|
41
42
|
expect(toJSON()).toMatchSnapshot();
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
it("matches items by itemKey", () => {
|
|
46
|
+
const items = [
|
|
47
|
+
{key: "k1", label: "Option 1", value: "1"},
|
|
48
|
+
{key: "k2", label: "Option 2", value: "2"},
|
|
49
|
+
];
|
|
50
|
+
const {toJSON} = renderWithTheme(
|
|
51
|
+
<RNPickerSelect
|
|
52
|
+
{...defaultProps}
|
|
53
|
+
itemKey="k2"
|
|
54
|
+
items={items}
|
|
55
|
+
placeholder={{label: "Select", value: ""}}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
expect(toJSON()).toMatchSnapshot();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("renders children when provided", () => {
|
|
62
|
+
const {getByTestId} = renderWithTheme(
|
|
63
|
+
<RNPickerSelect {...defaultProps}>
|
|
64
|
+
<>Custom child content</>
|
|
65
|
+
</RNPickerSelect>
|
|
66
|
+
);
|
|
67
|
+
expect(getByTestId).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("renders custom InputAccessoryView", () => {
|
|
71
|
+
const CustomAccessory = () => null;
|
|
72
|
+
const {toJSON} = renderWithTheme(
|
|
73
|
+
<RNPickerSelect {...defaultProps} InputAccessoryView={CustomAccessory} />
|
|
74
|
+
);
|
|
75
|
+
expect(toJSON()).toMatchSnapshot();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("passes textInputProps to TextInput", () => {
|
|
79
|
+
const {toJSON} = renderWithTheme(
|
|
80
|
+
<RNPickerSelect
|
|
81
|
+
{...defaultProps}
|
|
82
|
+
textInputProps={{placeholder: "Custom placeholder"}}
|
|
83
|
+
value="1"
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
expect(toJSON()).toMatchSnapshot();
|
|
87
|
+
});
|
|
88
|
+
|
|
44
89
|
it("calls onValueChange when value changes", () => {
|
|
45
90
|
const mockOnValueChange = mock(() => {});
|
|
46
91
|
renderWithTheme(
|
|
@@ -49,4 +94,189 @@ describe("PickerSelect", () => {
|
|
|
49
94
|
// The component is rendered, onValueChange would be called on user interaction
|
|
50
95
|
expect(mockOnValueChange).toBeDefined();
|
|
51
96
|
});
|
|
97
|
+
|
|
98
|
+
it("does not crash when value does not match any item", () => {
|
|
99
|
+
const {toJSON} = renderWithTheme(<RNPickerSelect {...defaultProps} value="nonexistent" />);
|
|
100
|
+
expect(toJSON()).toBeTruthy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("accepts onOpen, onClose, onDonePress callbacks", () => {
|
|
104
|
+
const onOpen = mock(() => {});
|
|
105
|
+
const onClose = mock(() => {});
|
|
106
|
+
const onDonePress = mock(() => {});
|
|
107
|
+
const {toJSON} = renderWithTheme(
|
|
108
|
+
<RNPickerSelect
|
|
109
|
+
{...defaultProps}
|
|
110
|
+
onClose={onClose}
|
|
111
|
+
onDonePress={onDonePress}
|
|
112
|
+
onOpen={onOpen}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
expect(toJSON()).toBeTruthy();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("accepts onUpArrow and onDownArrow callbacks", () => {
|
|
119
|
+
const onUpArrow = mock(() => {});
|
|
120
|
+
const onDownArrow = mock(() => {});
|
|
121
|
+
const {toJSON} = renderWithTheme(
|
|
122
|
+
<RNPickerSelect {...defaultProps} onDownArrow={onDownArrow} onUpArrow={onUpArrow} />
|
|
123
|
+
);
|
|
124
|
+
expect(toJSON()).toBeTruthy();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("forwards touchableDoneProps and touchableWrapperProps", () => {
|
|
128
|
+
const {toJSON} = renderWithTheme(
|
|
129
|
+
<RNPickerSelect
|
|
130
|
+
{...defaultProps}
|
|
131
|
+
touchableDoneProps={{testID: "custom_done"}}
|
|
132
|
+
touchableWrapperProps={{testID: "custom_wrapper"}}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
expect(toJSON()).toBeTruthy();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("renders without crashing when fixAndroidTouchableBug is true", () => {
|
|
139
|
+
const {toJSON} = renderWithTheme(<RNPickerSelect {...defaultProps} fixAndroidTouchableBug />);
|
|
140
|
+
expect(toJSON()).toBeTruthy();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("renders without crashing when useNativeAndroidPickerStyle is false", () => {
|
|
144
|
+
const {toJSON} = renderWithTheme(
|
|
145
|
+
<RNPickerSelect {...defaultProps} useNativeAndroidPickerStyle={false} />
|
|
146
|
+
);
|
|
147
|
+
expect(toJSON()).toBeTruthy();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("renders with modalProps", () => {
|
|
151
|
+
const {toJSON} = renderWithTheme(
|
|
152
|
+
<RNPickerSelect {...defaultProps} modalProps={{animationType: "fade"}} />
|
|
153
|
+
);
|
|
154
|
+
expect(toJSON()).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("updates selected item when value prop changes", () => {
|
|
158
|
+
const {rerender, toJSON} = renderWithTheme(<RNPickerSelect {...defaultProps} value="1" />);
|
|
159
|
+
rerender(<RNPickerSelect {...defaultProps} value="3" />);
|
|
160
|
+
expect(toJSON()).toBeTruthy();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("interactions on iOS", () => {
|
|
164
|
+
it("fires onOpen when the iOS wrapper is pressed and onClose when Done is pressed", async () => {
|
|
165
|
+
const onOpen = mock(() => {});
|
|
166
|
+
const onClose = mock(() => {});
|
|
167
|
+
const onDonePress = mock(() => {});
|
|
168
|
+
const {getByTestId} = renderWithTheme(
|
|
169
|
+
<RNPickerSelect
|
|
170
|
+
{...defaultProps}
|
|
171
|
+
onClose={onClose}
|
|
172
|
+
onDonePress={onDonePress}
|
|
173
|
+
onOpen={onOpen}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
await act(async () => {
|
|
178
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
179
|
+
});
|
|
180
|
+
expect(onOpen).toHaveBeenCalled();
|
|
181
|
+
|
|
182
|
+
await act(async () => {
|
|
183
|
+
fireEvent.press(getByTestId("done_button"));
|
|
184
|
+
});
|
|
185
|
+
expect(onClose).toHaveBeenCalled();
|
|
186
|
+
expect(onDonePress).toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("invokes up/down arrow callbacks when their touchables are pressed", async () => {
|
|
190
|
+
const onUpArrow = mock(() => {});
|
|
191
|
+
const onDownArrow = mock(() => {});
|
|
192
|
+
const {getByTestId, UNSAFE_getAllByType} = renderWithTheme(
|
|
193
|
+
<RNPickerSelect {...defaultProps} onDownArrow={onDownArrow} onUpArrow={onUpArrow} />
|
|
194
|
+
);
|
|
195
|
+
// Open the modal so the accessory view is rendered.
|
|
196
|
+
await act(async () => {
|
|
197
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
198
|
+
});
|
|
199
|
+
const {TouchableOpacity} = require("react-native");
|
|
200
|
+
interface TouchableTestInstance {
|
|
201
|
+
props: {onPress?: () => void};
|
|
202
|
+
}
|
|
203
|
+
const touchables: TouchableTestInstance[] = UNSAFE_getAllByType(TouchableOpacity).filter(
|
|
204
|
+
(t: TouchableTestInstance) => typeof t.props.onPress === "function"
|
|
205
|
+
);
|
|
206
|
+
// The accessory view renders an up-arrow TouchableOpacity then a down-arrow one.
|
|
207
|
+
expect(touchables.length).toBeGreaterThanOrEqual(2);
|
|
208
|
+
await act(async () => {
|
|
209
|
+
touchables[0].props.onPress?.();
|
|
210
|
+
});
|
|
211
|
+
await act(async () => {
|
|
212
|
+
touchables[1].props.onPress?.();
|
|
213
|
+
});
|
|
214
|
+
expect(onUpArrow).toHaveBeenCalled();
|
|
215
|
+
expect(onDownArrow).toHaveBeenCalled();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("toggles the done button press state when pressed in/out", async () => {
|
|
219
|
+
const {getByTestId} = renderWithTheme(<RNPickerSelect {...defaultProps} />);
|
|
220
|
+
await act(async () => {
|
|
221
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
222
|
+
});
|
|
223
|
+
const doneButton = getByTestId("done_button");
|
|
224
|
+
await act(async () => {
|
|
225
|
+
fireEvent(doneButton, "pressIn");
|
|
226
|
+
});
|
|
227
|
+
await act(async () => {
|
|
228
|
+
fireEvent(doneButton, "pressOut");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("does not open when disabled", async () => {
|
|
233
|
+
const onOpen = mock(() => {});
|
|
234
|
+
const {getByTestId} = renderWithTheme(
|
|
235
|
+
<RNPickerSelect {...defaultProps} disabled onOpen={onOpen} />
|
|
236
|
+
);
|
|
237
|
+
await act(async () => {
|
|
238
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
239
|
+
});
|
|
240
|
+
expect(onOpen).not.toHaveBeenCalled();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("calls onValueChange and updates selected item when the Picker emits a change", async () => {
|
|
244
|
+
const mockOnValueChange = mock(() => {});
|
|
245
|
+
const {getByTestId} = renderWithTheme(
|
|
246
|
+
<RNPickerSelect {...defaultProps} onValueChange={mockOnValueChange} />
|
|
247
|
+
);
|
|
248
|
+
await act(async () => {
|
|
249
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
250
|
+
});
|
|
251
|
+
const picker = getByTestId("ios_picker");
|
|
252
|
+
await act(async () => {
|
|
253
|
+
picker.props.onValueChange?.("2", 1);
|
|
254
|
+
});
|
|
255
|
+
expect(mockOnValueChange).toHaveBeenCalledWith("2", 1);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("updates orientation state when the iOS modal rotates", async () => {
|
|
259
|
+
const {getByTestId, toJSON} = renderWithTheme(<RNPickerSelect {...defaultProps} />);
|
|
260
|
+
await act(async () => {
|
|
261
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
262
|
+
});
|
|
263
|
+
const modal = getByTestId("ios_modal");
|
|
264
|
+
await act(async () => {
|
|
265
|
+
modal.props.onOrientationChange?.({nativeEvent: {orientation: "landscape"}});
|
|
266
|
+
});
|
|
267
|
+
expect(toJSON()).toBeTruthy();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("closes the modal when the top overlay is pressed", async () => {
|
|
271
|
+
const onClose = mock(() => {});
|
|
272
|
+
const {getByTestId} = renderWithTheme(<RNPickerSelect {...defaultProps} onClose={onClose} />);
|
|
273
|
+
await act(async () => {
|
|
274
|
+
fireEvent.press(getByTestId("ios_touchable_wrapper"));
|
|
275
|
+
});
|
|
276
|
+
await act(async () => {
|
|
277
|
+
fireEvent.press(getByTestId("ios_modal_top"));
|
|
278
|
+
});
|
|
279
|
+
expect(onClose).toHaveBeenCalled();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
52
282
|
});
|
|
@@ -81,4 +81,42 @@ describe("SegmentedControl", () => {
|
|
|
81
81
|
expect(getByText("Tab 3")).toBeTruthy();
|
|
82
82
|
expect(toJSON()).toMatchSnapshot();
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
it("invokes handleNext when next scroll button is pressed", () => {
|
|
86
|
+
const manyItems = ["Tab 1", "Tab 2", "Tab 3", "Tab 4", "Tab 5", "Tab 6"];
|
|
87
|
+
const {UNSAFE_getAllByProps, queryByText} = renderWithTheme(
|
|
88
|
+
<SegmentedControl items={manyItems} maxItems={3} selectedIndex={0} />
|
|
89
|
+
);
|
|
90
|
+
// Find the right chevron (next button) by its icon prop
|
|
91
|
+
const nextIcons = UNSAFE_getAllByProps({iconName: "chevron-right"});
|
|
92
|
+
expect(nextIcons.length).toBeGreaterThan(0);
|
|
93
|
+
const pressable = nextIcons[0].parent;
|
|
94
|
+
expect(pressable).toBeTruthy();
|
|
95
|
+
if (pressable) {
|
|
96
|
+
fireEvent.press(pressable);
|
|
97
|
+
}
|
|
98
|
+
// After pressing next, should display items starting at Tab 4
|
|
99
|
+
expect(queryByText("Tab 4")).toBeTruthy();
|
|
100
|
+
expect(queryByText("Tab 1")).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("invokes handlePrevious when previous scroll button is pressed", () => {
|
|
104
|
+
const manyItems = ["Tab 1", "Tab 2", "Tab 3", "Tab 4", "Tab 5", "Tab 6"];
|
|
105
|
+
const {UNSAFE_getAllByProps, queryByText} = renderWithTheme(
|
|
106
|
+
<SegmentedControl items={manyItems} maxItems={3} selectedIndex={0} />
|
|
107
|
+
);
|
|
108
|
+
const nextIcons = UNSAFE_getAllByProps({iconName: "chevron-right"});
|
|
109
|
+
const nextPressable = nextIcons[0].parent;
|
|
110
|
+
if (nextPressable) {
|
|
111
|
+
fireEvent.press(nextPressable);
|
|
112
|
+
}
|
|
113
|
+
// Now scrolled forward; press previous to go back
|
|
114
|
+
const prevIcons = UNSAFE_getAllByProps({iconName: "chevron-left"});
|
|
115
|
+
const prevPressable = prevIcons[0].parent;
|
|
116
|
+
if (prevPressable) {
|
|
117
|
+
fireEvent.press(prevPressable);
|
|
118
|
+
}
|
|
119
|
+
// Should be back to Tab 1
|
|
120
|
+
expect(queryByText("Tab 1")).toBeTruthy();
|
|
121
|
+
});
|
|
84
122
|
});
|
package/src/SelectBadge.test.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {describe, expect, it} from "bun:test";
|
|
1
|
+
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {act, fireEvent} from "@testing-library/react-native";
|
|
2
3
|
|
|
3
4
|
import {SelectBadge} from "./SelectBadge";
|
|
4
5
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -100,4 +101,54 @@ describe("SelectBadge", () => {
|
|
|
100
101
|
);
|
|
101
102
|
expect(toJSON()).toMatchSnapshot();
|
|
102
103
|
});
|
|
104
|
+
|
|
105
|
+
it("toggles picker visibility when badge is pressed", () => {
|
|
106
|
+
const {getByLabelText} = renderWithTheme(
|
|
107
|
+
<SelectBadge onChange={() => {}} options={defaultOptions} value="a" />
|
|
108
|
+
);
|
|
109
|
+
const touchable = getByLabelText("Open select badge options");
|
|
110
|
+
expect(() => fireEvent.press(touchable)).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("invokes onChange when iOS picker value is changed and Save is pressed", () => {
|
|
114
|
+
const handleChange = mock((_val: string) => {});
|
|
115
|
+
const {getByLabelText, UNSAFE_getAllByProps} = renderWithTheme(
|
|
116
|
+
<SelectBadge onChange={handleChange} options={defaultOptions} value="a" />
|
|
117
|
+
);
|
|
118
|
+
// Open the iOS picker modal
|
|
119
|
+
act(() => {
|
|
120
|
+
fireEvent.press(getByLabelText("Open select badge options"));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Change the picker's value via onValueChange
|
|
124
|
+
const pickers = UNSAFE_getAllByProps({selectedValue: "a"});
|
|
125
|
+
const picker = pickers.find((p) => typeof p.props.onValueChange === "function");
|
|
126
|
+
act(() => {
|
|
127
|
+
if (picker) {
|
|
128
|
+
picker.props.onValueChange("b");
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Tap Save to commit the change
|
|
133
|
+
act(() => {
|
|
134
|
+
fireEvent.press(getByLabelText("Save selected value"));
|
|
135
|
+
});
|
|
136
|
+
expect(handleChange).toHaveBeenCalledWith("b");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("does not call onChange when iOS picker is dismissed", () => {
|
|
140
|
+
const handleChange = mock((_val: string) => {});
|
|
141
|
+
const {getByLabelText} = renderWithTheme(
|
|
142
|
+
<SelectBadge onChange={handleChange} options={defaultOptions} value="a" />
|
|
143
|
+
);
|
|
144
|
+
// Open the iOS picker modal
|
|
145
|
+
act(() => {
|
|
146
|
+
fireEvent.press(getByLabelText("Open select badge options"));
|
|
147
|
+
});
|
|
148
|
+
// Dismiss the picker by pressing the backdrop
|
|
149
|
+
act(() => {
|
|
150
|
+
fireEvent.press(getByLabelText("Dismiss picker modal"));
|
|
151
|
+
});
|
|
152
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
103
154
|
});
|
package/src/SideDrawer.test.tsx
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import type {ReactNode} from "react";
|
|
3
|
+
import {Pressable, Text as RNText, View} from "react-native";
|
|
4
|
+
|
|
5
|
+
// Capture the props passed to Drawer so we can exercise the render callbacks.
|
|
6
|
+
interface CapturedDrawerProps {
|
|
7
|
+
onOpen?: () => void;
|
|
8
|
+
onClose?: () => void;
|
|
9
|
+
renderDrawerContent?: () => ReactNode;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let lastDrawerProps: CapturedDrawerProps | null = null;
|
|
14
|
+
mock.module("react-native-drawer-layout", () => ({
|
|
15
|
+
Drawer: (props: CapturedDrawerProps) => {
|
|
16
|
+
lastDrawerProps = props;
|
|
17
|
+
return (
|
|
18
|
+
<View testID="mock-drawer">
|
|
19
|
+
{props.renderDrawerContent ? props.renderDrawerContent() : null}
|
|
20
|
+
<Pressable onPress={props.onOpen} testID="mock-drawer-open">
|
|
21
|
+
<RNText>open</RNText>
|
|
22
|
+
</Pressable>
|
|
23
|
+
<Pressable onPress={props.onClose} testID="mock-drawer-close">
|
|
24
|
+
<RNText>close</RNText>
|
|
25
|
+
</Pressable>
|
|
26
|
+
{props.children}
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
2
31
|
|
|
3
32
|
import {SideDrawer} from "./SideDrawer";
|
|
4
33
|
import {Text} from "./Text";
|
|
@@ -96,4 +125,44 @@ describe("SideDrawer", () => {
|
|
|
96
125
|
);
|
|
97
126
|
expect(toJSON()).toMatchSnapshot();
|
|
98
127
|
});
|
|
128
|
+
|
|
129
|
+
it("invokes onOpen and onClose when Drawer triggers the callbacks", () => {
|
|
130
|
+
const handleOpen = mock(() => {});
|
|
131
|
+
const handleClose = mock(() => {});
|
|
132
|
+
renderWithTheme(
|
|
133
|
+
<SideDrawer
|
|
134
|
+
isOpen
|
|
135
|
+
onClose={handleClose}
|
|
136
|
+
onOpen={handleOpen}
|
|
137
|
+
renderContent={() => <Text>Drawer body</Text>}
|
|
138
|
+
>
|
|
139
|
+
<Text>Content</Text>
|
|
140
|
+
</SideDrawer>
|
|
141
|
+
);
|
|
142
|
+
lastDrawerProps?.onOpen?.();
|
|
143
|
+
lastDrawerProps?.onClose?.();
|
|
144
|
+
expect(handleOpen).toHaveBeenCalledTimes(1);
|
|
145
|
+
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("renders the drawer content via the render callback", () => {
|
|
149
|
+
const {getByText} = renderWithTheme(
|
|
150
|
+
<SideDrawer isOpen renderContent={() => <Text>Rendered drawer body</Text>}>
|
|
151
|
+
<Text>Main</Text>
|
|
152
|
+
</SideDrawer>
|
|
153
|
+
);
|
|
154
|
+
expect(getByText("Rendered drawer body")).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("exercises the default no-op callbacks when onOpen/onClose are omitted", () => {
|
|
158
|
+
renderWithTheme(
|
|
159
|
+
<SideDrawer isOpen renderContent={() => <Text>Default callbacks</Text>}>
|
|
160
|
+
<Text>Content</Text>
|
|
161
|
+
</SideDrawer>
|
|
162
|
+
);
|
|
163
|
+
expect(() => {
|
|
164
|
+
lastDrawerProps?.onOpen?.();
|
|
165
|
+
lastDrawerProps?.onClose?.();
|
|
166
|
+
}).not.toThrow();
|
|
167
|
+
});
|
|
99
168
|
});
|
package/src/Signature.test.tsx
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {fireEvent} from "@testing-library/react-native";
|
|
3
|
+
import {forwardRef, useImperativeHandle} from "react";
|
|
3
4
|
import {View} from "react-native";
|
|
4
5
|
|
|
5
6
|
import {Signature} from "./Signature";
|
|
6
7
|
import {renderWithTheme} from "./test-utils";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
const clearMock = mock(() => {});
|
|
10
|
+
let toDataURLReturn: string = "";
|
|
11
|
+
const toDataURLMock = mock(() => toDataURLReturn);
|
|
12
|
+
let lastOnEnd: (() => void) | undefined;
|
|
13
|
+
|
|
14
|
+
// Mock react-signature-canvas so we can exercise the ref methods and onEnd callback.
|
|
9
15
|
mock.module("react-signature-canvas", () => ({
|
|
10
|
-
default: forwardRef(({backgroundColor}: any, ref) =>
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
default: forwardRef(({backgroundColor, onEnd}: any, ref) => {
|
|
17
|
+
lastOnEnd = onEnd;
|
|
18
|
+
useImperativeHandle(ref, () => ({
|
|
19
|
+
clear: clearMock,
|
|
20
|
+
toDataURL: toDataURLMock,
|
|
21
|
+
}));
|
|
22
|
+
return <View style={{backgroundColor}} testID="signature-canvas" />;
|
|
23
|
+
}),
|
|
13
24
|
}));
|
|
14
25
|
|
|
15
26
|
describe("Signature", () => {
|
|
@@ -29,4 +40,30 @@ describe("Signature", () => {
|
|
|
29
40
|
const {getByText} = renderWithTheme(<Signature onChange={mockOnChange} />);
|
|
30
41
|
expect(getByText("Clear")).toBeTruthy();
|
|
31
42
|
});
|
|
43
|
+
|
|
44
|
+
it("calls clear on the signature canvas when Clear is pressed", () => {
|
|
45
|
+
clearMock.mockClear();
|
|
46
|
+
const mockOnChange = mock(() => {});
|
|
47
|
+
const {getByText} = renderWithTheme(<Signature onChange={mockOnChange} />);
|
|
48
|
+
fireEvent.press(getByText("Clear"));
|
|
49
|
+
expect(clearMock).toHaveBeenCalledTimes(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("calls onChange with the data URL when a stroke ends", () => {
|
|
53
|
+
toDataURLReturn = "data:image/png;base64,abc";
|
|
54
|
+
const mockOnChange = mock(() => {});
|
|
55
|
+
renderWithTheme(<Signature onChange={mockOnChange} />);
|
|
56
|
+
expect(lastOnEnd).toBeDefined();
|
|
57
|
+
lastOnEnd?.();
|
|
58
|
+
expect(mockOnChange).toHaveBeenCalledWith("data:image/png;base64,abc");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not call onChange when toDataURL returns an empty value", () => {
|
|
62
|
+
toDataURLReturn = "";
|
|
63
|
+
const mockOnChange = mock(() => {});
|
|
64
|
+
renderWithTheme(<Signature onChange={mockOnChange} />);
|
|
65
|
+
expect(lastOnEnd).toBeDefined();
|
|
66
|
+
lastOnEnd?.();
|
|
67
|
+
expect(mockOnChange).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
32
69
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
2
|
import {forwardRef} from "react";
|
|
3
3
|
import {View} from "react-native";
|
|
4
|
+
import type {ReactTestInstance} from "react-test-renderer";
|
|
4
5
|
|
|
5
6
|
import {SignatureField} from "./SignatureField";
|
|
6
7
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -57,4 +58,38 @@ describe("SignatureField", () => {
|
|
|
57
58
|
);
|
|
58
59
|
expect(getByText("Signature is required")).toBeTruthy();
|
|
59
60
|
});
|
|
61
|
+
|
|
62
|
+
it("invokes onStart and onEnd callbacks via the Signature component", () => {
|
|
63
|
+
const onStart = mock(() => {});
|
|
64
|
+
const onEnd = mock(() => {});
|
|
65
|
+
const onChange = mock((_v: string) => {});
|
|
66
|
+
|
|
67
|
+
const {UNSAFE_getAllByProps} = renderWithTheme(
|
|
68
|
+
<SignatureField onChange={onChange} onEnd={onEnd} onStart={onStart} />
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Find the inner Signature wrapper by looking for elements with onStart
|
|
72
|
+
const elementsWithOnStart = UNSAFE_getAllByProps({}).filter(
|
|
73
|
+
(el: ReactTestInstance) => typeof el.props?.onStart === "function"
|
|
74
|
+
);
|
|
75
|
+
expect(elementsWithOnStart.length).toBeGreaterThan(0);
|
|
76
|
+
const signatureEl = elementsWithOnStart[0];
|
|
77
|
+
signatureEl.props.onStart();
|
|
78
|
+
signatureEl.props.onEnd();
|
|
79
|
+
expect(onStart).toHaveBeenCalled();
|
|
80
|
+
expect(onEnd).toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("does not crash when onStart/onEnd are not provided", () => {
|
|
84
|
+
const onChange = mock((_v: string) => {});
|
|
85
|
+
const {UNSAFE_getAllByProps} = renderWithTheme(<SignatureField onChange={onChange} />);
|
|
86
|
+
|
|
87
|
+
const elementsWithOnStart = UNSAFE_getAllByProps({}).filter(
|
|
88
|
+
(el: ReactTestInstance) => typeof el.props?.onStart === "function"
|
|
89
|
+
);
|
|
90
|
+
expect(elementsWithOnStart.length).toBeGreaterThan(0);
|
|
91
|
+
const signatureEl = elementsWithOnStart[0];
|
|
92
|
+
expect(() => signatureEl.props.onStart()).not.toThrow();
|
|
93
|
+
expect(() => signatureEl.props.onEnd()).not.toThrow();
|
|
94
|
+
});
|
|
60
95
|
});
|
package/src/Slider.test.tsx
CHANGED
|
@@ -244,4 +244,63 @@ describe("Slider", () => {
|
|
|
244
244
|
expect(toJSON()).toMatchSnapshot();
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
describe("advanced branches", () => {
|
|
249
|
+
it("renders icon-based value mapping when useIcons is true", () => {
|
|
250
|
+
const mockOnChange = mock(() => {});
|
|
251
|
+
const {toJSON} = renderWithTheme(
|
|
252
|
+
<Slider
|
|
253
|
+
maximumValue={2}
|
|
254
|
+
minimumValue={0}
|
|
255
|
+
onChange={mockOnChange}
|
|
256
|
+
showSelection
|
|
257
|
+
step={1}
|
|
258
|
+
useIcons
|
|
259
|
+
value={1}
|
|
260
|
+
valueMapping={[
|
|
261
|
+
{label: "face-frown", size: "md", value: 0},
|
|
262
|
+
{label: "face-meh", size: "lg", value: 1},
|
|
263
|
+
{label: "face-smile", size: "xl", value: 2},
|
|
264
|
+
]}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
expect(toJSON()).toBeTruthy();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("renders custom labels alongside min and max labels", () => {
|
|
271
|
+
const mockOnChange = mock(() => {});
|
|
272
|
+
const {getByText} = renderWithTheme(
|
|
273
|
+
<Slider
|
|
274
|
+
labels={{
|
|
275
|
+
custom: [{label: "Middle", value: 50}],
|
|
276
|
+
max: "High",
|
|
277
|
+
min: "Low",
|
|
278
|
+
}}
|
|
279
|
+
maximumValue={100}
|
|
280
|
+
minimumValue={0}
|
|
281
|
+
onChange={mockOnChange}
|
|
282
|
+
value={50}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
285
|
+
expect(getByText("Low")).toBeTruthy();
|
|
286
|
+
expect(getByText("Middle")).toBeTruthy();
|
|
287
|
+
expect(getByText("High")).toBeTruthy();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("falls back to plain numeric formatter when valueMapping is empty", () => {
|
|
291
|
+
const mockOnChange = mock(() => {});
|
|
292
|
+
const {getByText} = renderWithTheme(
|
|
293
|
+
<Slider
|
|
294
|
+
maximumValue={10}
|
|
295
|
+
minimumValue={0}
|
|
296
|
+
onChange={mockOnChange}
|
|
297
|
+
showSelection
|
|
298
|
+
step={1}
|
|
299
|
+
value={7}
|
|
300
|
+
valueMapping={[]}
|
|
301
|
+
/>
|
|
302
|
+
);
|
|
303
|
+
expect(getByText("7")).toBeTruthy();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
247
306
|
});
|
package/src/Spinner.test.tsx
CHANGED
|
@@ -71,4 +71,10 @@ describe("Spinner", () => {
|
|
|
71
71
|
|
|
72
72
|
expect(toJSON()).toMatchSnapshot();
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it("clears the delay timer on unmount", () => {
|
|
76
|
+
const {unmount, toJSON} = renderWithTheme(<Spinner />);
|
|
77
|
+
expect(toJSON()).toBeNull();
|
|
78
|
+
expect(() => unmount()).not.toThrow();
|
|
79
|
+
});
|
|
74
80
|
});
|