@terreno/ui 0.11.1 → 0.11.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.
@@ -0,0 +1,180 @@
1
+ import {describe, expect, it, mock} from "bun:test";
2
+ import {act, fireEvent, renderHook} from "@testing-library/react-native";
3
+
4
+ import {renderWithTheme} from "./test-utils";
5
+ import {useWebDropdownAnchor, WebDropdownMenu} from "./WebDropdownMenu";
6
+
7
+ describe("WebDropdownMenu", () => {
8
+ const anchor = {height: 40, width: 200, x: 16, y: 32};
9
+ const options = [
10
+ {label: "Option A", value: "a"},
11
+ {label: "Option B", value: "b"},
12
+ {label: "Option C", value: "c"},
13
+ ];
14
+
15
+ it("marks the underlying Modal hidden when not visible", () => {
16
+ const {getByTestId} = renderWithTheme(
17
+ <WebDropdownMenu
18
+ anchor={anchor}
19
+ onClose={() => {}}
20
+ onSelect={() => {}}
21
+ options={options}
22
+ visible={false}
23
+ />
24
+ );
25
+ expect(getByTestId("web_dropdown_modal").props.visible).toBe(false);
26
+ });
27
+
28
+ it("marks the underlying Modal visible when open", () => {
29
+ const {getByTestId} = renderWithTheme(
30
+ <WebDropdownMenu
31
+ anchor={anchor}
32
+ onClose={() => {}}
33
+ onSelect={() => {}}
34
+ options={options}
35
+ visible
36
+ />
37
+ );
38
+ expect(getByTestId("web_dropdown_modal").props.visible).toBe(true);
39
+ });
40
+
41
+ it("renders every option when visible", () => {
42
+ const {getByText} = renderWithTheme(
43
+ <WebDropdownMenu
44
+ anchor={anchor}
45
+ onClose={() => {}}
46
+ onSelect={() => {}}
47
+ options={options}
48
+ selectedValue="b"
49
+ visible
50
+ />
51
+ );
52
+ expect(getByText("Option A")).toBeTruthy();
53
+ expect(getByText("Option B")).toBeTruthy();
54
+ expect(getByText("Option C")).toBeTruthy();
55
+ });
56
+
57
+ it("invokes onSelect with value and index when an option is pressed", () => {
58
+ const onSelect = mock(() => {});
59
+ const {getByTestId} = renderWithTheme(
60
+ <WebDropdownMenu
61
+ anchor={anchor}
62
+ onClose={() => {}}
63
+ onSelect={onSelect}
64
+ options={options}
65
+ visible
66
+ />
67
+ );
68
+ fireEvent.press(getByTestId("web_dropdown_option_b"));
69
+ expect(onSelect).toHaveBeenCalledTimes(1);
70
+ expect(onSelect.mock.calls[0]).toEqual(["b", 1]);
71
+ });
72
+
73
+ it("invokes onClose when the backdrop is pressed", () => {
74
+ const onClose = mock(() => {});
75
+ const {getByTestId} = renderWithTheme(
76
+ <WebDropdownMenu
77
+ anchor={anchor}
78
+ onClose={onClose}
79
+ onSelect={() => {}}
80
+ options={options}
81
+ visible
82
+ />
83
+ );
84
+ fireEvent.press(getByTestId("web_dropdown_backdrop"));
85
+ expect(onClose).toHaveBeenCalledTimes(1);
86
+ });
87
+
88
+ it("anchors the menu below the trigger using the provided anchor", () => {
89
+ const {getByTestId} = renderWithTheme(
90
+ <WebDropdownMenu
91
+ anchor={anchor}
92
+ onClose={() => {}}
93
+ onSelect={() => {}}
94
+ options={options}
95
+ visible
96
+ />
97
+ );
98
+ const menu = getByTestId("web_dropdown_menu");
99
+ const style = Array.isArray(menu.props.style)
100
+ ? Object.assign({}, ...menu.props.style)
101
+ : menu.props.style;
102
+ expect(style.left).toBe(anchor.x);
103
+ expect(style.top).toBe(anchor.y + anchor.height + 4);
104
+ expect(style.width).toBe(anchor.width);
105
+ });
106
+
107
+ it("uses the custom testID prefix when provided", () => {
108
+ const {getByTestId, queryByTestId} = renderWithTheme(
109
+ <WebDropdownMenu
110
+ anchor={anchor}
111
+ onClose={() => {}}
112
+ onSelect={() => {}}
113
+ options={options}
114
+ testIDPrefix="badge_menu"
115
+ visible
116
+ />
117
+ );
118
+ expect(getByTestId("badge_menu_menu")).toBeTruthy();
119
+ expect(getByTestId("badge_menu_backdrop")).toBeTruthy();
120
+ expect(getByTestId("badge_menu_option_a")).toBeTruthy();
121
+ expect(queryByTestId("web_dropdown_menu")).toBeNull();
122
+ });
123
+
124
+ it("highlights the option matched by selectedIndex regardless of duplicated values", () => {
125
+ const dupOptions = [
126
+ {label: "Placeholder", value: ""},
127
+ {label: "Blank option", value: ""},
128
+ {label: "Real", value: "real"},
129
+ ];
130
+ const {getByText} = renderWithTheme(
131
+ <WebDropdownMenu
132
+ anchor={anchor}
133
+ onClose={() => {}}
134
+ onSelect={() => {}}
135
+ options={dupOptions}
136
+ selectedIndex={1}
137
+ visible
138
+ />
139
+ );
140
+ expect(getByText("Blank option").props.style.fontWeight).toBe("600");
141
+ expect(getByText("Placeholder").props.style.fontWeight).toBe("400");
142
+ expect(getByText("Real").props.style.fontWeight).toBe("400");
143
+ });
144
+ });
145
+
146
+ describe("useWebDropdownAnchor", () => {
147
+ it("exposes a default zero-sized anchor before measuring", () => {
148
+ const {result} = renderHook(() => useWebDropdownAnchor());
149
+ expect(result.current.anchor).toEqual({height: 0, width: 0, x: 0, y: 0});
150
+ expect(result.current.triggerRef.current).toBeNull();
151
+ });
152
+
153
+ it("invokes the callback synchronously with the existing anchor when the ref is empty", () => {
154
+ const {result} = renderHook(() => useWebDropdownAnchor());
155
+ const onMeasured = mock(() => {});
156
+ act(() => {
157
+ result.current.measure(onMeasured);
158
+ });
159
+ expect(onMeasured).toHaveBeenCalledTimes(1);
160
+ expect(onMeasured.mock.calls[0][0]).toEqual({height: 0, width: 0, x: 0, y: 0});
161
+ });
162
+
163
+ it("measures the trigger and updates anchor state when the ref has measureInWindow", () => {
164
+ const {result} = renderHook(() => useWebDropdownAnchor());
165
+ // Simulate a mounted native View by assigning a measureInWindow shim to the
166
+ // ref. The hook does not care whether the node is a real View instance.
167
+ const measureInWindow = mock((cb: (x: number, y: number, w: number, h: number) => void) => {
168
+ cb(10, 20, 100, 40);
169
+ });
170
+ (result.current.triggerRef as {current: unknown}).current = {measureInWindow};
171
+ const onMeasured = mock(() => {});
172
+ act(() => {
173
+ result.current.measure(onMeasured);
174
+ });
175
+ expect(measureInWindow).toHaveBeenCalledTimes(1);
176
+ expect(onMeasured).toHaveBeenCalledTimes(1);
177
+ expect(onMeasured.mock.calls[0][0]).toEqual({height: 40, width: 100, x: 10, y: 20});
178
+ expect(result.current.anchor).toEqual({height: 40, width: 100, x: 10, y: 20});
179
+ });
180
+ });
@@ -0,0 +1,183 @@
1
+ import {type ReactElement, useRef, useState} from "react";
2
+ import {
3
+ type DimensionValue,
4
+ Modal,
5
+ Pressable,
6
+ ScrollView,
7
+ Text,
8
+ type TextStyle,
9
+ View,
10
+ } from "react-native";
11
+
12
+ import {useTheme} from "./Theme";
13
+
14
+ export interface WebDropdownMenuOption {
15
+ key?: string | number;
16
+ label: string;
17
+ value: string;
18
+ color?: string;
19
+ }
20
+
21
+ export interface WebDropdownAnchor {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ }
27
+
28
+ export interface WebDropdownMenuProps {
29
+ /** Controls visibility of the popup. */
30
+ visible: boolean;
31
+ /** Position of the trigger element so the menu can be anchored beneath it. */
32
+ anchor: WebDropdownAnchor;
33
+ /** Options to render in the list. */
34
+ options: WebDropdownMenuOption[];
35
+ /** Currently selected value (used to highlight the matching option). */
36
+ selectedValue?: string;
37
+ /**
38
+ * Optional index of the currently selected option. When provided, takes
39
+ * precedence over `selectedValue` — useful when option values aren't
40
+ * unique (e.g. a placeholder with an empty value sharing the same string
41
+ * representation as another option).
42
+ */
43
+ selectedIndex?: number;
44
+ /** Called when an option is chosen. */
45
+ onSelect: (value: string, index: number) => void;
46
+ /** Called when the backdrop is pressed or Escape is hit. */
47
+ onClose: () => void;
48
+ /** Optional fixed width for the menu. Defaults to the trigger width. */
49
+ width?: DimensionValue;
50
+ /** Optional minimum width for the menu. */
51
+ minWidth?: DimensionValue;
52
+ /** Additional style applied to each option's label. */
53
+ optionTextStyle?: TextStyle;
54
+ /** Prefix for the testIDs on the menu / backdrop / option nodes. */
55
+ testIDPrefix?: string;
56
+ }
57
+
58
+ interface PressableWebState {
59
+ pressed: boolean;
60
+ hovered?: boolean;
61
+ focused?: boolean;
62
+ }
63
+
64
+ /**
65
+ * Shared web-only popup used by `RNPickerSelect` and `SelectBadge` so every
66
+ * browser renders the same styled dropdown instead of falling back to the
67
+ * platform-native `<select>` UI. Must be anchored to a trigger element via
68
+ * `useWebDropdownAnchor` (or an equivalent measurement).
69
+ */
70
+ export const WebDropdownMenu = ({
71
+ visible,
72
+ anchor,
73
+ options,
74
+ selectedValue,
75
+ selectedIndex,
76
+ onSelect,
77
+ onClose,
78
+ width,
79
+ minWidth,
80
+ optionTextStyle,
81
+ testIDPrefix = "web_dropdown",
82
+ }: WebDropdownMenuProps): ReactElement => {
83
+ const {theme} = useTheme();
84
+
85
+ return (
86
+ <Modal
87
+ animationType="none"
88
+ onRequestClose={onClose}
89
+ testID={`${testIDPrefix}_modal`}
90
+ transparent
91
+ visible={visible}
92
+ >
93
+ <Pressable
94
+ aria-role="button"
95
+ onPress={onClose}
96
+ style={{flex: 1}}
97
+ testID={`${testIDPrefix}_backdrop`}
98
+ />
99
+ <View
100
+ style={{
101
+ backgroundColor: theme.surface.base,
102
+ borderColor: theme.border.dark,
103
+ borderRadius: 4,
104
+ borderWidth: 1,
105
+ left: anchor.x,
106
+ maxHeight: 300,
107
+ minWidth,
108
+ overflow: "hidden",
109
+ position: "absolute",
110
+ shadowColor: "#000",
111
+ shadowOffset: {height: 2, width: 0},
112
+ shadowOpacity: 0.15,
113
+ shadowRadius: 8,
114
+ top: anchor.y + anchor.height + 4,
115
+ width: width ?? anchor.width,
116
+ }}
117
+ testID={`${testIDPrefix}_menu`}
118
+ >
119
+ <ScrollView>
120
+ {options.map((item, idx) => {
121
+ const isSelected =
122
+ selectedIndex !== undefined ? idx === selectedIndex : item.value === selectedValue;
123
+ return (
124
+ <Pressable
125
+ aria-role="button"
126
+ key={item.key ?? idx}
127
+ onPress={() => onSelect(item.value, idx)}
128
+ style={(state: PressableWebState) => ({
129
+ backgroundColor:
130
+ isSelected || state.hovered || state.pressed
131
+ ? theme.surface.neutralLight
132
+ : theme.surface.base,
133
+ paddingHorizontal: 12,
134
+ paddingVertical: 10,
135
+ })}
136
+ testID={`${testIDPrefix}_option_${item.value}`}
137
+ >
138
+ <Text
139
+ style={{
140
+ color: item.color ?? theme.text.primary,
141
+ fontWeight: isSelected ? "600" : "400",
142
+ ...optionTextStyle,
143
+ }}
144
+ >
145
+ {item.label}
146
+ </Text>
147
+ </Pressable>
148
+ );
149
+ })}
150
+ </ScrollView>
151
+ </View>
152
+ </Modal>
153
+ );
154
+ };
155
+
156
+ /**
157
+ * Hook that wires up a `View` ref + anchor state for use with
158
+ * `WebDropdownMenu`. Measure the trigger via `measure()` before opening so
159
+ * the menu lines up beneath it across browsers.
160
+ */
161
+ export const useWebDropdownAnchor = (): {
162
+ triggerRef: React.RefObject<View | null>;
163
+ anchor: WebDropdownAnchor;
164
+ measure: (onMeasured: (anchor: WebDropdownAnchor) => void) => void;
165
+ } => {
166
+ const triggerRef = useRef<View>(null);
167
+ const [anchor, setAnchor] = useState<WebDropdownAnchor>({height: 0, width: 0, x: 0, y: 0});
168
+
169
+ const measure = (onMeasured: (next: WebDropdownAnchor) => void): void => {
170
+ const node = triggerRef.current;
171
+ if (node && typeof node.measureInWindow === "function") {
172
+ node.measureInWindow((x, y, w, h) => {
173
+ const next = {height: h, width: w, x, y};
174
+ setAnchor(next);
175
+ onMeasured(next);
176
+ });
177
+ return;
178
+ }
179
+ onMeasured(anchor);
180
+ };
181
+
182
+ return {anchor, measure, triggerRef};
183
+ };
@@ -254,6 +254,9 @@ exports[`SelectBadge renders correctly with default props 1`] = `
254
254
  },
255
255
  ],
256
256
  "props": {
257
+ "ref": {
258
+ "current": null,
259
+ },
257
260
  "style": {
258
261
  "alignItems": "flex-start",
259
262
  "opacity": 1,
@@ -518,6 +521,9 @@ exports[`SelectBadge renders with info status (default) 1`] = `
518
521
  },
