@terreno/ui 0.14.0 → 0.14.2

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 (98) hide show
  1. package/dist/ActionSheet.d.ts +1 -1
  2. package/dist/ActionSheet.js +17 -29
  3. package/dist/ActionSheet.js.map +1 -1
  4. package/dist/Common.d.ts +8 -2
  5. package/dist/Common.js +4 -4
  6. package/dist/Common.js.map +1 -1
  7. package/dist/ConsentFormScreen.js +3 -3
  8. package/dist/ConsentFormScreen.js.map +1 -1
  9. package/dist/DateUtilities.d.ts +25 -25
  10. package/dist/DateUtilities.js +31 -32
  11. package/dist/DateUtilities.js.map +1 -1
  12. package/dist/MarkdownView.js +20 -7
  13. package/dist/MarkdownView.js.map +1 -1
  14. package/dist/MediaQuery.d.ts +4 -4
  15. package/dist/MediaQuery.js +8 -8
  16. package/dist/MediaQuery.js.map +1 -1
  17. package/dist/Page.d.ts +1 -0
  18. package/dist/Page.js +6 -2
  19. package/dist/Page.js.map +1 -1
  20. package/dist/PickerSelect.d.ts +1 -1
  21. package/dist/PickerSelect.js +2 -2
  22. package/dist/PickerSelect.js.map +1 -1
  23. package/dist/TapToEdit.d.ts +1 -1
  24. package/dist/TapToEdit.js +2 -3
  25. package/dist/TapToEdit.js.map +1 -1
  26. package/dist/ToastNotifications.js +2 -2
  27. package/dist/ToastNotifications.js.map +1 -1
  28. package/dist/Tooltip.d.ts +24 -1
  29. package/dist/Tooltip.js +2 -2
  30. package/dist/Tooltip.js.map +1 -1
  31. package/dist/Unifier.d.ts +1 -1
  32. package/dist/Unifier.js +14 -11
  33. package/dist/Unifier.js.map +1 -1
  34. package/dist/Utilities.d.ts +8 -8
  35. package/dist/Utilities.js +12 -14
  36. package/dist/Utilities.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/signUp/PasswordRequirements.js +3 -3
  41. package/dist/signUp/PasswordRequirements.js.map +1 -1
  42. package/dist/table/TableHeaderCell.js +1 -9
  43. package/dist/table/TableHeaderCell.js.map +1 -1
  44. package/dist/table/tableContext.d.ts +1 -1
  45. package/dist/table/tableContext.js +2 -2
  46. package/dist/table/tableContext.js.map +1 -1
  47. package/dist/useConsentHistory.d.ts +6 -1
  48. package/dist/useConsentHistory.js +2 -1
  49. package/dist/useConsentHistory.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/ActionSheet.test.tsx +554 -0
  52. package/src/ActionSheet.tsx +26 -39
  53. package/src/Banner.test.tsx +107 -1
  54. package/src/Common.ts +10 -4
  55. package/src/ConsentFormScreen.test.tsx +22 -0
  56. package/src/ConsentFormScreen.tsx +9 -3
  57. package/src/DataTable.test.tsx +393 -1
  58. package/src/DateTimeField.test.tsx +716 -2
  59. package/src/DateUtilities.tsx +37 -38
  60. package/src/HeightActionSheet.test.tsx +17 -1
  61. package/src/HeightField.test.tsx +141 -1
  62. package/src/HeightFieldDesktop.test.tsx +19 -0
  63. package/src/MarkdownView.test.tsx +28 -0
  64. package/src/MarkdownView.tsx +69 -7
  65. package/src/MediaQuery.ts +8 -8
  66. package/src/MobileAddressAutoComplete.test.tsx +26 -3
  67. package/src/Page.test.tsx +28 -0
  68. package/src/Page.tsx +17 -2
  69. package/src/PickerSelect.test.tsx +243 -0
  70. package/src/PickerSelect.tsx +3 -3
  71. package/src/SplitPage.test.tsx +299 -43
  72. package/src/TapToEdit.test.tsx +44 -0
  73. package/src/TapToEdit.tsx +2 -3
  74. package/src/ToastNotifications.test.tsx +1412 -0
  75. package/src/ToastNotifications.tsx +2 -2
  76. package/src/Tooltip.test.tsx +1294 -3
  77. package/src/Tooltip.tsx +2 -2
  78. package/src/Unifier.ts +14 -11
  79. package/src/Utilities.tsx +14 -16
  80. package/src/WebAddressAutocomplete.test.tsx +237 -0
  81. package/src/WebDropdownMenu.test.tsx +51 -2
  82. package/src/__snapshots__/Banner.test.tsx.snap +125 -0
  83. package/src/__snapshots__/DataTable.test.tsx.snap +366 -0
  84. package/src/__snapshots__/MarkdownView.test.tsx.snap +284 -74
  85. package/src/__snapshots__/SplitPage.test.tsx.snap +698 -46
  86. package/src/bunSetup.ts +0 -4
  87. package/src/index.tsx +1 -1
  88. package/src/login/LoginScreen.test.tsx +35 -1
  89. package/src/signUp/PasswordRequirements.tsx +9 -6
  90. package/src/signUp/__snapshots__/PasswordRequirements.test.tsx.snap +50 -2
  91. package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +25 -1
  92. package/src/table/TableHeaderCell.tsx +8 -11
  93. package/src/table/TableRow.test.tsx +31 -1
  94. package/src/table/__snapshots__/TableHeaderCell.test.tsx.snap +2 -0
  95. package/src/table/tableContext.tsx +2 -2
  96. package/src/useConsentHistory.test.ts +20 -13
  97. package/src/useConsentHistory.ts +7 -2
  98. package/src/useStoredState.test.tsx +47 -0
