@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.
Files changed (94) hide show
  1. package/dist/Banner.js +2 -2
  2. package/dist/Banner.js.map +1 -1
  3. package/dist/Common.d.ts +6 -10
  4. package/dist/Common.js.map +1 -1
  5. package/dist/ConsentHistory.d.ts +2 -1
  6. package/dist/DataTable.js +4 -2
  7. package/dist/DataTable.js.map +1 -1
  8. package/dist/DateUtilities.js +16 -7
  9. package/dist/DateUtilities.js.map +1 -1
  10. package/dist/DraggableList.d.ts +5 -4
  11. package/dist/DraggableList.js.map +1 -1
  12. package/dist/Icon.js +0 -3
  13. package/dist/Icon.js.map +1 -1
  14. package/dist/PasswordField.d.ts +1 -1
  15. package/dist/PasswordField.js.map +1 -1
  16. package/dist/TextFieldNumberActionSheet.d.ts +1 -1
  17. package/dist/Toast.d.ts +1 -1
  18. package/dist/Toast.js +2 -2
  19. package/dist/Toast.js.map +1 -1
  20. package/dist/index.d.ts +2 -2
  21. package/package.json +2 -1
  22. package/src/ActionSheet.test.tsx +262 -3
  23. package/src/AddressField.test.tsx +50 -0
  24. package/src/Banner.test.tsx +22 -0
  25. package/src/Banner.tsx +2 -2
  26. package/src/Box.test.tsx +218 -0
  27. package/src/Button.test.tsx +71 -0
  28. package/src/Common.ts +11 -9
  29. package/src/ConsentFormScreen.test.tsx +167 -0
  30. package/src/ConsentHistory.tsx +1 -1
  31. package/src/ConsentNavigator.test.tsx +210 -4
  32. package/src/DataTable.tsx +15 -15
  33. package/src/DateUtilities.test.ts +34 -16
  34. package/src/DateUtilities.tsx +24 -13
  35. package/src/DecimalRangeActionSheet.test.tsx +53 -2
  36. package/src/DraggableList.tsx +5 -5
  37. package/src/EmailField.test.tsx +81 -0
  38. package/src/EmojiSelector.test.tsx +262 -1
  39. package/src/ErrorBoundary.test.tsx +52 -1
  40. package/src/HeightActionSheet.test.tsx +57 -2
  41. package/src/Icon.tsx +0 -3
  42. package/src/InfoModalIcon.test.tsx +16 -0
  43. package/src/InfoTooltipButton.test.tsx +53 -1
  44. package/src/MobileAddressAutoComplete.test.tsx +137 -7
  45. package/src/Modal.test.tsx +188 -0
  46. package/src/NumberPickerActionSheet.test.tsx +59 -2
  47. package/src/OpenAPIContext.test.tsx +184 -3
  48. package/src/Page.test.tsx +162 -1
  49. package/src/Pagination.test.tsx +16 -0
  50. package/src/PasswordField.tsx +1 -1
  51. package/src/PhoneNumberField.test.tsx +46 -9
  52. package/src/PickerSelect.test.tsx +230 -0
  53. package/src/SegmentedControl.test.tsx +38 -0
  54. package/src/SelectBadge.test.tsx +52 -1
  55. package/src/SideDrawer.test.tsx +69 -0
  56. package/src/Signature.test.tsx +42 -5
  57. package/src/SignatureField.test.tsx +35 -0
  58. package/src/Slider.test.tsx +59 -0
  59. package/src/Spinner.test.tsx +6 -0
  60. package/src/SplitPage.test.tsx +228 -2
  61. package/src/TapToEdit.test.tsx +171 -1
  62. package/src/TerrenoProvider.test.tsx +42 -2
  63. package/src/TextFieldNumberActionSheet.tsx +1 -1
  64. package/src/Theme.test.tsx +118 -28
  65. package/src/Toast.test.tsx +95 -2
  66. package/src/Toast.tsx +3 -3
  67. package/src/Tooltip.test.tsx +204 -1
  68. package/src/UnifiedAddressAutoComplete.test.tsx +38 -19
  69. package/src/UserInactivity.test.tsx +73 -1
  70. package/src/Utilities.test.tsx +190 -2
  71. package/src/WebAddressAutocomplete.test.tsx +148 -1
  72. package/src/__snapshots__/ActionSheet.test.tsx.snap +1736 -0
  73. package/src/__snapshots__/Button.test.tsx.snap +68 -0
  74. package/src/__snapshots__/EmojiSelector.test.tsx.snap +1363 -0
  75. package/src/__snapshots__/InfoTooltipButton.test.tsx.snap +72 -3
  76. package/src/__snapshots__/MobileAddressAutoComplete.test.tsx.snap +60 -9
  77. package/src/__snapshots__/Modal.test.tsx.snap +181 -0
  78. package/src/__snapshots__/Page.test.tsx.snap +48 -2
  79. package/src/__snapshots__/PhoneNumberField.test.tsx.snap +0 -93
  80. package/src/__snapshots__/PickerSelect.test.tsx.snap +706 -0
  81. package/src/__snapshots__/SideDrawer.test.tsx.snap +533 -1399
  82. package/src/__snapshots__/Signature.test.tsx.snap +0 -3
  83. package/src/__snapshots__/SplitPage.test.tsx.snap +970 -0
  84. package/src/__snapshots__/UnifiedAddressAutoComplete.test.tsx.snap +220 -4
  85. package/src/__snapshots__/WebAddressAutocomplete.test.tsx.snap +93 -0
  86. package/src/bunSetup.ts +204 -121
  87. package/src/index.tsx +2 -2
  88. package/src/table/TableHeaderCell.test.tsx +142 -0
  89. package/src/table/TableRow.test.tsx +33 -0
  90. package/src/table/__snapshots__/TableRow.test.tsx.snap +403 -0
  91. package/src/table/tableContext.test.tsx +96 -0
  92. package/src/test-utils.tsx +1 -1
  93. package/src/useConsentForms.test.ts +130 -0
  94. package/src/useSubmitConsent.test.ts +64 -0