519
522
  ],
520
523
  "props": {
524
+ "ref": {
525
+ "current": null,
526
+ },
521
527
  "style": {
522
528
  "alignItems": "flex-start",
523
529
  "opacity": 1,
@@ -782,6 +788,9 @@ exports[`SelectBadge renders with success status 1`] = `
782
788
  },
783
789
  ],
784
790
  "props": {
791
+ "ref": {
792
+ "current": null,
793
+ },
785
794
  "style": {
786
795
  "alignItems": "flex-start",
787
796
  "opacity": 1,
@@ -1046,6 +1055,9 @@ exports[`SelectBadge renders with warning status 1`] = `
1046
1055
  },
1047
1056
  ],
1048
1057
  "props": {
1058
+ "ref": {
1059
+ "current": null,
1060
+ },
1049
1061
  "style": {
1050
1062
  "alignItems": "flex-start",
1051
1063
  "opacity": 1,
@@ -1310,6 +1322,9 @@ exports[`SelectBadge renders with error status 1`] = `
1310
1322
  },
1311
1323
  ],
1312
1324
  "props": {
1325
+ "ref": {
1326
+ "current": null,
1327
+ },
1313
1328
  "style": {
1314
1329
  "alignItems": "flex-start",
1315
1330
  "opacity": 1,
@@ -1574,6 +1589,9 @@ exports[`SelectBadge renders with neutral status 1`] = `
1574
1589
  },
1575
1590
  ],