package/src/bunSetup.ts CHANGED
@@ -1189,10 +1189,6 @@ mock.module("react-native/Libraries/vendor/core/ErrorUtils", () => ({
1189
1189
  },
1190
1190
  }));
1191
1191
 
1192
- mock.module("react-native/Libraries/Core/ReactNativeVersion", () => ({
1193
- version: {major: 0, minor: 81, patch: 5},
1194
- }));
1195
-
1196
1192
  mock.module("react-native/Libraries/Core/NativeExceptionsManager", () => ({
1197
1193
  default: null,
1198
1194
  }));
package/src/index.tsx CHANGED
@@ -87,7 +87,7 @@ export * from "./TextArea";
87
87
  export * from "./TextField";
88
88
  export * from "./Theme";
89
89
  export * from "./Toast";
90
- export * from "./Tooltip";
90
+ export {Tooltip} from "./Tooltip";
91
91
  export * from "./table/Table";
92
92
  export * from "./table/Table";
93
93
  export * from "./table/TableBadge";
@@ -1,5 +1,5 @@
1
1
  import {describe, expect, it, mock} from "bun:test";
2
- import {fireEvent} from "@testing-library/react-native";
2
+ import {act, fireEvent, waitFor} from "@testing-library/react-native";
3
3
  import {renderWithTheme} from "../test-utils";
4
4
  import {LoginScreen} from "./LoginScreen";
5
5
 
@@ -131,6 +131,18 @@ describe("LoginScreen", () => {
131
131
  expect(queryByTestId("login-screen-signup-link")).toBeNull();
132
132
  });
133
133
 
134
+ it("calls onSubmit when submit button is pressed and fields filled", async () => {
135
+ const onSubmit = mock(() => Promise.resolve());
136
+ const {getByTestId} = renderWithTheme(
137
+ <LoginScreen fields={defaultFields} onSubmit={onSubmit} />
138
+ );
139
+ fireEvent.changeText(getByTestId("login-screen-email-input"), "user@test.com");
140
+ fireEvent.changeText(getByTestId("login-screen-password-input"), "secret123");
141
+ fireEvent.press(getByTestId("login-screen-submit-button"));
142
+ await new Promise((r) => setTimeout(r, 600));
143
+ expect(onSubmit).toHaveBeenCalled();
144
+ });
145
+
134
146
  it("renders correctly with all props", () => {
135
147
  const {toJSON} = renderWithTheme(
136
148
  <LoginScreen
@@ -145,4 +157,26 @@ describe("LoginScreen", () => {
145
157
  );
146
158
  expect(toJSON()).toMatchSnapshot();
147
159
  });
160
+
161
+ it("calls onSubmit with form values when submit button is pressed", async () => {
162
+ const onSubmit = mock(() => Promise.resolve());
163
+ const {getByTestId} = renderWithTheme(
164
+ <LoginScreen fields={defaultFields} onSubmit={onSubmit} />
165
+ );
166
+
167
+ await act(async () => {
168
+ fireEvent.changeText(getByTestId("login-screen-email-input"), "user@test.com");
169
+ });
170
+ await act(async () => {
171
+ fireEvent.changeText(getByTestId("login-screen-password-input"), "secret123");
172
+ });
173
+ await act(async () => {
174
+ fireEvent.press(getByTestId("login-screen-submit-button"));
175
+ });
176
+
177
+ await waitFor(() => {
178
+ expect(onSubmit).toHaveBeenCalledTimes(1);
179
+ });
180
+ expect(onSubmit.mock.calls[0][0]).toEqual({email: "user@test.com", password: "secret123"});
181
+ });
148
182
  });