@@ -0,0 +1,96 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {render} from "@testing-library/react-native";
3
+ import {useEffect} from "react";
4
+ import {Text} from "react-native";
5
+
6
+ import type {TableContextType} from "../Common";
7
+ import {renderWithTheme} from "../test-utils";
8
+ import {TableContextProvider, useTableContext} from "./tableContext";
9
+
10
+ const captureContext = (): {
11
+ getContext: () => TableContextType;
12
+ Consumer: () => React.ReactElement;
13
+ } => {
14
+ let captured: TableContextType | undefined;
15
+ const Consumer = () => {
16
+ captured = useTableContext();
17
+ return <Text>ok</Text>;
18
+ };
19
+ const getContext = (): TableContextType => {
20
+ if (!captured) {
21
+ throw new Error("Context was not captured");
22
+ }
23
+ return captured;
24
+ };
25
+ return {Consumer, getContext};
26
+ };
27
+
28
+ describe("tableContext", () => {
29
+ it("exposes default values from useTableContext when no provider is present", () => {
30
+ const {getContext, Consumer} = captureContext();
31
+ renderWithTheme(<Consumer />);
32
+ const ctx = getContext();
33
+ expect(ctx.columns).toEqual([]);
34
+ expect(ctx.stickyHeader).toBe(true);
35
+ expect(ctx.alternateRowBackground).toBe(true);
36
+ expect(ctx.borderStyle).toBe("sm");
37
+ expect(ctx.hasDrawerContents).toBe(false);
38
+ expect(typeof ctx.setSortColumn).toBe("function");
39
+ // Invoke default setSortColumn to cover the no-op default
40
+ expect(() => ctx.setSortColumn({column: 0, direction: "asc"})).not.toThrow();
41
+ });
42
+
43
+ it("passes provided values through the provider", () => {
44
+ const setSortColumn = mock(() => {});
45
+ const {getContext, Consumer} = captureContext();
46
+ render(
47
+ <TableContextProvider
48
+ alternateRowBackground={false}
49
+ borderStyle="lg"
50
+ columns={[100, 200]}
51
+ hasDrawerContents
52
+ page={3}
53
+ setSortColumn={setSortColumn}
54
+ sortColumn={{column: 1, direction: "desc"}}
55
+ stickyHeader={false}
56
+ >
57
+ <Consumer />
58
+ </TableContextProvider>
59
+ );
60
+ const ctx = getContext();
61
+ expect(ctx.columns).toEqual([100, 200]);
62
+ expect(ctx.hasDrawerContents).toBe(true);
63
+ expect(ctx.stickyHeader).toBe(false);
64
+ expect(ctx.alternateRowBackground).toBe(false);
65
+ expect(ctx.borderStyle).toBe("lg");
66
+ expect(ctx.sortColumn).toEqual({column: 1, direction: "desc"});
67
+ ctx.setSortColumn({column: 0, direction: "asc"});
68
+ expect(setSortColumn).toHaveBeenCalled();
69
+ });
70
+
71
+ it("useEffect-based consumer can call setSortColumn without errors", () => {
72
+ const setSortColumn = mock(() => {});
73
+ const Consumer = () => {
74
+ const {setSortColumn: setter} = useTableContext();
75
+ useEffect(() => {
76
+ setter({column: 2, direction: "desc"});
77
+ }, [setter]);
78
+ return <Text>ok</Text>;
79
+ };
80
+ render(
81
+ <TableContextProvider
82
+ alternateRowBackground
83
+ borderStyle="sm"
84
+ columns={[100]}
85
+ hasDrawerContents={false}
86
+ page={1}
87
+ setSortColumn={setSortColumn}
88
+ sortColumn={undefined}
89
+ stickyHeader
90
+ >
91
+ <Consumer />
92
+ </TableContextProvider>
93
+ );
94
+ expect(setSortColumn).toHaveBeenCalledWith({column: 2, direction: "desc"});
95
+ });
96
+ });
@@ -5,7 +5,7 @@ import type React from "react";
5
5
  import {ThemeProvider} from "./Theme";
