@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.
- package/dist/Banner.js +2 -2
- package/dist/Banner.js.map +1 -1
- package/dist/Common.d.ts +6 -10
- package/dist/Common.js.map +1 -1
- package/dist/ConsentHistory.d.ts +2 -1
- package/dist/DataTable.js +4 -2
- package/dist/DataTable.js.map +1 -1
- package/dist/DateUtilities.js +16 -7
- package/dist/DateUtilities.js.map +1 -1
- package/dist/DraggableList.d.ts +5 -4
- package/dist/DraggableList.js.map +1 -1
- package/dist/Icon.js +0 -3
- package/dist/Icon.js.map +1 -1
- package/dist/PasswordField.d.ts +1 -1
- package/dist/PasswordField.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/Common.ts +11 -9
- package/src/ConsentFormScreen.test.tsx +167 -0
- package/src/ConsentHistory.tsx +1 -1
- package/src/ConsentNavigator.test.tsx +210 -4
- package/src/DataTable.tsx +15 -15
- package/src/DateUtilities.test.ts +34 -16
- package/src/DateUtilities.tsx +24 -13
- package/src/DecimalRangeActionSheet.test.tsx +53 -2
- package/src/DraggableList.tsx +5 -5
- package/src/EmailField.test.tsx +81 -0
- package/src/EmojiSelector.test.tsx +262 -1
- package/src/ErrorBoundary.test.tsx +52 -1
- package/src/HeightActionSheet.test.tsx +57 -2
- package/src/Icon.tsx +0 -3
- 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/OpenAPIContext.test.tsx +184 -3
- package/src/Page.test.tsx +162 -1
- package/src/Pagination.test.tsx +16 -0
- package/src/PasswordField.tsx +1 -1
- 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
package/src/SplitPage.test.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
-
import {
|
|
1
|
+
import {afterAll, beforeAll, describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {act} from "@testing-library/react-native";
|
|
3
|
+
import {Dimensions, View} from "react-native";
|
|
3
4
|
|
|
4
5
|
import {SplitPage} from "./SplitPage";
|
|
5
6
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -79,4 +80,229 @@ describe("SplitPage", () => {
|
|
|
79
80
|
);
|
|
80
81
|
expect(toJSON()).toMatchSnapshot();
|
|
81
82
|
});
|
|
83
|
+
|
|
84
|
+
it("returns null when no children and no renderContent", () => {
|
|
85
|
+
const {toJSON} = renderWithTheme(
|
|
86
|
+
<SplitPage listViewData={[]} renderListViewItem={() => null} />
|
|
87
|
+
);
|
|
88
|
+
expect(toJSON()).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns null when tabs count does not match children count", () => {
|
|
92
|
+
const {toJSON} = renderWithTheme(
|
|
93
|
+
<SplitPage {...defaultProps} tabs={["Tab 1"]}>
|
|
94
|
+
<View testID="child-1" />
|
|
95
|
+
<View testID="child-2" />
|
|
96
|
+
<View testID="child-3" />
|
|
97
|
+
</SplitPage>
|
|
98
|
+
);
|
|
99
|
+
expect(toJSON()).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("renders with list view header", () => {
|
|
103
|
+
const {toJSON} = renderWithTheme(
|
|
104
|
+
<SplitPage
|
|
105
|
+
{...defaultProps}
|
|
106
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
107
|
+
renderListViewHeader={() => <View testID="list-header" />}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
expect(toJSON()).toMatchSnapshot();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("renders with custom list view width and max width", () => {
|
|
114
|
+
const {toJSON} = renderWithTheme(
|
|
115
|
+
<SplitPage
|
|
116
|
+
{...defaultProps}
|
|
117
|
+
listViewMaxWidth={500}
|
|
118
|
+
listViewWidth={400}
|
|
119
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
expect(toJSON()).toMatchSnapshot();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("renders with listViewExtraData", () => {
|
|
126
|
+
const {toJSON} = renderWithTheme(
|
|
127
|
+
<SplitPage
|
|
128
|
+
{...defaultProps}
|
|
129
|
+
listViewExtraData={{counter: 1}}
|
|
130
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
expect(toJSON()).toMatchSnapshot();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders with keyboard offset", () => {
|
|
137
|
+
const {toJSON} = renderWithTheme(
|
|
138
|
+
<SplitPage
|
|
139
|
+
{...defaultProps}
|
|
140
|
+
keyboardOffset={100}
|
|
141
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
expect(toJSON()).toMatchSnapshot();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("filters out null children", () => {
|
|
148
|
+
const {toJSON} = renderWithTheme(
|
|
149
|
+
<SplitPage {...defaultProps}>
|
|
150
|
+
<View testID="child-1" />
|
|
151
|
+
{null}
|
|
152
|
+
<View testID="child-2" />
|
|
153
|
+
</SplitPage>
|
|
154
|
+
);
|
|
155
|
+
expect(toJSON()).toMatchSnapshot();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("renders with showItemList true to reset selection", () => {
|
|
159
|
+
const onSelectionChange = mock(() => {});
|
|
160
|
+
const {toJSON} = renderWithTheme(
|
|
161
|
+
<SplitPage
|
|
162
|
+
{...defaultProps}
|
|
163
|
+
onSelectionChange={onSelectionChange}
|
|
164
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
165
|
+
showItemList
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
expect(toJSON()).toMatchSnapshot();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("renders with bottomNavBarHeight", () => {
|
|
172
|
+
const {toJSON} = renderWithTheme(
|
|
173
|
+
<SplitPage {...defaultProps} bottomNavBarHeight={60}>
|
|
174
|
+
<View testID="child-1" />
|
|
175
|
+
</SplitPage>
|
|
176
|
+
);
|
|
177
|
+
expect(toJSON()).toMatchSnapshot();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("desktop viewport (mediaQueryLargerThan('sm') true)", () => {
|
|
181
|
+
const desktopImpl = () => ({fontScale: 1, height: 1000, scale: 2, width: 1400}) as any;
|
|
182
|
+
const mobileImpl = () => ({fontScale: 1, height: 812, scale: 2, width: 375}) as any;
|
|
183
|
+
let originalGet: typeof Dimensions.get;
|
|
184
|
+
beforeAll(() => {
|
|
185
|
+
originalGet = Dimensions.get;
|
|
186
|
+
if (typeof (Dimensions.get as any).mockImplementation === "function") {
|
|
187
|
+
(Dimensions.get as any).mockImplementation(desktopImpl);
|
|
188
|
+
} else {
|
|
189
|
+
(Dimensions.get as any) = desktopImpl;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
afterAll(() => {
|
|
193
|
+
if (typeof (Dimensions.get as any).mockImplementation === "function") {
|
|
194
|
+
(Dimensions.get as any).mockImplementation(mobileImpl);
|
|
195
|
+
} else {
|
|
196
|
+
(Dimensions.get as any) = originalGet;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("verifies Dimensions mock is overridden", () => {
|
|
201
|
+
expect(Dimensions.get("window").width).toBe(1400);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("renders renderList/renderContent on desktop", () => {
|
|
205
|
+
const {toJSON} = renderWithTheme(
|
|
206
|
+
<SplitPage
|
|
207
|
+
{...defaultProps}
|
|
208
|
+
renderContent={(selectedId) => <View testID={`content-${selectedId}`} />}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
expect(toJSON()).toBeTruthy();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("renders renderList/renderChildrenContent on desktop with 2 children", () => {
|
|
215
|
+
const {toJSON} = renderWithTheme(
|
|
216
|
+
<SplitPage {...defaultProps}>
|
|
217
|
+
<View testID="child-1" />
|
|
218
|
+
<View testID="child-2" />
|
|
219
|
+
</SplitPage>
|
|
220
|
+
);
|
|
221
|
+
expect(toJSON()).toBeTruthy();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("renders renderChildrenContent with >2 children and tabs on desktop", () => {
|
|
225
|
+
const {toJSON} = renderWithTheme(
|
|
226
|
+
<SplitPage {...defaultProps} tabs={["A", "B", "C"]}>
|
|
227
|
+
<View testID="child-1" />
|
|
228
|
+
<View testID="child-2" />
|
|
229
|
+
<View testID="child-3" />
|
|
230
|
+
</SplitPage>
|
|
231
|
+
);
|
|
232
|
+
expect(toJSON()).toBeTruthy();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("renders with listViewWidth/listViewMaxWidth applied", () => {
|
|
236
|
+
const {toJSON} = renderWithTheme(
|
|
237
|
+
<SplitPage
|
|
238
|
+
{...defaultProps}
|
|
239
|
+
listViewMaxWidth={400}
|
|
240
|
+
listViewWidth={350}
|
|
241
|
+
renderContent={(id) => <View testID={`content-${id}`} />}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
expect(toJSON()).toBeTruthy();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("item selection callbacks", () => {
|
|
249
|
+
it("onItemSelect runs onSelectionChange when item clicked via Box press", async () => {
|
|
250
|
+
const {fireEvent} = await import("@testing-library/react-native");
|
|
251
|
+
const onSelectionChange = mock(async (_arg: unknown) => {});
|
|
252
|
+
const {getAllByLabelText} = renderWithTheme(
|
|
253
|
+
<SplitPage
|
|
254
|
+
{...defaultProps}
|
|
255
|
+
onSelectionChange={onSelectionChange}
|
|
256
|
+
renderContent={(id) => <View testID={`content-${id}`} />}
|
|
257
|
+
/>
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const boxes = getAllByLabelText("Select");
|
|
261
|
+
expect(boxes.length).toBeGreaterThan(0);
|
|
262
|
+
await act(async () => {
|
|
263
|
+
fireEvent.press(boxes[0]);
|
|
264
|
+
});
|
|
265
|
+
expect(onSelectionChange).toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("selecting an item shows mobile children content when no renderContent", async () => {
|
|
269
|
+
const {fireEvent} = await import("@testing-library/react-native");
|
|
270
|
+
const {getAllByLabelText, queryByTestId} = renderWithTheme(
|
|
271
|
+
<SplitPage {...defaultProps}>
|
|
272
|
+
<View testID="child-1" />
|
|
273
|
+
<View testID="child-2" />
|
|
274
|
+
</SplitPage>
|
|
275
|
+
);
|
|
276
|
+
const boxes = getAllByLabelText("Select");
|
|
277
|
+
await act(async () => {
|
|
278
|
+
fireEvent.press(boxes[0]);
|
|
279
|
+
});
|
|
280
|
+
expect(queryByTestId("swiper-flatlist")).toBeTruthy();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("selection deselect when showItemList becomes true", async () => {
|
|
284
|
+
const onSelectionChange = mock(async () => {});
|
|
285
|
+
const {rerender} = renderWithTheme(
|
|
286
|
+
<SplitPage
|
|
287
|
+
{...defaultProps}
|
|
288
|
+
onSelectionChange={onSelectionChange}
|
|
289
|
+
renderContent={(id) => <View testID={`content-${id}`} />}
|
|
290
|
+
/>
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
await act(async () => {
|
|
294
|
+
rerender(
|
|
295
|
+
<SplitPage
|
|
296
|
+
{...defaultProps}
|
|
297
|
+
onSelectionChange={onSelectionChange}
|
|
298
|
+
renderContent={(id) => <View testID={`content-${id}`} />}
|
|
299
|
+
showItemList
|
|
300
|
+
/>
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// showItemList=true triggers onItemDeselect -> onSelectionChange(undefined)
|
|
305
|
+
expect(onSelectionChange).toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
82
308
|
});
|
package/src/TapToEdit.test.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
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";
|
|
3
|
+
import {Linking} from "react-native";
|
|
2
4
|
|
|
3
5
|
import {formatAddress, TapToEdit} from "./TapToEdit";
|
|
4
6
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -90,6 +92,174 @@ describe("TapToEdit", () => {
|
|
|
90
92
|
);
|
|
91
93
|
expect(toJSON()).toMatchSnapshot();
|
|
92
94
|
});
|
|
95
|
+
|
|
96
|
+
it("renders multiselect type as comma-joined string", () => {
|
|
97
|
+
const {getByText} = renderWithTheme(
|
|
98
|
+
<TapToEdit editable={false} title="Tags" type="multiselect" value={["a", "b", "c"]} />
|
|
99
|
+
);
|
|
100
|
+
expect(getByText("a, b, c")).toBeTruthy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("renders url type showing hostname for a valid URL", () => {
|
|
104
|
+
const {getByText} = renderWithTheme(
|
|
105
|
+
<TapToEdit editable={false} title="Website" type="url" value="https://example.com/foo" />
|
|
106
|
+
);
|
|
107
|
+
expect(getByText("example.com")).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("renders url type falling back to raw value for invalid URL", () => {
|
|
111
|
+
const {getByText} = renderWithTheme(
|
|
112
|
+
<TapToEdit editable={false} title="Website" type="url" value="not-a-url" />
|
|
113
|
+
);
|
|
114
|
+
expect(getByText("not-a-url")).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders url type with empty value without logging error", () => {
|
|
118
|
+
const {toJSON} = renderWithTheme(
|
|
119
|
+
<TapToEdit editable={false} title="Website" type="url" value="" />
|
|
120
|
+
);
|
|
121
|
+
expect(toJSON()).toBeTruthy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("renders address type using formatAddress", () => {
|
|
125
|
+
const {getByText} = renderWithTheme(
|
|
126
|
+
<TapToEdit
|
|
127
|
+
editable={false}
|
|
128
|
+
title="Home"
|
|
129
|
+
type="address"
|
|
130
|
+
value={{
|
|
131
|
+
address1: "123 Main St",
|
|
132
|
+
city: "Boston",
|
|
133
|
+
state: "MA",
|
|
134
|
+
zipcode: "02101",
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
expect(getByText(/123 Main St/)).toBeTruthy();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("invokes Linking.openURL for url type when clicked", async () => {
|
|
142
|
+
const originalOpen = Linking.openURL;
|
|
143
|
+
const openMock = mock(() => Promise.resolve(true));
|
|
144
|
+
(Linking as any).openURL = openMock;
|
|
145
|
+
|
|
146
|
+
const {getByLabelText} = renderWithTheme(
|
|
147
|
+
<TapToEdit editable={false} title="Site" type="url" value="https://example.com" />
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await act(async () => {
|
|
151
|
+
fireEvent.press(getByLabelText("Link"));
|
|
152
|
+
});
|
|
153
|
+
expect(openMock).toHaveBeenCalled();
|
|
154
|
+
|
|
155
|
+
(Linking as any).openURL = originalOpen;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("invokes Linking.openURL with google maps for address type when clicked", async () => {
|
|
159
|
+
const originalOpen = Linking.openURL;
|
|
160
|
+
const openMock = mock(() => Promise.resolve(true));
|
|
161
|
+
(Linking as any).openURL = openMock;
|
|
162
|
+
|
|
163
|
+
const {getByLabelText} = renderWithTheme(
|
|
164
|
+
<TapToEdit
|
|
165
|
+
editable={false}
|
|
166
|
+
title="Home"
|
|
167
|
+
type="address"
|
|
168
|
+
value={{address1: "1 Market St", city: "SF", state: "CA", zipcode: "94105"}}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
await act(async () => {
|
|
173
|
+
fireEvent.press(getByLabelText("Link"));
|
|
174
|
+
});
|
|
175
|
+
expect(openMock).toHaveBeenCalled();
|
|
176
|
+
const arg = openMock.mock.calls[0][0];
|
|
177
|
+
expect(arg).toContain("google.com/maps");
|
|
178
|
+
|
|
179
|
+
(Linking as any).openURL = originalOpen;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("throws when editable is true and setValue is not provided", () => {
|
|
183
|
+
expect(() =>
|
|
184
|
+
renderWithTheme(<TapToEdit editable title="Required Save" value="foo" />)
|
|
185
|
+
).toThrow();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("enters editing mode when Edit button is pressed", async () => {
|
|
189
|
+
const setValue = mock(() => {});
|
|
190
|
+
const {getByLabelText, queryByText} = renderWithTheme(
|
|
191
|
+
<TapToEdit setValue={setValue} title="Name" value="Jane" />
|
|
192
|
+
);
|
|
193
|
+
await act(async () => {
|
|
194
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
195
|
+
});
|
|
196
|
+
expect(queryByText("Cancel")).toBeTruthy();
|
|
197
|
+
expect(queryByText("Save")).toBeTruthy();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("calls setValue with initial value and exits editing on Cancel", async () => {
|
|
201
|
+
const setValue = mock(() => {});
|
|
202
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
203
|
+
<TapToEdit setValue={setValue} title="Name" value="Jane" />
|
|
204
|
+
);
|
|
205
|
+
await act(async () => {
|
|
206
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
207
|
+
});
|
|
208
|
+
await act(async () => {
|
|
209
|
+
fireEvent.press(getByText("Cancel"));
|
|
210
|
+
});
|
|
211
|
+
expect(setValue).toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("clears value when Clear button is pressed", async () => {
|
|
215
|
+
const setValue = mock(() => {});
|
|
216
|
+
const onSave = mock(() => Promise.resolve());
|
|
217
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
218
|
+
<TapToEdit onSave={onSave} setValue={setValue} showClearButton title="Name" value="Jane" />
|
|
219
|
+
);
|
|
220
|
+
await act(async () => {
|
|
221
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
222
|
+
});
|
|
223
|
+
await act(async () => {
|
|
224
|
+
fireEvent.press(getByText("Clear"));
|
|
225
|
+
});
|
|
226
|
+
expect(setValue).toHaveBeenCalledWith("");
|
|
227
|
+
expect(onSave).toHaveBeenCalledWith("");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("calls onSave when Save is pressed", async () => {
|
|
231
|
+
const setValue = mock(() => {});
|
|
232
|
+
const onSave = mock(() => Promise.resolve());
|
|
233
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
234
|
+
<TapToEdit onSave={onSave} setValue={setValue} title="Name" value="Jane" />
|
|
235
|
+
);
|
|
236
|
+
await act(async () => {
|
|
237
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
238
|
+
});
|
|
239
|
+
await act(async () => {
|
|
240
|
+
fireEvent.press(getByText("Save"));
|
|
241
|
+
});
|
|
242
|
+
expect(onSave).toHaveBeenCalledWith("Jane");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("logs error when saving without onSave", async () => {
|
|
246
|
+
const setValue = mock(() => {});
|
|
247
|
+
const originalError = console.error;
|
|
248
|
+
const errorMock = mock(() => {});
|
|
249
|
+
console.error = errorMock;
|
|
250
|
+
|
|
251
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
252
|
+
<TapToEdit setValue={setValue} title="Name" value="Jane" />
|
|
253
|
+
);
|
|
254
|
+
await act(async () => {
|
|
255
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
256
|
+
});
|
|
257
|
+
await act(async () => {
|
|
258
|
+
fireEvent.press(getByText("Save"));
|
|
259
|
+
});
|
|
260
|
+
expect(errorMock).toHaveBeenCalled();
|
|
261
|
+
console.error = originalError;
|
|
262
|
+
});
|
|
93
263
|
});
|
|
94
264
|
|
|
95
265
|
describe("formatAddress", () => {
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import {describe, expect, it} from "bun:test";
|
|
2
|
-
import {render} from "@testing-library/react-native";
|
|
1
|
+
import {beforeAll, describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {act, render} from "@testing-library/react-native";
|
|
3
3
|
import {Text, View} from "react-native";
|
|
4
4
|
|
|
5
5
|
import {TerrenoProvider} from "./TerrenoProvider";
|
|
6
|
+
import {useToast} from "./Toast";
|
|
7
|
+
|
|
8
|
+
interface RafGlobal {
|
|
9
|
+
requestAnimationFrame?: (callback: FrameRequestCallback) => number;
|
|
10
|
+
cancelAnimationFrame?: (id: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
const g = globalThis as RafGlobal;
|
|
15
|
+
if (!g.requestAnimationFrame) {
|
|
16
|
+
g.requestAnimationFrame = (callback) =>
|
|
17
|
+
setTimeout(() => callback(Date.now()), 0) as unknown as number;
|
|
18
|
+
g.cancelAnimationFrame = (id) => clearTimeout(id);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
6
21
|
|
|
7
22
|
describe("TerrenoProvider", () => {
|
|
8
23
|
it("renders children correctly", () => {
|
|
@@ -33,4 +48,29 @@ describe("TerrenoProvider", () => {
|
|
|
33
48
|
);
|
|
34
49
|
expect(toJSON()).toMatchSnapshot();
|
|
35
50
|
});
|
|
51
|
+
|
|
52
|
+
it("renders a toast via the configured renderToast prop", async () => {
|
|
53
|
+
const dismissSpy = mock(() => {});
|
|
54
|
+
let toastApi: ReturnType<typeof useToast> | null = null;
|
|
55
|
+
|
|
56
|
+
const ToastCaller = () => {
|
|
57
|
+
toastApi = useToast();
|
|
58
|
+
return <Text>App</Text>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const {queryByText} = render(
|
|
62
|
+
<TerrenoProvider>
|
|
63
|
+
<ToastCaller />
|
|
64
|
+
</TerrenoProvider>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(toastApi).not.toBeNull();
|
|
68
|
+
|
|
69
|
+
await act(async () => {
|
|
70
|
+
toastApi?.info("Hello from toast", {onDismiss: dismissSpy});
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(queryByText("Hello from toast")).toBeTruthy();
|
|
75
|
+
});
|
|
36
76
|
});
|
|
@@ -12,7 +12,7 @@ export class NumberPickerActionSheet extends React.Component<
|
|
|
12
12
|
TextFieldPickerActionSheetProps,
|
|
13
13
|
NumberPickerActionSheetState
|
|
14
14
|
> {
|
|
15
|
-
render() {
|
|
15
|
+
render(): React.ReactElement {
|
|
16
16
|
return (
|
|
17
17
|
<ActionSheet bounceOnOpen gestureEnabled ref={this.props.actionSheetRef}>
|
|
18
18
|
<Box marginBottom={8} paddingX={4} width="100%">
|
package/src/Theme.test.tsx
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {describe, expect, it} from "bun:test";
|
|
2
|
-
import {render} from "@testing-library/react-native";
|
|
2
|
+
import {act, render} from "@testing-library/react-native";
|
|
3
3
|
import {Text, View} from "react-native";
|
|
4
4
|
|
|
5
5
|
import {ThemeProvider, useTheme} from "./Theme";
|
|
6
6
|
|
|
7
|
+
type ThemeContextValue = ReturnType<typeof useTheme>;
|
|
8
|
+
type ThemeValue = ThemeContextValue["theme"];
|
|
9
|
+
|
|
7
10
|
const ThemeConsumer = () => {
|
|
8
11
|
const {theme} = useTheme();
|
|
9
12
|
return (
|
|
@@ -40,7 +43,7 @@ describe("Theme", () => {
|
|
|
40
43
|
|
|
41
44
|
describe("useTheme", () => {
|
|
42
45
|
it("returns theme object", () => {
|
|
43
|
-
let capturedTheme:
|
|
46
|
+
let capturedTheme: ThemeContextValue | undefined;
|
|
44
47
|
const Capture = () => {
|
|
45
48
|
capturedTheme = useTheme();
|
|
46
49
|
return null;
|
|
@@ -52,14 +55,14 @@ describe("Theme", () => {
|
|
|
52
55
|
</ThemeProvider>
|
|
53
56
|
);
|
|
54
57
|
|
|
55
|
-
expect(capturedTheme
|
|
56
|
-
expect(capturedTheme
|
|
57
|
-
expect(capturedTheme
|
|
58
|
-
expect(capturedTheme
|
|
58
|
+
expect(capturedTheme?.theme).toBeDefined();
|
|
59
|
+
expect(capturedTheme?.setTheme).toBeDefined();
|
|
60
|
+
expect(capturedTheme?.setPrimitives).toBeDefined();
|
|
61
|
+
expect(capturedTheme?.resetTheme).toBeDefined();
|
|
59
62
|
});
|
|
60
63
|
|
|
61
64
|
it("provides surface colors", () => {
|
|
62
|
-
let theme:
|
|
65
|
+
let theme: ThemeValue | undefined;
|
|
63
66
|
const Capture = () => {
|
|
64
67
|
theme = useTheme().theme;
|
|
65
68
|
return null;
|
|
@@ -71,14 +74,14 @@ describe("Theme", () => {
|
|
|
71
74
|
</ThemeProvider>
|
|
72
75
|
);
|
|
73
76
|
|
|
74
|
-
expect(theme
|
|
75
|
-
expect(theme
|
|
76
|
-
expect(theme
|
|
77
|
-
expect(theme
|
|
77
|
+
expect(theme?.surface).toBeDefined();
|
|
78
|
+
expect(theme?.surface?.base).toBeDefined();
|
|
79
|
+
expect(theme?.surface?.primary).toBeDefined();
|
|
80
|
+
expect(theme?.surface?.error).toBeDefined();
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
it("provides text colors", () => {
|
|
81
|
-
let theme:
|
|
84
|
+
let theme: ThemeValue | undefined;
|
|
82
85
|
const Capture = () => {
|
|
83
86
|
theme = useTheme().theme;
|
|
84
87
|
return null;
|
|
@@ -90,14 +93,14 @@ describe("Theme", () => {
|
|
|
90
93
|
</ThemeProvider>
|
|
91
94
|
);
|
|
92
95
|
|
|
93
|
-
expect(theme
|
|
94
|
-
expect(theme
|
|
95
|
-
expect(theme
|
|
96
|
-
expect(theme
|
|
96
|
+
expect(theme?.text).toBeDefined();
|
|
97
|
+
expect(theme?.text?.primary).toBeDefined();
|
|
98
|
+
expect(theme?.text?.inverted).toBeDefined();
|
|
99
|
+
expect(theme?.text?.error).toBeDefined();
|
|
97
100
|
});
|
|
98
101
|
|
|
99
102
|
it("provides border colors", () => {
|
|
100
|
-
let theme:
|
|
103
|
+
let theme: ThemeValue | undefined;
|
|
101
104
|
const Capture = () => {
|
|
102
105
|
theme = useTheme().theme;
|
|
103
106
|
return null;
|
|
@@ -109,12 +112,12 @@ describe("Theme", () => {
|
|
|
109
112
|
</ThemeProvider>
|
|
110
113
|
);
|
|
111
114
|
|
|
112
|
-
expect(theme
|
|
113
|
-
expect(theme
|
|
115
|
+
expect(theme?.border).toBeDefined();
|
|
116
|
+
expect(theme?.border?.default).toBeDefined();
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
it("provides spacing values", () => {
|
|
117
|
-
let theme:
|
|
120
|
+
let theme: ThemeValue | undefined;
|
|
118
121
|
const Capture = () => {
|
|
119
122
|
theme = useTheme().theme;
|
|
120
123
|
return null;
|
|
@@ -126,14 +129,14 @@ describe("Theme", () => {
|
|
|
126
129
|
</ThemeProvider>
|
|
127
130
|
);
|
|
128
131
|
|
|
129
|
-
expect(theme
|
|
130
|
-
expect(theme
|
|
131
|
-
expect(theme
|
|
132
|
-
expect(theme
|
|
132
|
+
expect(theme?.spacing).toBeDefined();
|
|
133
|
+
expect(theme?.spacing?.sm).toBeDefined();
|
|
134
|
+
expect(theme?.spacing?.md).toBeDefined();
|
|
135
|
+
expect(theme?.spacing?.lg).toBeDefined();
|
|
133
136
|
});
|
|
134
137
|
|
|
135
138
|
it("provides radius values", () => {
|
|
136
|
-
let theme:
|
|
139
|
+
let theme: ThemeValue | undefined;
|
|
137
140
|
const Capture = () => {
|
|
138
141
|
theme = useTheme().theme;
|
|
139
142
|
return null;
|
|
@@ -145,9 +148,96 @@ describe("Theme", () => {
|
|
|
145
148
|
</ThemeProvider>
|
|
146
149
|
);
|
|
147
150
|
|
|
148
|
-
expect(theme
|
|
149
|
-
expect(theme
|
|
150
|
-
expect(theme
|
|
151
|
+
expect(theme?.radius).toBeDefined();
|
|
152
|
+
expect(theme?.radius?.default).toBeDefined();
|
|
153
|
+
expect(theme?.radius?.rounded).toBeDefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("updates theme when setTheme is called", () => {
|
|
157
|
+
let captured: ThemeContextValue | undefined;
|
|
158
|
+
const Capture = () => {
|
|
159
|
+
captured = useTheme();
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
render(
|
|
163
|
+
<ThemeProvider>
|
|
164
|
+
<Capture />
|
|
165
|
+
</ThemeProvider>
|
|
166
|
+
);
|
|
167
|
+
act(() => {
|
|
168
|
+
captured?.setTheme({surface: {base: "error100"}});
|
|
169
|
+
});
|
|
170
|
+
expect(captured?.theme.surface.base).toBe("#D33232");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("updates primitives when setPrimitives is called", () => {
|
|
174
|
+
let captured: ThemeContextValue | undefined;
|
|
175
|
+
const Capture = () => {
|
|
176
|
+
captured = useTheme();
|
|
177
|
+
return null;
|
|
178
|
+
};
|
|
179
|
+
render(
|
|
180
|
+
<ThemeProvider>
|
|
181
|
+
<Capture />
|
|
182
|
+
</ThemeProvider>
|
|
183
|
+
);
|
|
184
|
+
act(() => {
|
|
185
|
+
captured?.setPrimitives({neutral000: "#AABBCC"});
|
|
186
|
+
});
|
|
187
|
+
expect(captured?.theme.surface.base).toBe("#AABBCC");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("resets theme to default when resetTheme is called", () => {
|
|
191
|
+
let captured: ThemeContextValue | undefined;
|
|
192
|
+
const Capture = () => {
|
|
193
|
+
captured = useTheme();
|
|
194
|
+
return null;
|
|
195
|
+
};
|
|
196
|
+
render(
|
|
197
|
+
<ThemeProvider>
|
|
198
|
+
<Capture />
|
|
199
|
+
</ThemeProvider>
|
|
200
|
+
);
|
|
201
|
+
act(() => {
|
|
202
|
+
captured?.setTheme({surface: {base: "error100"}});
|
|
203
|
+
captured?.setPrimitives({neutral000: "#123456"});
|
|
204
|
+
});
|
|
205
|
+
act(() => {
|
|
206
|
+
captured?.resetTheme();
|
|
207
|
+
});
|
|
208
|
+
expect(captured?.theme.surface.base).toBe("#FFFFFF");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("supports non-object top-level values when setTheme is called", () => {
|
|
212
|
+
let captured: ThemeContextValue | undefined;
|
|
213
|
+
const Capture = () => {
|
|
214
|
+
captured = useTheme();
|
|
215
|
+
return null;
|
|
216
|
+
};
|
|
217
|
+
render(
|
|
218
|
+
<ThemeProvider>
|
|
219
|
+
<Capture />
|
|
220
|
+
</ThemeProvider>
|
|
221
|
+
);
|
|
222
|
+
act(() => {
|
|
223
|
+
captured?.setTheme({primitives: undefined});
|
|
224
|
+
});
|
|
225
|
+
expect(captured?.theme).toBeDefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("invokes the no-op default context setters when rendered without a provider", () => {
|
|
229
|
+
let captured: ThemeContextValue | undefined;
|
|
230
|
+
const Capture = () => {
|
|
231
|
+
captured = useTheme();
|
|
232
|
+
return null;
|
|
233
|
+
};
|
|
234
|
+
render(<Capture />);
|
|
235
|
+
// Exercise the default no-op callbacks on the context.
|
|
236
|
+
expect(() => {
|
|
237
|
+
captured?.resetTheme();
|
|
238
|
+
captured?.setPrimitives({neutral000: "#000000"});
|
|
239
|
+
captured?.setTheme({surface: {base: "neutral000"}});
|
|
240
|
+
}).not.toThrow();
|
|
151
241
|
});
|
|
152
242
|
});
|
|
153
243
|
});
|