@@ -1,6 +1,6 @@
1
1
  import type {FC} from "react";
2
- import {View} from "react-native";
3
2
 
3
+ import {Box} from "../Box";
4
4
  import {Icon} from "../Icon";
5
5
  import {Text} from "../Text";
6
6
  import type {PasswordRequirement} from "./signUpTypes";
@@ -23,13 +23,16 @@ export const PasswordRequirements: FC<PasswordRequirementsProps> = ({
23
23
  testID = "password-requirements",
24
24
  }) => {
25
25
  return (
26
- <View testID={testID}>
26
+ <Box testID={testID}>
27
27
  {requirements.map((req) => {
28
28
  const isMet = password.length > 0 && req.validate(password);
29
29
  return (
30
- <View
30
+ <Box
31
+ alignItems="center"
32
+ direction="row"
33
+ gap={2}
31
34
  key={req.key}
32
- style={{alignItems: "center", flexDirection: "row", gap: 8, marginBottom: 4}}
35
+ marginBottom={1}
33
36
  testID={`${testID}-${req.key}`}
34
37
  >
35
38
  <Icon
@@ -41,9 +44,9 @@ export const PasswordRequirements: FC<PasswordRequirementsProps> = ({
41
44
  <Text color={isMet ? "success" : "secondaryLight"} size="sm">
42
45
  {req.label}
43
46
  </Text>
44
- </View>
47
+ </Box>
45
48
  );
46
49
  })}
47
- </View>
50
+ </Box>
48
51
  );
49
52
  };
@@ -34,11 +34,15 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
34
34
  },
35
35
  ],
36
36
  "props": {
37
+ "onPointerEnter": [Function: AsyncFunction],
38
+ "onPointerLeave": [Function: AsyncFunction],
37
39
  "style": {
38
40
  "alignItems": "center",
41
+ "display": "flex",
39
42
  "flexDirection": "row",
40
43
  "gap": 8,
41
44
  "marginBottom": 4,
45
+ "testID": "password-requirements-minLength",
42
46
  },
43
47
  "testID": "password-requirements-minLength",
44
48
  },
@@ -74,11 +78,15 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
74
78
  },
75
79
  ],
76
80
  "props": {
81
+ "onPointerEnter": [Function: AsyncFunction],
82
+ "onPointerLeave": [Function: AsyncFunction],
77
83
  "style": {
78
84
  "alignItems": "center",
85
+ "display": "flex",
79
86
  "flexDirection": "row",
80
87
  "gap": 8,
81
88
  "marginBottom": 4,
89
+ "testID": "password-requirements-uppercase",
82
90
  },
83
91
  "testID": "password-requirements-uppercase",
84
92
  },
@@ -114,11 +122,15 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
114
122
  },
115
123
  ],
116
124
  "props": {
125
+ "onPointerEnter": [Function: AsyncFunction],
126
+ "onPointerLeave": [Function: AsyncFunction],
117
127
  "style": {
118
128
  "alignItems": "center",
129
+ "display": "flex",
119
130
  "flexDirection": "row",
120
131
  "gap": 8,
121
132
  "marginBottom": 4,
133
+ "testID": "password-requirements-lowercase",
122
134
  },
123
135
  "testID": "password-requirements-lowercase",
124
136
  },
@@ -154,11 +166,15 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
154
166
  },
155
167
  ],
156
168
  "props": {
169
+ "onPointerEnter": [Function: AsyncFunction],
170
+ "onPointerLeave": [Function: AsyncFunction],
157
171
  "style": {
158
172
  "alignItems": "center",
173
+ "display": "flex",
159
174
  "flexDirection": "row",
160
175
  "gap": 8,
161
176
  "marginBottom": 4,
177
+ "testID": "password-requirements-number",
162
178
  },
163
179
  "testID": "password-requirements-number",
164
180
  },