6
6
 
7
7
  export const renderWithTheme = (ui: React.ReactElement) => {
8
- return render(<ThemeProvider>{ui}</ThemeProvider>);
8
+ return render(ui, {wrapper: ThemeProvider});
9
9
  };
10
10
 
11
11
  export const createCommonMocks = () => ({
@@ -0,0 +1,130 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {renderHook} from "@testing-library/react-native";
3
+
4
+ import {detectLocale, useConsentForms} from "./useConsentForms";
5
+
6
+ mock.module("expo-localization", () => ({
7
+ getLocales: () => [{languageTag: "es-ES"}],
8
+ }));
9
+
10
+ describe("detectLocale", () => {
11
+ it("returns locale from expo-localization in native test env", () => {
12
+ const originalNavigator = (globalThis as any).navigator;
13
+ (globalThis as any).navigator = undefined;
14
+ const locale = detectLocale();
15
+ (globalThis as any).navigator = originalNavigator;
16
+ expect(typeof locale).toBe("string");
17
+ expect(locale.length).toBeGreaterThan(0);
18
+ });
19
+
20
+ it("falls back to en when expo-localization throws and no navigator", () => {
21
+ const originalNavigator = (globalThis as any).navigator;
22
+ (globalThis as any).navigator = undefined;
23
+ mock.module("expo-localization", () => ({
24
+ getLocales: () => {
25
+ throw new Error("not available");
26
+ },
27
+ }));
28
+ const locale = detectLocale();
29
+ (globalThis as any).navigator = originalNavigator;
30
+ // Reset mock
31
+ mock.module("expo-localization", () => ({
32
+ getLocales: () => [{languageTag: "es-ES"}],
33
+ }));
34
+ expect(locale).toBe("en");
35
+ });
36
+
37
+ it("returns navigator.language when available", () => {
38
+ const originalNavigator = (globalThis as any).navigator;
39
+ (globalThis as any).navigator = {language: "fr-FR"};
40
+ const locale = detectLocale();
41
+ (globalThis as any).navigator = originalNavigator;
42
+ expect(locale).toBe("fr-FR");
43
+ });
44
+ });
45
+
46
+ describe("useConsentForms", () => {
47
+ const buildApi = (queryResult: {data?: unknown; error?: unknown; isLoading?: boolean}) => {
48
+ const refetch = mock(() => {});
49
+ const useGetPendingConsentsQuery = mock(() => ({
50
+ data: queryResult.data,
51
+ error: queryResult.error,
52
+ isLoading: queryResult.isLoading ?? false,
53
+ refetch,
54
+ }));
55
+ const api = {
56
+ enhanceEndpoints: mock(() => ({
57
+ injectEndpoints: mock((opts: any) => {
58
+ // Call endpoint builder to exercise provides/onQueryStarted
59
+ const build = {
60
+ query: mock((def: any) => {
61
+ // Call onQueryStarted with a successful result
62
+ if (def.onQueryStarted) {
63
+ void def.onQueryStarted(undefined, {
64
+ queryFulfilled: Promise.resolve({data: [{id: "1"}]}),
65
+ });
66
+ }
67
+ // Also exercise the URL builder
68
+ def.query();
69
+ return "pending-query";
70
+ }),
71
+ };
72
+ opts.endpoints(build);
73
+ return {useGetPendingConsentsQuery};
74
+ }),
75
+ })),
76
+ };
77
+ return {api, refetch};
78
+ };
79
+
80
+ it("returns an array of forms when response is an array", () => {
81
+ const {api} = buildApi({data: [{id: "1", title: "Form 1"} as any]});
82
+ const {result} = renderHook(() => useConsentForms(api as any));
83
+ expect(result.current.forms).toBeDefined();
84
+ expect(Array.isArray(result.current.forms)).toBe(true);
85
+ });
86
+
87
+ it("unwraps .data property when response is object shape", () => {
88
+ const {api} = buildApi({data: {data: [{id: "2"} as any]}});
89
+ const {result} = renderHook(() => useConsentForms(api as any, "/api"));
90
+ expect(Array.isArray(result.current.forms)).toBe(true);
91
+ });
92
+
93
+ it("returns empty array when no data is present", () => {
94
+ const {api} = buildApi({data: undefined, isLoading: true});
95
+ const {result} = renderHook(() => useConsentForms(api as any));
96
+ expect(result.current.forms).toEqual([]);
97
+ expect(result.current.isLoading).toBe(true);
98
+ });
99
+
100
+ it("calls onQueryStarted with failing query", () => {
101
+ const refetch = mock(() => {});
102
+ const api = {
103
+ enhanceEndpoints: mock(() => ({
104
+ injectEndpoints: mock((opts: any) => {
105
+ const build = {
106
+ query: mock((def: any) => {
107
+ if (def.onQueryStarted) {
108
+ void def.onQueryStarted(undefined, {
109
+ queryFulfilled: Promise.reject(new Error("failed")),
110
+ });
111
+ }
112
+ return "q";
113
+ }),
114
+ };
115
+ opts.endpoints(build);
116
+ return {
117
+ useGetPendingConsentsQuery: () => ({
118
+ data: undefined,
119
+ error: "error",
120
+ isLoading: false,
121
+ refetch,
122
+ }),
123
+ };
124
+ }),
125
+ })),
126
+ };
127
+ const {result} = renderHook(() => useConsentForms(api as any));
128
+ expect(result.current.error).toBe("error");
129
+ });
130
+ });
@@ -0,0 +1,64 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {renderHook} from "@testing-library/react-native";
3
+
4
+ import {useSubmitConsent} from "./useSubmitConsent";
5
+
6
+ describe("useSubmitConsent", () => {
7
+ const buildApi = () => {
8
+ const unwrap = mock(async () => ({success: true}));
9
+ const submitMutation = mock(() => ({unwrap}));
10
+ const useSubmitConsentResponseMutation = mock(() => [
11
+ submitMutation,
12
+ {error: undefined, isLoading: false},
13
+ ]);
14
+ const api = {
15
+ enhanceEndpoints: mock(() => ({
16
+ injectEndpoints: mock((opts: any) => {
17
+ const build = {
18
+ mutation: mock((def: any) => {
19
+ // Exercise the query builder
20
+ const result = def.query({
21
+ agreed: true,
22
+ consentFormId: "f1",
23
+ locale: "en",
24
+ });
25
+ expect(result.method).toBe("POST");
26
+ expect(result.url).toContain("/consents/respond");
27
+ return "submit-mutation";
28
+ }),
29
+ };
30
+ opts.endpoints(build);
31
+ return {useSubmitConsentResponseMutation};
32
+ }),
33
+ })),
34
+ };
35
+ return {api, submitMutation, unwrap};
36
+ };
37
+
38
+ it("returns submit function and state", () => {
39
+ const {api} = buildApi();
40
+ const {result} = renderHook(() => useSubmitConsent(api as any));
41
+ expect(typeof result.current.submit).toBe("function");
42
+ expect(result.current.isSubmitting).toBe(false);
43
+ expect(result.current.error).toBeUndefined();
44
+ });
45
+
46
+ it("calls submit mutation when submit is invoked", async () => {
47
+ const {api, submitMutation, unwrap} = buildApi();
48
+ const {result} = renderHook(() => useSubmitConsent(api as any, "/api"));
49
+ const response = await result.current.submit({
50
+ agreed: true,
51
+ consentFormId: "f1",
52
+ locale: "en",
53
+ });
54
+ expect(submitMutation).toHaveBeenCalled();
55
+ expect(unwrap).toHaveBeenCalled();
56
+ expect(response).toEqual({success: true});
57
+ });
58
+
59
+ it("uses empty baseUrl when none provided", () => {
60
+ const {api} = buildApi();
61
+ const {result} = renderHook(() => useSubmitConsent(api as any));
62
+ expect(result.current.submit).toBeDefined();
63
+ });
64
+ });