@terreno/ui 0.13.0 → 0.13.3

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 (54) hide show
  1. package/dist/ConsentFormScreen.js +8 -7
  2. package/dist/ConsentFormScreen.js.map +1 -1
  3. package/dist/PickerSelect.d.ts +22 -10
  4. package/dist/PickerSelect.js +11 -9
  5. package/dist/PickerSelect.js.map +1 -1
  6. package/dist/SelectBadge.js +11 -1
  7. package/dist/SelectBadge.js.map +1 -1
  8. package/dist/SelectField.js +2 -2
  9. package/dist/SelectField.js.map +1 -1
  10. package/dist/SidebarNavigation.native.js.map +1 -1
  11. package/dist/Signature.js +4 -0
  12. package/dist/Signature.js.map +1 -1
  13. package/dist/Signature.native.js +4 -0
  14. package/dist/Signature.native.js.map +1 -1
  15. package/dist/Theme.d.ts +1 -1
  16. package/dist/Theme.js.map +1 -1
  17. package/dist/useConsentForms.d.ts +4 -3
  18. package/dist/useConsentForms.js +26 -4
  19. package/dist/useConsentForms.js.map +1 -1
  20. package/dist/useConsentHistory.d.ts +4 -3
  21. package/dist/useConsentHistory.js +26 -4
  22. package/dist/useConsentHistory.js.map +1 -1
  23. package/dist/useSubmitConsent.d.ts +7 -6
  24. package/dist/useSubmitConsent.js +25 -3
  25. package/dist/useSubmitConsent.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/ConsentFormScreen.test.tsx +47 -0
  28. package/src/ConsentFormScreen.tsx +11 -0
  29. package/src/HeightField.test.tsx +68 -0
  30. package/src/Modal.tsx +2 -2
  31. package/src/PickerSelect.tsx +48 -34
  32. package/src/SelectBadge.tsx +7 -3
  33. package/src/SelectField.tsx +2 -2
  34. package/src/SidebarNavigation.native.tsx +6 -2
  35. package/src/Signature.native.tsx +4 -0
  36. package/src/Signature.test.tsx +10 -0
  37. package/src/Signature.tsx +4 -0
  38. package/src/Theme.tsx +17 -14
  39. package/src/Tooltip.test.tsx +41 -22
  40. package/src/__snapshots__/AddressField.test.tsx.snap +0 -1
  41. package/src/__snapshots__/CustomSelectField.test.tsx.snap +0 -7
  42. package/src/__snapshots__/Field.test.tsx.snap +0 -6
  43. package/src/__snapshots__/PickerSelect.test.tsx.snap +0 -7
  44. package/src/__snapshots__/SelectField.test.tsx.snap +0 -6
  45. package/src/__snapshots__/TerrenoProvider.test.tsx.snap +4 -18
  46. package/src/__snapshots__/TimezonePicker.test.tsx.snap +0 -6
  47. package/src/bunSetup.ts +22 -0
  48. package/src/table/__snapshots__/TableBadge.test.tsx.snap +0 -1
  49. package/src/useConsentForms.test.ts +25 -0
  50. package/src/useConsentForms.ts +32 -7
  51. package/src/useConsentHistory.test.ts +99 -0
  52. package/src/useConsentHistory.ts +31 -6
  53. package/src/useSubmitConsent.test.ts +24 -0
  54. package/src/useSubmitConsent.ts +35 -10