@@ -194,11 +210,15 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
194
210
  },
195
211
  ],
196
212
  "props": {
213
+ "onPointerEnter": [Function: AsyncFunction],
214
+ "onPointerLeave": [Function: AsyncFunction],
197
215
  "style": {
198
216
  "alignItems": "center",
217
+ "display": "flex",
199
218
  "flexDirection": "row",
200
219
  "gap": 8,
201
220
  "marginBottom": 4,
221
+ "testID": "password-requirements-special",
202
222
  },
203
223
  "testID": "password-requirements-special",
204
224
  },
@@ -206,7 +226,11 @@ exports[`PasswordRequirements renders correctly with empty password 1`] = `
206
226
  },
207
227
  ],
208
228
  "props": {
209
- "style": undefined,
229
+ "onPointerEnter": [Function: AsyncFunction],
230
+ "onPointerLeave": [Function: AsyncFunction],
231
+ "style": {
232
+ "testID": "password-requirements",
233
+ },
210
234
  "testID": "password-requirements",
211
235
  },
212
236
  "type": "View",
@@ -247,11 +271,15 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
247
271
  },
248
272
  ],
249
273
  "props": {
274
+ "onPointerEnter": [Function: AsyncFunction],
275
+ "onPointerLeave": [Function: AsyncFunction],
250
276
  "style": {
251
277
  "alignItems": "center",
278
+ "display": "flex",
252
279
  "flexDirection": "row",
253
280
  "gap": 8,
254
281
  "marginBottom": 4,
282
+ "testID": "password-requirements-minLength",
255
283
  },
256
284
  "testID": "password-requirements-minLength",
257
285
  },
@@ -287,11 +315,15 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
287
315
  },
288
316
  ],
289
317
  "props": {
318
+ "onPointerEnter": [Function: AsyncFunction],
319
+ "onPointerLeave": [Function: AsyncFunction],
290
320
  "style": {
291
321
  "alignItems": "center",
322
+ "display": "flex",
292
323
  "flexDirection": "row",
293
324
  "gap": 8,
294
325
  "marginBottom": 4,
326
+ "testID": "password-requirements-uppercase",
295
327
  },
296
328
  "testID": "password-requirements-uppercase",
297
329
  },
@@ -327,11 +359,15 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
327
359
  },
328
360
  ],
329
361
  "props": {
362
+ "onPointerEnter": [Function: AsyncFunction],
363
+ "onPointerLeave": [Function: AsyncFunction],
330
364
  "style": {
331
365
  "alignItems": "center",
366
+ "display": "flex",
332
367
  "flexDirection": "row",
333
368
  "gap": 8,
334
369
  "marginBottom": 4,
370
+ "testID": "password-requirements-lowercase",
335
371
  },
336
372
  "testID": "password-requirements-lowercase",
337
373
  },
@@ -367,11 +403,15 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
367
403
  },
368
404
  ],
369
405
  "props": {
406
+ "onPointerEnter": [Function: AsyncFunction],
407
+ "onPointerLeave": [Function: AsyncFunction],
370
408
  "style": {
371
409
  "alignItems": "center",
410
+ "display": "flex",
372
411
  "flexDirection": "row",
373
412
  "gap": 8,
374
413
  "marginBottom": 4,
414
+ "testID": "password-requirements-number",
375
415
  },
376
416
  "testID": "password-requirements-number",
377
417
  },
@@ -407,11 +447,15 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
407
447
  },
408
448
  ],
409
449
  "props": {
450
+ "onPointerEnter": [Function: AsyncFunction],
451
+ "onPointerLeave": [Function: AsyncFunction],
410
452
  "style": {
411
453
  "alignItems": "center",
454
+ "display": "flex",
412
455
  "flexDirection": "row",
413
456
  "gap": 8,
414
457
  "marginBottom": 4,
458
+ "testID": "password-requirements-special",
415
459
  },
416
460
  "testID": "password-requirements-special",
417
461
  },
@@ -419,7 +463,11 @@ exports[`PasswordRequirements renders correctly with a strong password 1`] = `
419
463
  },
420
464
  ],
421
465
  "props": {
422
- "style": undefined,
466
+ "onPointerEnter": [Function: AsyncFunction],
467
+ "onPointerLeave": [Function: AsyncFunction],
468
+ "style": {
469
+ "testID": "password-requirements",
470
+ },
423
471
  "testID": "password-requirements",
424
472
  },