1576
1591
  "props": {
1592
+ "ref": {
1593
+ "current": null,
1594
+ },
1577
1595
  "style": {
1578
1596
  "alignItems": "flex-start",
1579
1597
  "opacity": 1,
@@ -1838,6 +1856,9 @@ exports[`SelectBadge renders with secondary style 1`] = `
1838
1856
  },
1839
1857
  ],
1840
1858
  "props": {
1859
+ "ref": {
1860
+ "current": null,
1861
+ },
1841
1862
  "style": {
1842
1863
  "alignItems": "flex-start",
1843
1864
  "opacity": 1,
@@ -2102,6 +2123,9 @@ exports[`SelectBadge renders with custom colors 1`] = `
2102
2123
  },
2103
2124
  ],
2104
2125
  "props": {
2126
+ "ref": {
2127
+ "current": null,
2128
+ },
2105
2129
  "style": {
2106
2130
  "alignItems": "flex-start",
2107
2131
  "opacity": 1,
@@ -2366,6 +2390,9 @@ exports[`SelectBadge renders disabled state 1`] = `
2366
2390
  },
2367
2391
  ],
2368
2392
  "props": {
2393
+ "ref": {
2394
+ "current": null,
2395
+ },
2369
2396
  "style": {
2370
2397
  "alignItems": "flex-start",
2371
2398
  "opacity": 0.5,
@@ -1,32 +1,53 @@
1
1
  import {describe, expect, it, mock} from "bun:test";
2
2
  import {renderHook} from "@testing-library/react-native";
3
3
 
4
+ import type {ConsentFormPublic} from "./useConsentForms";
4
5
  import {detectLocale, useConsentForms} from "./useConsentForms";
5
6
 
7
+ type ConsentFormsApi = Parameters<typeof useConsentForms>[0];
8
+
9
+ interface GlobalThisWithNavigator {
10
+ navigator: {language: string} | undefined;
11
+ }
12
+
13
+ interface MockQueryDef {
14
+ onQueryStarted?: (
15
+ _arg: unknown,
16
+ helpers: {queryFulfilled: Promise<{data: ConsentFormPublic[]}> | Promise<never>}
17
+ ) => Promise<void>;
18
+ query: () => string;
19
+ }
20
+
21
+ interface MockInjectOpts {
22
+ endpoints: (build: {query: (def: MockQueryDef) => string}) => Record<string, unknown>;
23
+ }
24
+
6
25
  mock.module("expo-localization", () => ({
7
26
  getLocales: () => [{languageTag: "es-ES"}],
8
27
  }));
9
28
 
10
29
  describe("detectLocale", () => {
11
30
  it("returns locale from expo-localization in native test env", () => {
12
- const originalNavigator = (globalThis as any).navigator;
13
- (globalThis as any).navigator = undefined;
31
+ const g = globalThis as unknown as GlobalThisWithNavigator;
32
+ const originalNavigator = g.navigator;
33
+ g.navigator = undefined;
14
34
  const locale = detectLocale();
15
- (globalThis as any).navigator = originalNavigator;
35
+ g.navigator = originalNavigator;
16
36
  expect(typeof locale).toBe("string");
17
37
  expect(locale.length).toBeGreaterThan(0);
18
38
  });
19
39
 
20
40
  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;
41
+ const g = globalThis as unknown as GlobalThisWithNavigator;
42
+ const originalNavigator = g.navigator;
43
+ g.navigator = undefined;
23
44
  mock.module("expo-localization", () => ({
24
45
  getLocales: () => {
25
46
  throw new Error("not available");
26
47
  },
27
48
  }));
28
49
  const locale = detectLocale();
29
- (globalThis as any).navigator = originalNavigator;
50
+ g.navigator = originalNavigator;
30
51
  // Reset mock
31
52
  mock.module("expo-localization", () => ({
32
53
  getLocales: () => [{languageTag: "es-ES"}],
@@ -35,10 +56,11 @@ describe("detectLocale", () => {
35
56
  });
36
57
 
37
58
  it("returns navigator.language when available", () => {
38
- const originalNavigator = (globalThis as any).navigator;
39
- (globalThis as any).navigator = {language: "fr-FR"};
59
+ const g = globalThis as unknown as GlobalThisWithNavigator;
60
+ const originalNavigator = g.navigator;
61
+ g.navigator = {language: "fr-FR"};
40
62
  const locale = detectLocale();
41
- (globalThis as any).navigator = originalNavigator;
63
+ g.navigator = originalNavigator;
42
64
  expect(locale).toBe("fr-FR");
43
65
  });
44
66
  });
@@ -54,10 +76,10 @@ describe("useConsentForms", () => {
54
76
  }));
55
77
  const api = {
56
78
  enhanceEndpoints: mock(() => ({
57
- injectEndpoints: mock((opts: any) => {
79
+ injectEndpoints: mock((opts: MockInjectOpts) => {
58
80
  // Call endpoint builder to exercise provides/onQueryStarted
59
81
  const build = {
60
- query: mock((def: any) => {
82
+ query: mock((def: MockQueryDef) => {
61
83
  // Call onQueryStarted with a successful result
62
84
  if (def.onQueryStarted) {
63
85
  void def.onQueryStarted(undefined, {
@@ -78,21 +100,21 @@ describe("useConsentForms", () => {
78
100
  };
79
101
 
80
102
  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));
103
+ const {api} = buildApi({data: [{id: "1", title: "Form 1"} as unknown as ConsentFormPublic]});
104
+ const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
83
105
  expect(result.current.forms).toBeDefined();
84
106
  expect(Array.isArray(result.current.forms)).toBe(true);
85
107
  });
86
108
 
87
109
  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"));
110
+ const {api} = buildApi({data: {data: [{id: "2"} as unknown as ConsentFormPublic]}});
111
+ const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi, "/api"));
90
112
  expect(Array.isArray(result.current.forms)).toBe(true);
91
113
  });
92
114
 
93
115
  it("returns empty array when no data is present", () => {
94
116
  const {api} = buildApi({data: undefined, isLoading: true});
95
- const {result} = renderHook(() => useConsentForms(api as any));
117
+ const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
96
118
  expect(result.current.forms).toEqual([]);
97
119
  expect(result.current.isLoading).toBe(true);
98
120
  });
@@ -101,9 +123,9 @@ describe("useConsentForms", () => {
101
123
  const refetch = mock(() => {});
102
124
  const api = {
103
125
  enhanceEndpoints: mock(() => ({
104
- injectEndpoints: mock((opts: any) => {
126
+ injectEndpoints: mock((opts: MockInjectOpts) => {
105
127
  const build = {
106
- query: mock((def: any) => {
128
+ query: mock((def: MockQueryDef) => {
107
129
  if (def.onQueryStarted) {
108
130
  void def.onQueryStarted(undefined, {
109
131
  queryFulfilled: Promise.reject(new Error("failed")),
@@ -124,7 +146,7 @@ describe("useConsentForms", () => {
124
146
  }),
125
147
  })),
126
148
  };
127
- const {result} = renderHook(() => useConsentForms(api as any));
149
+ const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
128
150
  expect(result.current.error).toBe("error");
129
151
  });
130
152
  });
@@ -1,8 +1,19 @@
1
1
  import {describe, expect, it, mock} from "bun:test";
2
2
  import {renderHook} from "@testing-library/react-native";
3
3
 
4
+ import type {SubmitConsentBody} from "./useSubmitConsent";
4
5
  import {useSubmitConsent} from "./useSubmitConsent";
5
6
 
7
+ type SubmitConsentApi = Parameters<typeof useSubmitConsent>[0];
8
+
9
+ interface MockMutationDef {
10
+ query: (body: SubmitConsentBody) => {method: string; url: string};
11
+ }
12
+
13
+ interface MockInjectOpts {
14
+ endpoints: (build: {mutation: (def: MockMutationDef) => string}) => Record<string, unknown>;
15
+ }
16
+
6
17
  describe("useSubmitConsent", () => {
7
18
  const buildApi = () => {
8
19
  const unwrap = mock(async () => ({success: true}));
@@ -13,9 +24,9 @@ describe("useSubmitConsent", () => {
13
24
  ]);
14
25
  const api = {
15
26
  enhanceEndpoints: mock(() => ({
16
- injectEndpoints: mock((opts: any) => {
27
+ injectEndpoints: mock((opts: MockInjectOpts) => {
17
28
  const build = {
18
- mutation: mock((def: any) => {
29
+ mutation: mock((def: MockMutationDef) => {
19
30
  // Exercise the query builder
20
31
  const result = def.query({
21
32
  agreed: true,
@@ -37,7 +48,7 @@ describe("useSubmitConsent", () => {
37
48
 
38
49
  it("returns submit function and state", () => {
39
50
  const {api} = buildApi();
40
- const {result} = renderHook(() => useSubmitConsent(api as any));
51
+ const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
41
52
  expect(typeof result.current.submit).toBe("function");
42
53
  expect(result.current.isSubmitting).toBe(false);
43
54
  expect(result.current.error).toBeUndefined();
@@ -45,7 +56,7 @@ describe("useSubmitConsent", () => {
45
56
 
46
57
  it("calls submit mutation when submit is invoked", async () => {
47
58
  const {api, submitMutation, unwrap} = buildApi();
48
- const {result} = renderHook(() => useSubmitConsent(api as any, "/api"));
59
+ const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi, "/api"));
49
60
  const response = await result.current.submit({
50
61
  agreed: true,
51
62
  consentFormId: "f1",
@@ -58,7 +69,7 @@ describe("useSubmitConsent", () => {
58
69
 
59
70
  it("uses empty baseUrl when none provided", () => {
60
71
  const {api} = buildApi();
61
- const {result} = renderHook(() => useSubmitConsent(api as any));
72
+ const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
62
73
  expect(result.current.submit).toBeDefined();
63
74
  });
64
75
  });