@frak-labs/react-sdk 0.1.1 → 0.2.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/README.md +25 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +28 -64
- package/dist/index.d.ts +28 -64
- package/dist/index.js +1 -1
- package/package.json +17 -16
- package/src/hook/helper/useReferralInteraction.test.ts +358 -0
- package/src/hook/helper/useReferralInteraction.ts +78 -0
- package/src/hook/index.ts +10 -0
- package/src/hook/useDisplayModal.test.ts +275 -0
- package/src/hook/useDisplayModal.ts +68 -0
- package/src/hook/useFrakClient.test.ts +119 -0
- package/src/hook/useFrakClient.ts +11 -0
- package/src/hook/useFrakConfig.test.ts +184 -0
- package/src/hook/useFrakConfig.ts +22 -0
- package/src/hook/useGetMerchantInformation.ts +56 -0
- package/src/hook/useOpenSso.test.ts +202 -0
- package/src/hook/useOpenSso.ts +51 -0
- package/src/hook/usePrepareSso.test.ts +197 -0
- package/src/hook/usePrepareSso.ts +55 -0
- package/src/hook/useSendTransaction.test.ts +218 -0
- package/src/hook/useSendTransaction.ts +62 -0
- package/src/hook/useSiweAuthenticate.test.ts +258 -0
- package/src/hook/useSiweAuthenticate.ts +66 -0
- package/src/hook/useWalletStatus.test.ts +112 -0
- package/src/hook/useWalletStatus.ts +55 -0
- package/src/hook/utils/useFrakContext.test.ts +157 -0
- package/src/hook/utils/useFrakContext.ts +42 -0
- package/src/hook/utils/useMounted.test.ts +70 -0
- package/src/hook/utils/useMounted.ts +12 -0
- package/src/hook/utils/useWindowLocation.test.ts +54 -0
- package/src/hook/utils/useWindowLocation.ts +40 -0
- package/src/index.ts +25 -0
- package/src/provider/FrakConfigProvider.test.ts +246 -0
- package/src/provider/FrakConfigProvider.ts +54 -0
- package/src/provider/FrakIFrameClientProvider.test.tsx +209 -0
- package/src/provider/FrakIFrameClientProvider.ts +86 -0
- package/src/provider/index.ts +7 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useDisplayModal hook
|
|
3
|
+
* Tests TanStack Mutation wrapper for displaying modals
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
vi.mock("@frak-labs/core-sdk/actions");
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
LoginModalStepType,
|
|
12
|
+
ModalRpcStepsResultType,
|
|
13
|
+
SendTransactionModalStepType,
|
|
14
|
+
} from "@frak-labs/core-sdk";
|
|
15
|
+
import { displayModal } from "@frak-labs/core-sdk/actions";
|
|
16
|
+
import { ClientNotFound } from "@frak-labs/frame-connector";
|
|
17
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
18
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
19
|
+
import { useDisplayModal } from "./useDisplayModal";
|
|
20
|
+
|
|
21
|
+
describe("useDisplayModal", () => {
|
|
22
|
+
test("should throw ClientNotFound when client is not available", async ({
|
|
23
|
+
queryWrapper,
|
|
24
|
+
}) => {
|
|
25
|
+
const { result } = renderHook(() => useDisplayModal(), {
|
|
26
|
+
wrapper: queryWrapper.wrapper,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await waitFor(() => {
|
|
30
|
+
expect(result.current.mutate).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
result.current.mutate({
|
|
34
|
+
steps: {
|
|
35
|
+
login: {},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await waitFor(() => {
|
|
40
|
+
expect(result.current.isError).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.current.error).toBeInstanceOf(ClientNotFound);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("should display modal with login step", async ({
|
|
47
|
+
mockFrakProviders,
|
|
48
|
+
}) => {
|
|
49
|
+
const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
|
|
50
|
+
login: {
|
|
51
|
+
wallet: "0x1234567890123456789012345678901234567890",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
56
|
+
|
|
57
|
+
const { result } = renderHook(
|
|
58
|
+
() => useDisplayModal<[LoginModalStepType]>(),
|
|
59
|
+
{
|
|
60
|
+
wrapper: mockFrakProviders,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
result.current.mutate({
|
|
65
|
+
steps: {
|
|
66
|
+
login: {},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await waitFor(() => {
|
|
71
|
+
expect(result.current.isSuccess).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.current.data).toEqual(mockResult);
|
|
75
|
+
expect(displayModal).toHaveBeenCalledTimes(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("should display modal with sendTransaction step", async ({
|
|
79
|
+
mockFrakProviders,
|
|
80
|
+
}) => {
|
|
81
|
+
const mockResult: ModalRpcStepsResultType<
|
|
82
|
+
[SendTransactionModalStepType]
|
|
83
|
+
> = {
|
|
84
|
+
sendTransaction: {
|
|
85
|
+
hash: "0x1234567890123456789012345678901234567890123456789012345678901234",
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
90
|
+
|
|
91
|
+
const { result } = renderHook(
|
|
92
|
+
() => useDisplayModal<[SendTransactionModalStepType]>(),
|
|
93
|
+
{
|
|
94
|
+
wrapper: mockFrakProviders,
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
result.current.mutate({
|
|
99
|
+
steps: {
|
|
100
|
+
sendTransaction: {
|
|
101
|
+
tx: {
|
|
102
|
+
to: "0x1234567890123456789012345678901234567890",
|
|
103
|
+
data: "0x",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
expect(result.current.isSuccess).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result.current.data).toEqual(mockResult);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("should display modal with custom metadata", async ({
|
|
117
|
+
mockFrakProviders,
|
|
118
|
+
}) => {
|
|
119
|
+
const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
|
|
120
|
+
login: { wallet: "0x1234567890123456789012345678901234567890" },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
124
|
+
|
|
125
|
+
const { result } = renderHook(
|
|
126
|
+
() => useDisplayModal<[LoginModalStepType]>(),
|
|
127
|
+
{
|
|
128
|
+
wrapper: mockFrakProviders,
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
result.current.mutate({
|
|
133
|
+
steps: {
|
|
134
|
+
login: {},
|
|
135
|
+
},
|
|
136
|
+
metadata: {
|
|
137
|
+
header: {
|
|
138
|
+
title: "Custom Login",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(result.current.isSuccess).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(displayModal).toHaveBeenCalledWith(
|
|
148
|
+
expect.anything(),
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
metadata: expect.objectContaining({
|
|
151
|
+
header: {
|
|
152
|
+
title: "Custom Login",
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("should handle mutateAsync", async ({ mockFrakProviders }) => {
|
|
160
|
+
const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
|
|
161
|
+
login: { wallet: "0x1234567890123456789012345678901234567890" },
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
165
|
+
|
|
166
|
+
const { result } = renderHook(
|
|
167
|
+
() => useDisplayModal<[LoginModalStepType]>(),
|
|
168
|
+
{
|
|
169
|
+
wrapper: mockFrakProviders,
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const response = await result.current.mutateAsync({
|
|
174
|
+
steps: {
|
|
175
|
+
login: {},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(response).toEqual(mockResult);
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(result.current.isSuccess).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("should handle RPC errors", async ({ mockFrakProviders }) => {
|
|
187
|
+
const error = new Error("Modal display failed");
|
|
188
|
+
vi.mocked(displayModal).mockRejectedValue(error);
|
|
189
|
+
|
|
190
|
+
const { result } = renderHook(() => useDisplayModal(), {
|
|
191
|
+
wrapper: mockFrakProviders,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
result.current.mutate({
|
|
195
|
+
steps: {
|
|
196
|
+
login: {},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(result.current.isError).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(result.current.error).toEqual(error);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("should handle mutation options", async ({ mockFrakProviders }) => {
|
|
208
|
+
const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
|
|
209
|
+
login: { wallet: "0x1234567890123456789012345678901234567890" },
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
213
|
+
|
|
214
|
+
const onSuccess = vi.fn();
|
|
215
|
+
const onError = vi.fn();
|
|
216
|
+
|
|
217
|
+
const { result } = renderHook(
|
|
218
|
+
() =>
|
|
219
|
+
useDisplayModal<[LoginModalStepType]>({
|
|
220
|
+
mutations: {
|
|
221
|
+
onSuccess,
|
|
222
|
+
onError,
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
225
|
+
{
|
|
226
|
+
wrapper: mockFrakProviders,
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
result.current.mutate({
|
|
231
|
+
steps: {
|
|
232
|
+
login: {},
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(result.current.isSuccess).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(onSuccess).toHaveBeenCalled();
|
|
241
|
+
expect(onError).not.toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("should reset mutation state", async ({ mockFrakProviders }) => {
|
|
245
|
+
const mockResult: ModalRpcStepsResultType<[LoginModalStepType]> = {
|
|
246
|
+
login: { wallet: "0x1234567890123456789012345678901234567890" },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
vi.mocked(displayModal).mockResolvedValue(mockResult as any);
|
|
250
|
+
|
|
251
|
+
const { result } = renderHook(
|
|
252
|
+
() => useDisplayModal<[LoginModalStepType]>(),
|
|
253
|
+
{
|
|
254
|
+
wrapper: mockFrakProviders,
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
result.current.mutate({
|
|
259
|
+
steps: {
|
|
260
|
+
login: {},
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await waitFor(() => {
|
|
265
|
+
expect(result.current.isSuccess).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
result.current.reset();
|
|
269
|
+
|
|
270
|
+
await waitFor(() => {
|
|
271
|
+
expect(result.current.data).toBeUndefined();
|
|
272
|
+
expect(result.current.isIdle).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DisplayModalParamsType,
|
|
3
|
+
ModalRpcStepsResultType,
|
|
4
|
+
ModalStepTypes,
|
|
5
|
+
} from "@frak-labs/core-sdk";
|
|
6
|
+
import { displayModal } from "@frak-labs/core-sdk/actions";
|
|
7
|
+
import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
|
|
8
|
+
import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
|
|
9
|
+
import { useFrakClient } from "./useFrakClient";
|
|
10
|
+
|
|
11
|
+
/** @ignore */
|
|
12
|
+
type MutationOptions<T extends ModalStepTypes[]> = Omit<
|
|
13
|
+
UseMutationOptions<
|
|
14
|
+
ModalRpcStepsResultType<T>,
|
|
15
|
+
FrakRpcError,
|
|
16
|
+
DisplayModalParamsType<T>
|
|
17
|
+
>,
|
|
18
|
+
"mutationFn" | "mutationKey"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/** @inline */
|
|
22
|
+
interface UseDisplayModalParams<T extends ModalStepTypes[] = ModalStepTypes[]> {
|
|
23
|
+
/**
|
|
24
|
+
* Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
|
|
25
|
+
*/
|
|
26
|
+
mutations?: MutationOptions<T>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook that return a mutation helping to display a modal to the user
|
|
31
|
+
*
|
|
32
|
+
* It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.displayModal | `displayModal()`} action
|
|
33
|
+
*
|
|
34
|
+
* @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
|
|
35
|
+
*
|
|
36
|
+
* @typeParam T
|
|
37
|
+
* The modal steps types to display (the result will correspond to the steps types asked in params)
|
|
38
|
+
* An array of {@link @frak-labs/core-sdk!index.ModalStepTypes | `ModalStepTypes`}
|
|
39
|
+
* If not provided, it will default to a generic array of `ModalStepTypes`
|
|
40
|
+
*
|
|
41
|
+
* @group hooks
|
|
42
|
+
*
|
|
43
|
+
* @returns
|
|
44
|
+
* The mutation hook wrapping the `displayModal()` action
|
|
45
|
+
* The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!index.DisplayModalParamsType | `DisplayModalParamsType<T>`}, with type params `T` being the modal steps types to display
|
|
46
|
+
* The `data` result is a {@link @frak-labs/core-sdk!index.ModalRpcStepsResultType | `ModalRpcStepsResultType`}
|
|
47
|
+
*
|
|
48
|
+
* @see {@link @frak-labs/core-sdk!actions.displayModal | `displayModal()`} for more info about the underlying action
|
|
49
|
+
* @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
|
|
50
|
+
*/
|
|
51
|
+
export function useDisplayModal<T extends ModalStepTypes[] = ModalStepTypes[]>({
|
|
52
|
+
mutations,
|
|
53
|
+
}: UseDisplayModalParams<T> = {}) {
|
|
54
|
+
const client = useFrakClient();
|
|
55
|
+
|
|
56
|
+
return useMutation({
|
|
57
|
+
...mutations,
|
|
58
|
+
mutationKey: ["frak-sdk", "display-modal"],
|
|
59
|
+
mutationFn: async (args: DisplayModalParamsType<T>) => {
|
|
60
|
+
if (!client) {
|
|
61
|
+
throw new ClientNotFound();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Ask to display the modal
|
|
65
|
+
return displayModal(client, args);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useFrakClient hook
|
|
3
|
+
* Tests that the hook correctly retrieves the FrakClient from context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { renderHook } from "@testing-library/react";
|
|
7
|
+
import { createElement } from "react";
|
|
8
|
+
import { describe, expect, test } from "../../tests/vitest-fixtures";
|
|
9
|
+
import { FrakIFrameClientContext } from "../provider/FrakIFrameClientProvider";
|
|
10
|
+
import { useFrakClient } from "./useFrakClient";
|
|
11
|
+
|
|
12
|
+
describe("useFrakClient", () => {
|
|
13
|
+
test("should return undefined when used outside provider", () => {
|
|
14
|
+
const { result } = renderHook(() => useFrakClient());
|
|
15
|
+
|
|
16
|
+
expect(result.current).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("should return client when used inside provider", ({
|
|
20
|
+
mockFrakClient,
|
|
21
|
+
}) => {
|
|
22
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
23
|
+
createElement(
|
|
24
|
+
FrakIFrameClientContext.Provider,
|
|
25
|
+
{ value: mockFrakClient },
|
|
26
|
+
children
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const { result } = renderHook(() => useFrakClient(), {
|
|
30
|
+
wrapper,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.current).toBe(mockFrakClient);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("should return the same client instance across re-renders", ({
|
|
37
|
+
mockFrakClient,
|
|
38
|
+
}) => {
|
|
39
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
40
|
+
createElement(
|
|
41
|
+
FrakIFrameClientContext.Provider,
|
|
42
|
+
{ value: mockFrakClient },
|
|
43
|
+
children
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const { result, rerender } = renderHook(() => useFrakClient(), {
|
|
47
|
+
wrapper,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const firstClient = result.current;
|
|
51
|
+
rerender();
|
|
52
|
+
const secondClient = result.current;
|
|
53
|
+
|
|
54
|
+
expect(firstClient).toBe(secondClient);
|
|
55
|
+
expect(firstClient).toBe(mockFrakClient);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("should update when provider value changes", ({
|
|
59
|
+
mockFrakClient,
|
|
60
|
+
mockFrakConfig,
|
|
61
|
+
}) => {
|
|
62
|
+
const newMockClient = {
|
|
63
|
+
...mockFrakClient,
|
|
64
|
+
config: {
|
|
65
|
+
...mockFrakConfig,
|
|
66
|
+
domain: "new-domain.com",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let currentClient = mockFrakClient;
|
|
71
|
+
|
|
72
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
73
|
+
createElement(
|
|
74
|
+
FrakIFrameClientContext.Provider,
|
|
75
|
+
{ value: currentClient },
|
|
76
|
+
children
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const { result, rerender } = renderHook(() => useFrakClient(), {
|
|
80
|
+
wrapper,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Initially returns first client
|
|
84
|
+
expect(result.current).toBe(mockFrakClient);
|
|
85
|
+
|
|
86
|
+
// Update the client in provider
|
|
87
|
+
currentClient = newMockClient;
|
|
88
|
+
rerender();
|
|
89
|
+
|
|
90
|
+
// Should return new client
|
|
91
|
+
expect(result.current).toBe(newMockClient);
|
|
92
|
+
expect(result.current?.config.domain).toBe("new-domain.com");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("should handle client becoming undefined", ({ mockFrakClient }) => {
|
|
96
|
+
let currentClient: typeof mockFrakClient | undefined = mockFrakClient;
|
|
97
|
+
|
|
98
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
99
|
+
createElement(
|
|
100
|
+
FrakIFrameClientContext.Provider,
|
|
101
|
+
{ value: currentClient },
|
|
102
|
+
children
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const { result, rerender } = renderHook(() => useFrakClient(), {
|
|
106
|
+
wrapper,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Initially returns client
|
|
110
|
+
expect(result.current).toBe(mockFrakClient);
|
|
111
|
+
|
|
112
|
+
// Set client to undefined
|
|
113
|
+
currentClient = undefined;
|
|
114
|
+
rerender();
|
|
115
|
+
|
|
116
|
+
// Should return undefined
|
|
117
|
+
expect(result.current).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useFrakConfig hook
|
|
3
|
+
* Tests that the hook correctly retrieves the FrakConfig from context
|
|
4
|
+
* and throws error when used outside provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrakRpcError } from "@frak-labs/frame-connector";
|
|
8
|
+
import { renderHook } from "@testing-library/react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { describe, expect, test, vi } from "../../tests/vitest-fixtures";
|
|
11
|
+
import { FrakConfigProvider } from "../provider/FrakConfigProvider";
|
|
12
|
+
import { useFrakConfig } from "./useFrakConfig";
|
|
13
|
+
|
|
14
|
+
describe("useFrakConfig", () => {
|
|
15
|
+
test("should throw FrakRpcError when used outside provider", () => {
|
|
16
|
+
// Mock console.error to avoid noise in test output
|
|
17
|
+
const consoleErrorSpy = vi
|
|
18
|
+
.spyOn(console, "error")
|
|
19
|
+
.mockImplementation(() => {});
|
|
20
|
+
|
|
21
|
+
expect(() => {
|
|
22
|
+
renderHook(() => useFrakConfig());
|
|
23
|
+
}).toThrow(FrakRpcError);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
renderHook(() => useFrakConfig());
|
|
27
|
+
} catch (error) {
|
|
28
|
+
expect(error).toBeInstanceOf(FrakRpcError);
|
|
29
|
+
// FrakRpcError message contains the error code
|
|
30
|
+
expect((error as FrakRpcError).message).toContain("-32002");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
consoleErrorSpy.mockRestore();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("should return config when used inside provider", ({
|
|
37
|
+
mockFrakConfig,
|
|
38
|
+
}) => {
|
|
39
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
40
|
+
React.createElement(
|
|
41
|
+
FrakConfigProvider,
|
|
42
|
+
{ config: mockFrakConfig },
|
|
43
|
+
children
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
47
|
+
wrapper,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(result.current).toBeDefined();
|
|
51
|
+
expect(result.current.domain).toBe(mockFrakConfig.domain);
|
|
52
|
+
expect(result.current.metadata?.name).toBe(
|
|
53
|
+
mockFrakConfig.metadata?.name
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should apply default walletUrl if not provided", () => {
|
|
58
|
+
const configWithoutWalletUrl = {
|
|
59
|
+
domain: "example.com",
|
|
60
|
+
metadata: {
|
|
61
|
+
name: "Test App",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
66
|
+
React.createElement(
|
|
67
|
+
FrakConfigProvider,
|
|
68
|
+
{ config: configWithoutWalletUrl },
|
|
69
|
+
children
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
73
|
+
wrapper,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(result.current.walletUrl).toBe("https://wallet.frak.id");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("should use provided walletUrl if specified", ({ mockFrakConfig }) => {
|
|
80
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
81
|
+
React.createElement(
|
|
82
|
+
FrakConfigProvider,
|
|
83
|
+
{ config: mockFrakConfig },
|
|
84
|
+
children
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
88
|
+
wrapper,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result.current.walletUrl).toBe(mockFrakConfig.walletUrl);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should return stable config across re-renders", ({
|
|
95
|
+
mockFrakConfig,
|
|
96
|
+
}) => {
|
|
97
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
98
|
+
React.createElement(
|
|
99
|
+
FrakConfigProvider,
|
|
100
|
+
{ config: mockFrakConfig },
|
|
101
|
+
children
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const { result, rerender } = renderHook(() => useFrakConfig(), {
|
|
105
|
+
wrapper,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const firstConfig = result.current;
|
|
109
|
+
rerender();
|
|
110
|
+
const secondConfig = result.current;
|
|
111
|
+
|
|
112
|
+
// Config values should remain stable
|
|
113
|
+
expect(firstConfig.domain).toBe(secondConfig.domain);
|
|
114
|
+
expect(firstConfig.walletUrl).toBe(secondConfig.walletUrl);
|
|
115
|
+
expect(firstConfig.domain).toBe(mockFrakConfig.domain);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("should include all config properties", ({ mockFrakConfig }) => {
|
|
119
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
120
|
+
React.createElement(
|
|
121
|
+
FrakConfigProvider,
|
|
122
|
+
{ config: mockFrakConfig },
|
|
123
|
+
children
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
127
|
+
wrapper,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.current.domain).toBe(mockFrakConfig.domain);
|
|
131
|
+
expect(result.current.walletUrl).toBe(mockFrakConfig.walletUrl);
|
|
132
|
+
expect(result.current.metadata).toEqual(mockFrakConfig.metadata);
|
|
133
|
+
expect(result.current.customizations).toEqual(
|
|
134
|
+
mockFrakConfig.customizations
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("should handle minimal config", () => {
|
|
139
|
+
const minimalConfig = {
|
|
140
|
+
domain: "minimal.com",
|
|
141
|
+
metadata: {
|
|
142
|
+
name: "Minimal Test App",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
147
|
+
React.createElement(
|
|
148
|
+
FrakConfigProvider,
|
|
149
|
+
{ config: minimalConfig },
|
|
150
|
+
children
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
154
|
+
wrapper,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.current.domain).toBe("minimal.com");
|
|
158
|
+
expect(result.current.walletUrl).toBe("https://wallet.frak.id");
|
|
159
|
+
expect(result.current.metadata.name).toBe("Minimal Test App");
|
|
160
|
+
expect(result.current.customizations).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("should fallback to window.location.host if domain not provided", () => {
|
|
164
|
+
const configWithoutDomain = {
|
|
165
|
+
metadata: {
|
|
166
|
+
name: "Test App",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
171
|
+
React.createElement(
|
|
172
|
+
FrakConfigProvider,
|
|
173
|
+
{ config: configWithoutDomain as any },
|
|
174
|
+
children
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const { result } = renderHook(() => useFrakConfig(), {
|
|
178
|
+
wrapper,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// In test environment, window.location.host is "localhost:3000" (JSDOM default)
|
|
182
|
+
expect(result.current.domain).toBe(window.location.host);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { FrakConfigContext } from "../provider";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the current Frak config
|
|
7
|
+
* @throws {FrakRpcError} if the config is not found (only if this hooks is used outside a FrakConfigProvider)
|
|
8
|
+
* @group hooks
|
|
9
|
+
*
|
|
10
|
+
* @see {@link @frak-labs/react-sdk!FrakConfigProvider | FrakConfigProvider} for the config provider
|
|
11
|
+
* @see {@link @frak-labs/core-sdk!index.FrakWalletSdkConfig | FrakWalletSdkConfig} for the config type
|
|
12
|
+
*/
|
|
13
|
+
export function useFrakConfig() {
|
|
14
|
+
const config = useContext(FrakConfigContext);
|
|
15
|
+
if (!config) {
|
|
16
|
+
throw new FrakRpcError(
|
|
17
|
+
RpcErrorCodes.configError,
|
|
18
|
+
"Frak config not found"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return config;
|
|
22
|
+
}
|