425
473
  "type": "View",
@@ -397,11 +397,15 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
397
397
  },
398
398
  ],
399
399
  "props": {
400
+ "onPointerEnter": [Function: AsyncFunction],
401
+ "onPointerLeave": [Function: AsyncFunction],
400
402
  "style": {
401
403
  "alignItems": "center",
404
+ "display": "flex",
402
405
  "flexDirection": "row",
403
406
  "gap": 8,
404
407
  "marginBottom": 4,
408
+ "testID": "signup-screen-password-requirements-minLength",
405
409
  },
406
410
  "testID": "signup-screen-password-requirements-minLength",
407
411
  },
@@ -437,11 +441,15 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
437
441
  },
438
442
  ],
439
443
  "props": {
444
+ "onPointerEnter": [Function: AsyncFunction],
445
+ "onPointerLeave": [Function: AsyncFunction],
440
446
  "style": {
441
447
  "alignItems": "center",
448
+ "display": "flex",
442
449
  "flexDirection": "row",
443
450
  "gap": 8,
444
451
  "marginBottom": 4,
452
+ "testID": "signup-screen-password-requirements-uppercase",
445
453
  },
446
454
  "testID": "signup-screen-password-requirements-uppercase",
447
455
  },
@@ -477,11 +485,15 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
477
485
  },
478
486
  ],
479
487
  "props": {
488
+ "onPointerEnter": [Function: AsyncFunction],
489
+ "onPointerLeave": [Function: AsyncFunction],
480
490
  "style": {
481
491
  "alignItems": "center",
492
+ "display": "flex",
482
493
  "flexDirection": "row",
483
494
  "gap": 8,
484
495
  "marginBottom": 4,
496
+ "testID": "signup-screen-password-requirements-lowercase",
485
497
  },
486
498
  "testID": "signup-screen-password-requirements-lowercase",
487
499
  },
@@ -517,11 +529,15 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
517
529
  },
518
530
  ],
519
531
  "props": {
532
+ "onPointerEnter": [Function: AsyncFunction],
533
+ "onPointerLeave": [Function: AsyncFunction],
520
534
  "style": {
521
535
  "alignItems": "center",
536
+ "display": "flex",
522
537
  "flexDirection": "row",
523
538
  "gap": 8,
524
539
  "marginBottom": 4,
540
+ "testID": "signup-screen-password-requirements-number",
525
541
  },
526
542
  "testID": "signup-screen-password-requirements-number",
527
543
  },
@@ -557,11 +573,15 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
557
573
  },
558
574
  ],
559
575
  "props": {
576
+ "onPointerEnter": [Function: AsyncFunction],
577
+ "onPointerLeave": [Function: AsyncFunction],
560
578
  "style": {
561
579
  "alignItems": "center",
580
+ "display": "flex",
562
581
  "flexDirection": "row",
563
582
  "gap": 8,
564
583
  "marginBottom": 4,
584
+ "testID": "signup-screen-password-requirements-special",
565
585
  },
566
586
  "testID": "signup-screen-password-requirements-special",
567
587
  },