@@ -0,0 +1,99 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {renderHook} from "@testing-library/react-native";
3
+
4
+ import type {ConsentHistoryEntry} from "./useConsentHistory";
5
+ import {useConsentHistory} from "./useConsentHistory";
6
+
7
+ type ConsentHistoryApi = Parameters<typeof useConsentHistory>[0];
8
+
9
+ interface MockQueryDef {
10
+ query: () => string;
11
+ providesTags: string[];
12
+ }
13
+
14
+ interface MockInjectOpts {
15
+ endpoints: (build: {query: (def: MockQueryDef) => string}) => Record<string, unknown>;
16
+ }
17
+
18
+ describe("useConsentHistory", () => {
19
+ const buildApi = (queryResult: {data?: unknown; error?: unknown; isLoading?: boolean}) => {
20
+ const refetch = mock(() => {});
21
+ const useGetMyConsentsQuery = mock(() => ({
22
+ data: queryResult.data,
23
+ error: queryResult.error,
24
+ isLoading: queryResult.isLoading ?? false,
25
+ refetch,
26
+ }));
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";
35
+ }),
36
+ };
37
+ opts.endpoints(build);
38
+ return {useGetMyConsentsQuery};
39
+ }),
40
+ };
41
+ return {api, refetch};
42
+ };
43
+
44
+ it("returns an array of entries when response is an array", () => {
45
+ const {api} = buildApi({
46
+ data: [{_id: "1", agreed: true} as unknown as ConsentHistoryEntry],
47
+ });
48
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
49
+ expect(Array.isArray(result.current.entries)).toBe(true);
50
+ expect(result.current.entries).toHaveLength(1);
51
+ });
52
+
53
+ it("unwraps .data property when response is object shape", () => {
54
+ const {api} = buildApi({
55
+ data: {data: [{_id: "2", agreed: false} as unknown as ConsentHistoryEntry]},
56
+ });
57
+ const {result} = renderHook(() =>
58
+ useConsentHistory(api as unknown as ConsentHistoryApi, "/api")
59
+ );
60
+ expect(Array.isArray(result.current.entries)).toBe(true);
61
+ expect(result.current.entries).toHaveLength(1);
62
+ });
63
+
64
+ it("returns empty array when no data is present", () => {
65
+ const {api} = buildApi({data: undefined, isLoading: true});
66
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
67
+ expect(result.current.entries).toEqual([]);
68
+ expect(result.current.isLoading).toBe(true);
69
+ });
70
+
71
+ it("surfaces error from the query hook", () => {
72
+ const {api} = buildApi({data: undefined, error: "boom"});
73
+ const {result} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
74
+ expect(result.current.error).toBe("boom");
75
+ });
76
+
77
+ it("injects the my-consents endpoint only once per (api, baseUrl)", () => {
78
+ let injectCallCount = 0;
79
+ const refetch = mock(() => {});
80
+ const useGetMyConsentsQuery = mock(() => ({
81
+ data: undefined,
82
+ error: undefined,
83
+ isLoading: false,
84
+ refetch,
85
+ }));
86
+ const api = {
87
+ injectEndpoints: () => {
88
+ injectCallCount += 1;
89
+ return {useGetMyConsentsQuery};
90
+ },
91
+ };
92
+ const {rerender} = renderHook(() => useConsentHistory(api as unknown as ConsentHistoryApi));
93
+ rerender(undefined);
94
+ rerender(undefined);
95
+ // The hook reuses the cached enhanced api after the first render so the
96
+ // dev-mode RTK warning about re-injecting endpoints never fires.
97
+ expect(injectCallCount).toBe(1);
98
+ });
99
+ });
@@ -35,19 +35,37 @@ interface ConsentHistoryHookState {
35
35
  refetch: () => void | Promise<void>;
36
36
  }
37
37
 
38
+ interface ConsentHistoryEnhancedApi {
39
+ useGetMyConsentsQuery: () => ConsentHistoryHookState;
40
+ }
41
+
38
42
  interface ConsentHistoryApi {
39
43
  injectEndpoints: (options: {
40
44
  endpoints: (build: ConsentHistoryQueryBuilder) => {getMyConsents: unknown};
41
45
  overrideExisting: boolean;
42
- }) => {
43
- useGetMyConsentsQuery: () => ConsentHistoryHookState;
44
- };
46
+ }) => ConsentHistoryEnhancedApi;
45
47
  }
46
48
 
47
- export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
48
- const base = baseUrl || "";
49
+ /**
50
+ * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
51
+ * error in development whenever an endpoint with the same name is re-injected
52
+ * (with `overrideExisting: false`), so calling it on every render of every
53
+ * consumer would flood the console. WeakMap-by-api lets the GC reclaim entries
54
+ * when the api object is unreachable.
55
+ */
56
+ const enhancedApiCache = new WeakMap<ConsentHistoryApi, Map<string, ConsentHistoryEnhancedApi>>();
49
57
 
