@terreno/ui 0.14.2 → 0.15.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/Badge.js +1 -0
- package/dist/Badge.js.map +1 -1
- package/dist/Banner.d.ts +8 -0
- package/dist/Banner.js +2 -2
- package/dist/Banner.js.map +1 -1
- package/dist/PickerSelect.js +6 -2
- package/dist/PickerSelect.js.map +1 -1
- package/dist/Signature.d.ts +8 -1
- package/dist/Signature.js +93 -18
- package/dist/Signature.js.map +1 -1
- package/dist/Signature.native.d.ts +15 -0
- package/dist/Signature.native.js +116 -21
- package/dist/Signature.native.js.map +1 -1
- package/dist/TapToEdit.js +1 -1
- package/dist/TapToEdit.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
- package/src/Badge.test.tsx +7 -0
- package/src/Badge.tsx +1 -0
- package/src/Banner.test.tsx +23 -3
- package/src/Banner.tsx +3 -3
- package/src/DateTimeField.test.tsx +226 -0
- package/src/Field.test.tsx +23 -0
- package/src/PickerSelect.test.tsx +22 -0
- package/src/PickerSelect.tsx +24 -8
- package/src/Signature.native.tsx +147 -30
- package/src/Signature.test.tsx +2 -49
- package/src/Signature.tsx +128 -22
- package/src/SignatureField.test.tsx +0 -9
- package/src/TapToEdit.test.tsx +33 -0
- package/src/TapToEdit.tsx +1 -1
- package/src/ToastNotifications.test.tsx +74 -1
- package/src/__snapshots__/CustomSelectField.test.tsx.snap +5 -4
- package/src/__snapshots__/Field.test.tsx.snap +377 -0
- package/src/__snapshots__/PickerSelect.test.tsx.snap +5 -4
- package/src/__snapshots__/SegmentedControl.test.tsx.snap +9 -0
- package/src/__snapshots__/SelectField.test.tsx.snap +5 -4
- package/src/__snapshots__/Signature.test.tsx.snap +13 -3
- package/src/__snapshots__/SignatureField.test.tsx.snap +10 -3
- package/src/bunSetup.ts +0 -15
- package/src/index.tsx +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@react-native-community/slider": "5.0.1",
|
|
27
27
|
"@react-native-picker/picker": "2.11.4",
|
|
28
28
|
"@react-navigation/native": "^7.1.8",
|
|
29
|
+
"@shopify/react-native-skia": "2.2.12",
|
|
29
30
|
"emoji-datasource": "^16.0.0",
|
|
30
31
|
"expo-clipboard": "~8.0.8",
|
|
31
32
|
"expo-document-picker": "~14.0.8",
|
|
@@ -67,12 +68,10 @@
|
|
|
67
68
|
"react-native-reanimated": "~4.1.1",
|
|
68
69
|
"react-native-safe-area-context": "~5.6.0",
|
|
69
70
|
"react-native-screens": "~4.16.0",
|
|
70
|
-
"react-native-signature-canvas": "^4.7.4",
|
|
71
71
|
"react-native-svg": "15.12.1",
|
|
72
72
|
"react-native-swiper-flatlist": "^3.2.5",
|
|
73
73
|
"react-native-web": "~0.21.0",
|
|
74
74
|
"react-native-webview": "13.15.0",
|
|
75
|
-
"react-signature-canvas": "^1.1.0-alpha.2",
|
|
76
75
|
"react-time-picker": "^8.0.2"
|
|
77
76
|
},
|
|
78
77
|
"devDependencies": {
|
|
@@ -87,7 +86,6 @@
|
|
|
87
86
|
"@types/minimatch": "^6.0.0",
|
|
88
87
|
"@types/react": "~19.1.10",
|
|
89
88
|
"@types/react-datetime-picker": "^5.0.0",
|
|
90
|
-
"@types/react-signature-canvas": "^1.0.5",
|
|
91
89
|
"@types/react-time-picker": "^6.0.0",
|
|
92
90
|
"babel-preset-react-app": "^10.0.1",
|
|
93
91
|
"expo-modules-core": "~3.0.29",
|
|
@@ -138,5 +136,5 @@
|
|
|
138
136
|
"test:coverage": "TZ=America/New_York bun run ../scripts/check-coverage.ts",
|
|
139
137
|
"types": "bun typedoc"
|
|
140
138
|
},
|
|
141
|
-
"version": "0.
|
|
139
|
+
"version": "0.15.0"
|
|
142
140
|
}
|
package/src/Badge.test.tsx
CHANGED
|
@@ -34,6 +34,13 @@ describe("Badge", () => {
|
|
|
34
34
|
expect(getByText("50")).toBeTruthy();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it("keeps number badges scannable with a minimum width", () => {
|
|
38
|
+
const {getByTestId} = renderWithTheme(
|
|
39
|
+
<Badge testID="number-badge" value={5} variant="numberOnly" />
|
|
40
|
+
);
|
|
41
|
+
expect(getByTestId("number-badge")).toHaveStyle({minWidth: 20});
|
|
42
|
+
});
|
|
43
|
+
|
|
37
44
|
it("applies correct status colors", () => {
|
|
38
45
|
const statuses = ["error", "warning", "info", "success", "neutral"] as const;
|
|
39
46
|
|
package/src/Badge.tsx
CHANGED
|
@@ -108,6 +108,7 @@ export const Badge = ({
|
|
|
108
108
|
paddingVertical: variant === "iconOnly" ? 1 : theme.spacing.xs,
|
|
109
109
|
width: variant === "iconOnly" ? 16 : "auto",
|
|
110
110
|
},
|
|
111
|
+
...(variant === "numberOnly" ? [{minWidth: 20}] : []),
|
|
111
112
|
isIconOnly && {height: 16, width: 16},
|
|
112
113
|
secondary && {borderColor, borderWidth: 1},
|
|
113
114
|
]}
|
package/src/Banner.test.tsx
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
-
import {act, fireEvent, waitFor} from "@testing-library/react-native";
|
|
1
|
+
import {beforeEach, describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import {act, fireEvent, render, waitFor} from "@testing-library/react-native";
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
-
import {Banner, hideBanner} from "./Banner";
|
|
5
|
+
import {Banner, BannerButton, hideBanner} from "./Banner";
|
|
6
|
+
import {ThemeContext} from "./Theme";
|
|
6
7
|
import {renderWithTheme} from "./test-utils";
|
|
7
8
|
import {Unifier} from "./Unifier";
|
|
8
9
|
|
|
9
10
|
describe("Banner", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
Unifier.storage.getItem = mock(() => Promise.resolve(null));
|
|
13
|
+
Unifier.storage.setItem = mock(() => Promise.resolve());
|
|
14
|
+
});
|
|
10
15
|
it("renders correctly with default props", () => {
|
|
11
16
|
const {toJSON} = renderWithTheme(<Banner id="test-banner" text="Test message" />);
|
|
12
17
|
expect(toJSON()).toMatchSnapshot();
|
|
@@ -274,4 +279,19 @@ describe("Banner", () => {
|
|
|
274
279
|
expect(queryByText("Non persistent")).toBeNull();
|
|
275
280
|
});
|
|
276
281
|
});
|
|
282
|
+
|
|
283
|
+
it("BannerButton returns null when theme is not available", () => {
|
|
284
|
+
const nullThemeContext = {
|
|
285
|
+
resetTheme: () => {},
|
|
286
|
+
setPrimitives: () => {},
|
|
287
|
+
setTheme: () => {},
|
|
288
|
+
theme: null,
|
|
289
|
+
};
|
|
290
|
+
const {toJSON} = render(
|
|
291
|
+
<ThemeContext.Provider value={nullThemeContext as never}>
|
|
292
|
+
<BannerButton buttonOnClick={() => {}} buttonText="Test" />
|
|
293
|
+
</ThemeContext.Provider>
|
|
294
|
+
);
|
|
295
|
+
expect(toJSON()).toBeNull();
|
|
296
|
+
});
|
|
277
297
|
});
|
package/src/Banner.tsx
CHANGED
|
@@ -17,7 +17,7 @@ type BannerButtonProps = {
|
|
|
17
17
|
loading?: boolean;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
const BannerButton = ({
|
|
20
|
+
export const BannerButton = ({
|
|
21
21
|
loading: propsLoading,
|
|
22
22
|
buttonText,
|
|
23
23
|
buttonIconName,
|
|
@@ -178,14 +178,14 @@ export const Banner = (props: BannerProps): React.ReactElement | null => {
|
|
|
178
178
|
<View style={{paddingLeft: 16, paddingRight: 10}}>
|
|
179
179
|
<BannerButton
|
|
180
180
|
buttonIconName={buttonIconName}
|
|
181
|
-
buttonOnClick={buttonOnClick
|
|
181
|
+
buttonOnClick={buttonOnClick!}
|
|
182
182
|
buttonText={buttonText}
|
|
183
183
|
/>
|
|
184
184
|
</View>
|
|
185
185
|
)}
|
|
186
186
|
{Boolean(buttonText && !buttonIconName && buttonOnClick) && (
|
|
187
187
|
<View style={{paddingLeft: 16, paddingRight: 10}}>
|
|
188
|
-
<BannerButton buttonOnClick={buttonOnClick
|
|
188
|
+
<BannerButton buttonOnClick={buttonOnClick!} buttonText={buttonText} />
|
|
189
189
|
</View>
|
|
190
190
|
)}
|
|
191
191
|
</View>
|
|
@@ -1105,4 +1105,230 @@ describe("DateTimeField", () => {
|
|
|
1105
1105
|
expect(mockOnChange).toHaveBeenCalled();
|
|
1106
1106
|
});
|
|
1107
1107
|
});
|
|
1108
|
+
|
|
1109
|
+
describe("12 AM handling in time type (getISOFromFields)", () => {
|
|
1110
|
+
it("should convert hour 12 AM to 0 in time type", async () => {
|
|
1111
|
+
setDesktop();
|
|
1112
|
+
const user = userEvent.setup();
|
|
1113
|
+
// 04:00 UTC = 00:00 (12:00 AM) in America/New_York
|
|
1114
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1115
|
+
<DateTimeField
|
|
1116
|
+
onChange={mockOnChange}
|
|
1117
|
+
timezone="America/New_York"
|
|
1118
|
+
type="time"
|
|
1119
|
+
value="2023-05-15T04:00:00.000Z"
|
|
1120
|
+
/>
|
|
1121
|
+
);
|
|
1122
|
+
expect(getByPlaceholderText("hh").props.value).toBe("12");
|
|
1123
|
+
|
|
1124
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
1125
|
+
await user.clear(minuteInput);
|
|
1126
|
+
await user.type(minuteInput, "15");
|
|
1127
|
+
await act(async () => {
|
|
1128
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1129
|
+
});
|
|
1130
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1131
|
+
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
1132
|
+
const parsed = DateTime.fromISO(lastCall).setZone("America/New_York");
|
|
1133
|
+
expect(parsed.hour).toBe(0);
|
|
1134
|
+
expect(parsed.minute).toBe(15);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it("should convert hour 12 AM to 0 in datetime type", async () => {
|
|
1138
|
+
setDesktop();
|
|
1139
|
+
const user = userEvent.setup();
|
|
1140
|
+
// 04:30 UTC = 00:30 (12:30 AM) in America/New_York
|
|
1141
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1142
|
+
<DateTimeField
|
|
1143
|
+
onChange={mockOnChange}
|
|
1144
|
+
timezone="America/New_York"
|
|
1145
|
+
type="datetime"
|
|
1146
|
+
value="2023-05-15T04:30:00.000Z"
|
|
1147
|
+
/>
|
|
1148
|
+
);
|
|
1149
|
+
expect(getByPlaceholderText("hh").props.value).toBe("12");
|
|
1150
|
+
|
|
1151
|
+
const minuteInput = getByPlaceholderText("mm");
|
|
1152
|
+
await user.clear(minuteInput);
|
|
1153
|
+
await user.type(minuteInput, "45");
|
|
1154
|
+
await act(async () => {
|
|
1155
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1156
|
+
});
|
|
1157
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1158
|
+
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
1159
|
+
const parsed = DateTime.fromISO(lastCall).setZone("America/New_York");
|
|
1160
|
+
expect(parsed.hour).toBe(0);
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
describe("onActionSheetChange invalid date handling", () => {
|
|
1165
|
+
it("should warn and return early for invalid ISO string", async () => {
|
|
1166
|
+
setDesktop();
|
|
1167
|
+
const warnSpy = mock(() => {});
|
|
1168
|
+
const originalWarn = console.warn;
|
|
1169
|
+
console.warn = warnSpy;
|
|
1170
|
+
|
|
1171
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
1172
|
+
<DateTimeField
|
|
1173
|
+
onChange={mockOnChange}
|
|
1174
|
+
timezone="America/New_York"
|
|
1175
|
+
type="date"
|
|
1176
|
+
value="2023-05-15T00:00:00.000Z"
|
|
1177
|
+
/>
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
mockOnChange.mockClear();
|
|
1181
|
+
const actionSheet = UNSAFE_root.findAll(
|
|
1182
|
+
(n: any) => n.props?.onChange && n.props?.visible !== undefined
|
|
1183
|
+
);
|
|
1184
|
+
expect(actionSheet.length).toBeGreaterThan(0);
|
|
1185
|
+
await act(async () => {
|
|
1186
|
+
actionSheet[0].props.onChange("not-a-valid-date");
|
|
1187
|
+
});
|
|
1188
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1189
|
+
"Invalid date passed to DateTimeField",
|
|
1190
|
+
"not-a-valid-date"
|
|
1191
|
+
);
|
|
1192
|
+
expect(mockOnChange).not.toHaveBeenCalled();
|
|
1193
|
+
|
|
1194
|
+
console.warn = originalWarn;
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
describe("useEffect invalid value handling", () => {
|
|
1199
|
+
it("should warn and return early for invalid non-empty value prop", () => {
|
|
1200
|
+
const warnSpy = mock(() => {});
|
|
1201
|
+
const originalWarn = console.warn;
|
|
1202
|
+
console.warn = warnSpy;
|
|
1203
|
+
|
|
1204
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1205
|
+
<DateTimeField onChange={mockOnChange} type="date" value="invalid-date-string" />
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1209
|
+
"Invalid date passed to DateTimeField",
|
|
1210
|
+
"invalid-date-string"
|
|
1211
|
+
);
|
|
1212
|
+
expect(getByPlaceholderText("MM").props.value).toBe("");
|
|
1213
|
+
|
|
1214
|
+
console.warn = originalWarn;
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("should warn for invalid value in time type", () => {
|
|
1218
|
+
const warnSpy = mock(() => {});
|
|
1219
|
+
const originalWarn = console.warn;
|
|
1220
|
+
console.warn = warnSpy;
|
|
1221
|
+
|
|
1222
|
+
renderWithTheme(
|
|
1223
|
+
<DateTimeField
|
|
1224
|
+
onChange={mockOnChange}
|
|
1225
|
+
timezone="America/New_York"
|
|
1226
|
+
type="time"
|
|
1227
|
+
value="not-valid"
|
|
1228
|
+
/>
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
expect(warnSpy).toHaveBeenCalledWith("Invalid date passed to DateTimeField", "not-valid");
|
|
1232
|
+
|
|
1233
|
+
console.warn = originalWarn;
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
describe("getFieldValue datetime hour/minute indices", () => {
|
|
1238
|
+
it("should return hour and minute for datetime indices 3 and 4", () => {
|
|
1239
|
+
setDesktop();
|
|
1240
|
+
// 20:30 UTC = 4:30 PM in America/New_York
|
|
1241
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1242
|
+
<DateTimeField
|
|
1243
|
+
onChange={mockOnChange}
|
|
1244
|
+
timezone="America/New_York"
|
|
1245
|
+
type="datetime"
|
|
1246
|
+
value="2023-05-15T20:30:00.000Z"
|
|
1247
|
+
/>
|
|
1248
|
+
);
|
|
1249
|
+
// Indices 0-2 are date fields, indices 3-4 are hour/minute
|
|
1250
|
+
expect(getByPlaceholderText("hh").props.value).toBe("04");
|
|
1251
|
+
expect(getByPlaceholderText("mm").props.value).toBe("30");
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it("should return hour and minute for datetime at midnight", () => {
|
|
1255
|
+
setDesktop();
|
|
1256
|
+
// 04:00 UTC = 00:00 (12:00 AM) in America/New_York
|
|
1257
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1258
|
+
<DateTimeField
|
|
1259
|
+
onChange={mockOnChange}
|
|
1260
|
+
timezone="America/New_York"
|
|
1261
|
+
type="datetime"
|
|
1262
|
+
value="2023-05-15T04:00:00.000Z"
|
|
1263
|
+
/>
|
|
1264
|
+
);
|
|
1265
|
+
expect(getByPlaceholderText("hh").props.value).toBe("12");
|
|
1266
|
+
expect(getByPlaceholderText("mm").props.value).toBe("00");
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
describe("handleTimezoneChange branches", () => {
|
|
1271
|
+
it("should call onTimezoneChange when provided for datetime type", async () => {
|
|
1272
|
+
setDesktop();
|
|
1273
|
+
const mockTzChange = mock(() => {});
|
|
1274
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
1275
|
+
<DateTimeField
|
|
1276
|
+
onChange={mockOnChange}
|
|
1277
|
+
onTimezoneChange={mockTzChange}
|
|
1278
|
+
timezone="America/New_York"
|
|
1279
|
+
type="datetime"
|
|
1280
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1281
|
+
/>
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
const tzPickers = UNSAFE_root.findAll((n: any) => n.props?.onTimezoneChange);
|
|
1285
|
+
expect(tzPickers.length).toBeGreaterThan(0);
|
|
1286
|
+
await act(async () => {
|
|
1287
|
+
tzPickers[0].props.onTimezoneChange("America/Chicago");
|
|
1288
|
+
});
|
|
1289
|
+
expect(mockTzChange).toHaveBeenCalledWith("America/Chicago");
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
it("should set local timezone when onTimezoneChange not provided for datetime type", async () => {
|
|
1293
|
+
setDesktop();
|
|
1294
|
+
const {UNSAFE_root} = renderWithTheme(
|
|
1295
|
+
<DateTimeField
|
|
1296
|
+
onChange={mockOnChange}
|
|
1297
|
+
timezone="America/New_York"
|
|
1298
|
+
type="datetime"
|
|
1299
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1300
|
+
/>
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
const tzPickers = UNSAFE_root.findAll((n: any) => n.props?.onTimezoneChange);
|
|
1304
|
+
expect(tzPickers.length).toBeGreaterThan(0);
|
|
1305
|
+
await act(async () => {
|
|
1306
|
+
tzPickers[0].props.onTimezoneChange("America/Chicago");
|
|
1307
|
+
});
|
|
1308
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
describe("minute validation in validateField", () => {
|
|
1313
|
+
it("should validate minute field for datetime type via hour change triggering revalidation", async () => {
|
|
1314
|
+
setDesktop();
|
|
1315
|
+
const user = userEvent.setup();
|
|
1316
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
1317
|
+
<DateTimeField
|
|
1318
|
+
onChange={mockOnChange}
|
|
1319
|
+
timezone="America/New_York"
|
|
1320
|
+
type="datetime"
|
|
1321
|
+
value="2023-05-15T15:30:00.000Z"
|
|
1322
|
+
/>
|
|
1323
|
+
);
|
|
1324
|
+
// Type an invalid hour (triggers validateField for datetime index 3)
|
|
1325
|
+
const hourInput = getByPlaceholderText("hh");
|
|
1326
|
+
await user.clear(hourInput);
|
|
1327
|
+
await user.type(hourInput, "0");
|
|
1328
|
+
await act(async () => {
|
|
1329
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1330
|
+
});
|
|
1331
|
+
expect(hourInput).toBeTruthy();
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1108
1334
|
});
|
package/src/Field.test.tsx
CHANGED
|
@@ -125,4 +125,27 @@ describe("Field", () => {
|
|
|
125
125
|
);
|
|
126
126
|
expect(toJSON()).toMatchSnapshot();
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
it("renders customSelect field", () => {
|
|
130
|
+
const {toJSON} = renderWithTheme(
|
|
131
|
+
<Field
|
|
132
|
+
label="Custom"
|
|
133
|
+
onChange={() => {}}
|
|
134
|
+
options={[
|
|
135
|
+
{label: "Option A", value: "a"},
|
|
136
|
+
{label: "Option B", value: "b"},
|
|
137
|
+
]}
|
|
138
|
+
type="customSelect"
|
|
139
|
+
value="a"
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
expect(toJSON()).toMatchSnapshot();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("renders signature field", () => {
|
|
146
|
+
const {toJSON} = renderWithTheme(
|
|
147
|
+
<Field label="Sign here" onChange={() => {}} type="signature" value="" />
|
|
148
|
+
);
|
|
149
|
+
expect(toJSON()).toMatchSnapshot();
|
|
150
|
+
});
|
|
128
151
|
});
|
|
@@ -308,6 +308,28 @@ describe("PickerSelect", () => {
|
|
|
308
308
|
restoreDocument();
|
|
309
309
|
}
|
|
310
310
|
});
|
|
311
|
+
|
|
312
|
+
it("calls onValueChange when a web dropdown option is selected", async () => {
|
|
313
|
+
ensureDocument();
|
|
314
|
+
savedOS = PlatformModule.OS;
|
|
315
|
+
try {
|
|
316
|
+
PlatformModule.OS = "web";
|
|
317
|
+
const mockOnValueChange = mock(() => {});
|
|
318
|
+
const {getByTestId} = renderWithTheme(
|
|
319
|
+
<RNPickerSelect {...defaultProps} onValueChange={mockOnValueChange} value="1" />
|
|
320
|
+
);
|
|
321
|
+
await act(async () => {
|
|
322
|
+
fireEvent.press(getByTestId("web_picker"));
|
|
323
|
+
});
|
|
324
|
+
await act(async () => {
|
|
325
|
+
fireEvent.press(getByTestId("web_dropdown_option_2"));
|
|
326
|
+
});
|
|
327
|
+
expect(mockOnValueChange).toHaveBeenCalledWith("2", 2);
|
|
328
|
+
} finally {
|
|
329
|
+
PlatformModule.OS = savedOS;
|
|
330
|
+
restoreDocument();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
311
333
|
});
|
|
312
334
|
|
|
313
335
|
describe("android rendering", () => {
|
package/src/PickerSelect.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
Text,
|
|
39
39
|
TextInput,
|
|
40
40
|
type TextInputProps,
|
|
41
|
+
type TextProps,
|
|
41
42
|
TouchableOpacity,
|
|
42
43
|
View,
|
|
43
44
|
} from "react-native";
|
|
@@ -388,6 +389,7 @@ export const RNPickerSelect = ({
|
|
|
388
389
|
return <View style={{pointerEvents: "box-only"}}>{children}</View>;
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
const textProps = textInputProps as Partial<TextProps> | undefined;
|
|
391
393
|
return (
|
|
392
394
|
<View
|
|
393
395
|
style={{
|
|
@@ -397,13 +399,27 @@ export const RNPickerSelect = ({
|
|
|
397
399
|
width: "100%",
|
|
398
400
|
}}
|
|
399
401
|
>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
402
|
+
{disabled ? (
|
|
403
|
+
<Text
|
|
404
|
+
{...textProps}
|
|
405
|
+
style={
|
|
406
|
+
textProps?.style
|
|
407
|
+
? [{color: theme.text.secondaryLight, flex: 1}, textProps.style]
|
|
408
|
+
: {color: theme.text.secondaryLight, flex: 1}
|
|
409
|
+
}
|
|
410
|
+
testID={textInputProps?.testID ?? "text_input"}
|
|
411
|
+
>
|
|
412
|
+
{selectedItem?.inputLabel ? selectedItem?.inputLabel : selectedItem?.label}
|
|
413
|
+
</Text>
|
|
414
|
+
) : (
|
|
415
|
+
<TextInput
|
|
416
|
+
readOnly
|
|
417
|
+
style={{color: theme.text.primary}}
|
|
418
|
+
testID="text_input"
|
|
419
|
+
value={selectedItem?.inputLabel ? selectedItem?.inputLabel : selectedItem?.label}
|
|
420
|
+
{...textInputProps}
|
|
421
|
+
/>
|
|
422
|
+
)}
|
|
407
423
|
{renderIcon()}
|
|
408
424
|
</View>
|
|
409
425
|
);
|
|
@@ -629,7 +645,7 @@ export const RNPickerSelect = ({
|
|
|
629
645
|
{...touchableWrapperProps}
|
|
630
646
|
>
|
|
631
647
|
<Text
|
|
632
|
-
numberOfLines={1}
|
|
648
|
+
numberOfLines={disabled ? undefined : 1}
|
|
633
649
|
style={{
|
|
634
650
|
color: disabled ? theme.text.secondaryLight : theme.text.primary,
|
|
635
651
|
flex: 1,
|