@@ -569,7 +589,11 @@ exports[`SignUpScreen renders correctly with all props 1`] = `
569
589
  },
570
590
  ],
571
591
  "props": {
572
- "style": undefined,
592
+ "onPointerEnter": [Function: AsyncFunction],
593
+ "onPointerLeave": [Function: AsyncFunction],
594
+ "style": {
595
+ "testID": "signup-screen-password-requirements",
596
+ },
573
597
  "testID": "signup-screen-password-requirements",
574
598
  },
575
599
  "type": "View",
@@ -1,7 +1,6 @@
1
1
  // TableHeaderCell.tsx
2
2
  import {FontAwesome6} from "@expo/vector-icons";
3
3
  import {type ReactElement, useCallback} from "react";
4
- import {View} from "react-native";
5
4
 
6
5
  import {Box} from "../Box";
7
6
  import type {AlignItems, TableHeaderCellProps} from "../Common";
@@ -69,15 +68,13 @@ export const TableHeaderCell = ({
69
68
  {Boolean(sort) && (
70
69
  <Box alignSelf="end" paddingX={2}>
71
70
  {/* Make it look like an IconButton, but we can't nest buttons and the whole row is clickable. */}
72
- <View
73
- style={{
74
- alignItems: "center",
75
- backgroundColor: theme.surface.primary,
76
- borderRadius: theme.radius.rounded,
77
- height: 16,
78
- justifyContent: "center",
79
- width: 16,
80
- }}
71
+ <Box
72
+ alignItems="center"
73
+ color="primary"
74
+ height={16}
75
+ justifyContent="center"
76
+ rounding="rounded"
77
+ width={16}
81
78
  >
82
79
  <FontAwesome6
83
80
  color={theme.text.inverted}
@@ -86,7 +83,7 @@ export const TableHeaderCell = ({
86
83
  size={10}
87
84
  solid
88
85
  />
89
- </View>
86
+ </Box>
90
87
  </Box>
91
88
  )}
92
89
  </Box>
@@ -1,5 +1,7 @@
1
- import {describe, expect, it} from "bun:test";
1
+ import {describe, expect, it, type mock} from "bun:test";
2
+ import {act} from "@testing-library/react-native";
2
3
 
4
+ import {IconButton} from "../IconButton";
3
5
  import {Text} from "../Text";
4
6
  import {renderWithTheme} from "../test-utils";
5
7
  import {Table} from "./Table";
@@ -113,4 +115,32 @@ describe("TableRow", () => {
113
115
  // Snapshot captures the blank placeholder cell for the row without drawer contents
114
116
  expect(toJSON()).toMatchSnapshot();
115
117
  });
118
+
119
+ it("toggles drawer contents when the expand button is pressed", () => {
120
+ const iconButtonMock = IconButton as unknown as ReturnType<typeof mock>;
121
+ iconButtonMock.mockClear();
122
+
123
+ const {queryByText} = renderWithTheme(
124
+ <Table columns={[100]}>
125
+ <TableHeader>
126
+ <TableHeaderCell index={0} title="Name" />
127
+ </TableHeader>
128
+ <TableRow drawerContents={<Text>Hidden content</Text>}>
129
+ <TableText value="Row" />
130
+ </TableRow>
131
+ </Table>
132
+ );
133
+
134
+ expect(queryByText("Hidden content")).toBeNull();
135
+
136
+ act(() => {
137
+ iconButtonMock.mock.calls[iconButtonMock.mock.calls.length - 1][0].onClick();
138
+ });
139
+ expect(queryByText("Hidden content")).toBeTruthy();
140
+
141
+ act(() => {
142
+ iconButtonMock.mock.calls[iconButtonMock.mock.calls.length - 1][0].onClick();
143
+ });
144
+ expect(queryByText("Hidden content")).toBeNull();
145
+ });
116
146
  });
@@ -1544,6 +1544,8 @@ exports[`TableHeaderCell renders sortable header with sort indicator when sorted
1544
1544
  "$$typeof": Symbol(react.test.json),
1545
1545
  "children": null,