50
- const enhancedApi = api.injectEndpoints({
58
+ const getEnhancedApi = (api: ConsentHistoryApi, base: string): ConsentHistoryEnhancedApi => {
59
+ let byBase = enhancedApiCache.get(api);
60
+ if (!byBase) {
61
+ byBase = new Map();
62
+ enhancedApiCache.set(api, byBase);
63
+ }
64
+ const cached = byBase.get(base);
65
+ if (cached) {
66
+ return cached;
67
+ }
68
+ const enhanced = api.injectEndpoints({
51
69
  endpoints: (build) => ({
52
70
  getMyConsents: build.query({
53
71
  providesTags: ["MyConsents"],
@@ -56,6 +74,13 @@ export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
56
74
  }),
57
75
  overrideExisting: false,
58
76
  });
77
+ byBase.set(base, enhanced);
78
+ return enhanced;
79
+ };
80
+
81
+ export const useConsentHistory = (api: ConsentHistoryApi, baseUrl?: string) => {
82
+ const base = baseUrl || "";
83
+ const enhancedApi = getEnhancedApi(api, base);
59
84
 
60
85
  const {data, isLoading, error, refetch} = enhancedApi.useGetMyConsentsQuery();
61
86
  const entries: ConsentHistoryEntry[] = Array.isArray(data) ? data : (data?.data ?? []);
@@ -72,4 +72,28 @@ describe("useSubmitConsent", () => {
72
72
  const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
73
73
  expect(result.current.submit).toBeDefined();
74
74
  });
75
+
76
+ it("injects the submit endpoint only once per (api, baseUrl)", () => {
77
+ let injectCallCount = 0;
78
+ const unwrap = mock(async () => ({}));
79
+ const submitMutation = mock(() => ({unwrap}));
80
+ const useSubmitConsentResponseMutation = mock(() => [
81
+ submitMutation,
82
+ {error: undefined, isLoading: false},
83
+ ]);
84
+ const api = {
85
+ enhanceEndpoints: () => ({
86
+ injectEndpoints: () => {
87
+ injectCallCount += 1;
88
+ return {useSubmitConsentResponseMutation};
89
+ },
90
+ }),
91
+ };
92
+ const {rerender} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
93
+ rerender(undefined);
94
+ rerender(undefined);
95
+ // The hook reuses the cached enhanced api after the first render so the
96
+ // dev-mode RTK warning about re-injecting endpoints never fires.
97
+ expect(injectCallCount).toBe(1);
98
+ });
75
99
  });
@@ -26,27 +26,45 @@ interface SubmitConsentMutationBuilder {
26
26
  }) => unknown;
27
27
  }
28
28
 
29
+ interface SubmitConsentEnhancedApi {
30
+ useSubmitConsentResponseMutation: () => [
31
+ (body: SubmitConsentBody) => SubmitConsentMutationResult,
32
+ SubmitConsentMutationHookState,
33
+ ];
34
+ }
35
+
29
36
  interface SubmitConsentApiWithTags {
30
37
  injectEndpoints: (options: {
31
38
  endpoints: (build: SubmitConsentMutationBuilder) => {submitConsentResponse: unknown};
32
39
  overrideExisting: boolean;
33
- }) => {
34
- useSubmitConsentResponseMutation: () => [
35
- (body: SubmitConsentBody) => SubmitConsentMutationResult,
36
- SubmitConsentMutationHookState,
37
- ];
38
- };
40
+ }) => SubmitConsentEnhancedApi;
39
41
  }
40
42
 
41
43
  interface SubmitConsentApi {
42
44
  enhanceEndpoints: (options: {addTagTypes: string[]}) => SubmitConsentApiWithTags;
43
45
  }
44
46
 
45
- export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
46
- const base = baseUrl || "";
47
- const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
47
+ /**
48
+ * Cache the enhanced api per (api, baseUrl). `injectEndpoints` logs a console
49
+ * error in development whenever an endpoint with the same name is re-injected
50
+ * (with `overrideExisting: false`), so calling it on every render of every
51
+ * consumer would flood the console. WeakMap-by-api lets the GC reclaim entries
52
+ * when the api object is unreachable.
53
+ */
54
+ const enhancedApiCache = new WeakMap<SubmitConsentApi, Map<string, SubmitConsentEnhancedApi>>();
48
55
 
49
- const enhancedApi = apiWithConsentTags.injectEndpoints({
56
+ const getEnhancedApi = (api: SubmitConsentApi, base: string): SubmitConsentEnhancedApi => {
57
+ let byBase = enhancedApiCache.get(api);
58
+ if (!byBase) {
59
+ byBase = new Map();
60
+ enhancedApiCache.set(api, byBase);
61
+ }
62
+ const cached = byBase.get(base);
63
+ if (cached) {
64
+ return cached;
65
+ }
66
+ const apiWithConsentTags = api.enhanceEndpoints({addTagTypes: ["PendingConsents"]});
67
+ const enhanced = apiWithConsentTags.injectEndpoints({
50
68
  endpoints: (build) => ({
51
69
  submitConsentResponse: build.mutation({
52
70
  invalidatesTags: ["PendingConsents"],
@@ -59,6 +77,13 @@ export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
59
77
  }),
60
78
  overrideExisting: false,
61
79
  });
80
+ byBase.set(base, enhanced);
81
+ return enhanced;
82
+ };
83
+
84
+ export const useSubmitConsent = (api: SubmitConsentApi, baseUrl?: string) => {
85
+ const base = baseUrl || "";
86
+ const enhancedApi = getEnhancedApi(api, base);
62
87
 
63
88
  const [submitMutation, {isLoading: isSubmitting, error}] =
64
89
  enhancedApi.useSubmitConsentResponseMutation();