1546
1546
  "props": {
1547
+ "onPointerEnter": [Function: AsyncFunction],
1548
+ "onPointerLeave": [Function: AsyncFunction],
1547
1549
  "style": {
1548
1550
  "alignItems": "center",
1549
1551
  "backgroundColor": "#0E9DCD",
@@ -45,7 +45,7 @@ export const TableContextProvider = ({
45
45
  );
46
46
  };
47
47
 
48
- export function useTableContext(): TableContextType {
48
+ export const useTableContext = (): TableContextType => {
49
49
  const {
50
50
  columns,
51
51
  hasDrawerContents,
@@ -64,4 +64,4 @@ export function useTableContext(): TableContextType {
64
64
  sortColumn,
65
65
  stickyHeader,
66
66
  };
67
- }
67
+ };
@@ -25,17 +25,22 @@ describe("useConsentHistory", () => {
25
25
  refetch,
26
26
  }));
27
27
  const api = {
28
- injectEndpoints: mock((opts: MockInjectOpts) => {
29
- const build = {
30
- query: mock((def: MockQueryDef) => {
31
- // Exercise the URL builder so the closure captures `base`
32
- const url = def.query();
33
- expect(url).toContain("/consents/my");
34
- return "my-consents-query";
28
+ enhanceEndpoints: mock((opts: {addTagTypes: string[]}) => {
29
+ expect(opts.addTagTypes).toContain("MyConsents");
30
+ return {
31
+ injectEndpoints: mock((injectOpts: MockInjectOpts) => {
32
+ const build = {
33
+ query: mock((def: MockQueryDef) => {
34
+ // Exercise the URL builder so the closure captures `base`
35
+ const url = def.query();
36
+ expect(url).toContain("/consents/my");
37
+ return "my-consents-query";
38
+ }),
39
+ };
40
+ injectOpts.endpoints(build);
41
+ return {useGetMyConsentsQuery};
35
42
  }),
36
43
  };
37
- opts.endpoints(build);
38
- return {useGetMyConsentsQuery};
39
44
  }),
40
45
  };
41
46
  return {api, refetch};
@@ -84,10 +89,12 @@ describe("useConsentHistory", () => {
84
89
  refetch,
85
90
  }));
86
91
  const api = {
87
- injectEndpoints: () => {
88
- injectCallCount += 1;
89
- return {useGetMyConsentsQuery};
90
- },
92
+ enhanceEndpoints: () => ({
93
+ injectEndpoints: () => {
94
+ injectCallCount += 1;
95
+ return {useGetMyConsentsQuery};
96
+ },
97
+ }),
91
98
  };
92
99
  const {rerender} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
93
100
  rerender(undefined);
@@ -39,13 +39,17 @@ interface ConsentHistoryEnhancedApi {
39
39
  useGetMyConsentsQuery: () => ConsentHistoryHookState;
40
40
  }
41
41
 
42
- interface ConsentHistoryApi {
42
+ interface ConsentHistoryApiWithTags {
43
43
  injectEndpoints: (options: {
44
44
  endpoints: (build: ConsentHistoryQueryBuilder) => {getMyConsents: unknown};
45
45
  overrideExisting: boolean;
46
46
  }) => ConsentHistoryEnhancedApi;
47
47
  }
48
48
 
49
+ interface ConsentHistoryApi {
50
+ enhanceEndpoints: (options: {addTagTypes: string[]}) => ConsentHistoryApiWithTags;
51
+ }
52
+
49
53
  /**
50
54
  * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
51
55
  * error in development whenever an endpoint with the same name is re-injected
@@ -65,7 +69,8 @@ const getEnhancedApi = (api: ConsentHistoryApi, base: string): ConsentHistoryEnh
65
69
  if (cached) {
66
70
  return cached;
67
71
  }
68
- const enhanced = api.injectEndpoints({
72
+ const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["MyConsents"]});
73
+ const enhanced = apiWithConsentTags.injectEndpoints({
69
74
  endpoints: (build) => ({
70
75
  getMyConsents: build.query({
71
76
  providesTags: ["MyConsents"],
@@ -140,4 +140,51 @@ describe("useStoredState", () => {
140
140
  expect(result.current[0]).toBe("initial value");
141
141
  expect(result.current[2]).toBe(true);
142
142
  });
143
+
144
+ it("should handle the outer catch when fetchData rejects unexpectedly", async () => {
145
+ const originalConsoleError = console.error;
146
+ console.error = mock((...args: unknown[]) => {
147
+ const msg = String(args[0]);
148
+ if (msg.includes("Error reading data from AsyncStorage")) {
149
+ throw new Error("unexpected failure in error handler");
150
+ }
151
+ });
152
+
153
+ getItemMock = mock(() => Promise.reject(new Error("Storage read failure")));
154
+ Unifier.storage.getItem = getItemMock;
155
+
156
+ const {result} = renderHook(() => useStoredState("testKey", "fallback"));
157
+
158
+ await act(async () => {
159
+ await new Promise((resolve) => setTimeout(resolve, 50));
160
+ });
161
+
162
+ expect(result.current[2]).toBe(false);
163
+ console.error = originalConsoleError;
164
+ });
165
+
166
+ it("should not update state in outer catch if unmounted", async () => {
167
+ const originalConsoleError = console.error;
168
+ console.error = mock((...args: unknown[]) => {
169
+ const msg = String(args[0]);
170
+ if (msg.includes("Error reading data from AsyncStorage")) {
171
+ throw new Error("force outer catch");
172
+ }
173
+ });
174
+
175
+ getItemMock = mock(() => Promise.reject(new Error("fail")));
176
+ Unifier.storage.getItem = getItemMock;
177
+
178
+ const {result, unmount} = renderHook(() => useStoredState("testKey", "init"));
179
+
180
+ unmount();
181
+
182
+ await act(async () => {
183
+ await new Promise((resolve) => setTimeout(resolve, 50));
184
+ });
185
+
186
+ expect(result.current[0]).toBe("init");
187
+ expect(result.current[2]).toBe(true);
188
+ console.error = originalConsoleError;
189
+ });
143
190
  });