@ic-reactor/react 3.0.0-beta.7 → 3.0.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 +11 -10
- package/dist/createActorHooks.d.ts +2 -0
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js +2 -0
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +4 -0
- package/dist/createMutation.js.map +1 -1
- package/dist/hooks/index.d.ts +18 -5
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts +13 -11
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts +105 -0
- package/dist/hooks/useActorMethod.d.ts.map +1 -0
- package/dist/hooks/useActorMethod.js +192 -0
- package/dist/hooks/useActorMethod.js.map +1 -0
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +13 -10
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/package.json +9 -8
- package/src/createActorHooks.ts +146 -0
- package/src/createAuthHooks.ts +137 -0
- package/src/createInfiniteQuery.ts +471 -0
- package/src/createMutation.ts +163 -0
- package/src/createQuery.ts +197 -0
- package/src/createSuspenseInfiniteQuery.ts +478 -0
- package/src/createSuspenseQuery.ts +215 -0
- package/src/hooks/index.ts +93 -0
- package/src/hooks/useActorInfiniteQuery.test.tsx +457 -0
- package/src/hooks/useActorInfiniteQuery.ts +134 -0
- package/src/hooks/useActorMethod.test.tsx +798 -0
- package/src/hooks/useActorMethod.ts +397 -0
- package/src/hooks/useActorMutation.test.tsx +220 -0
- package/src/hooks/useActorMutation.ts +124 -0
- package/src/hooks/useActorQuery.test.tsx +287 -0
- package/src/hooks/useActorQuery.ts +110 -0
- package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +472 -0
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +137 -0
- package/src/hooks/useActorSuspenseQuery.test.tsx +254 -0
- package/src/hooks/useActorSuspenseQuery.ts +112 -0
- package/src/index.ts +21 -0
- package/src/types.ts +435 -0
- package/src/validation.ts +202 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react"
|
|
2
|
+
import {
|
|
3
|
+
useQuery,
|
|
4
|
+
useMutation,
|
|
5
|
+
type UseQueryResult,
|
|
6
|
+
type UseMutationResult,
|
|
7
|
+
type QueryKey,
|
|
8
|
+
type QueryObserverOptions,
|
|
9
|
+
} from "@tanstack/react-query"
|
|
10
|
+
import {
|
|
11
|
+
Reactor,
|
|
12
|
+
BaseActor,
|
|
13
|
+
FunctionName,
|
|
14
|
+
TransformKey,
|
|
15
|
+
ReactorArgs,
|
|
16
|
+
ReactorReturnOk,
|
|
17
|
+
ReactorReturnErr,
|
|
18
|
+
FunctionType,
|
|
19
|
+
} from "@ic-reactor/core"
|
|
20
|
+
import { CallConfig } from "@icp-sdk/core/agent"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for useActorMethod hook.
|
|
24
|
+
* Extends react-query's QueryObserverOptions with custom reactor params.
|
|
25
|
+
*
|
|
26
|
+
* This is a unified hook that handles both query and mutation methods.
|
|
27
|
+
* Query-specific options (like refetchInterval) only apply to query methods.
|
|
28
|
+
* Mutation-specific options (like invalidateQueries) only apply to mutation methods.
|
|
29
|
+
*/
|
|
30
|
+
export interface UseActorMethodParameters<
|
|
31
|
+
A = BaseActor,
|
|
32
|
+
M extends FunctionName<A> = FunctionName<A>,
|
|
33
|
+
T extends TransformKey = "candid",
|
|
34
|
+
> extends Omit<
|
|
35
|
+
QueryObserverOptions<
|
|
36
|
+
ReactorReturnOk<A, M, T>,
|
|
37
|
+
ReactorReturnErr<A, M, T>,
|
|
38
|
+
ReactorReturnOk<A, M, T>,
|
|
39
|
+
ReactorReturnOk<A, M, T>,
|
|
40
|
+
QueryKey
|
|
41
|
+
>,
|
|
42
|
+
"queryKey" | "queryFn"
|
|
43
|
+
> {
|
|
44
|
+
/** The reactor instance to use for method calls */
|
|
45
|
+
reactor: Reactor<A, T>
|
|
46
|
+
|
|
47
|
+
/** The method name to call on the canister */
|
|
48
|
+
functionName: M
|
|
49
|
+
|
|
50
|
+
/** Arguments to pass to the method (optional for parameterless methods) */
|
|
51
|
+
args?: ReactorArgs<A, M, T>
|
|
52
|
+
|
|
53
|
+
/** Agent call configuration (effectiveCanisterId, etc.) */
|
|
54
|
+
callConfig?: CallConfig
|
|
55
|
+
|
|
56
|
+
/** Custom query key (auto-generated if not provided) */
|
|
57
|
+
queryKey?: QueryKey
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Callback when the method call succeeds.
|
|
61
|
+
* Works for both query and mutation methods.
|
|
62
|
+
*/
|
|
63
|
+
onSuccess?: (data: ReactorReturnOk<A, M, T>) => void
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Callback when the method call fails.
|
|
67
|
+
* Works for both query and mutation methods.
|
|
68
|
+
*/
|
|
69
|
+
onError?: (error: ReactorReturnErr<A, M, T>) => void
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Query keys to invalidate after a successful mutation.
|
|
73
|
+
* Only applies to mutation methods (updates).
|
|
74
|
+
*/
|
|
75
|
+
invalidateQueries?: QueryKey[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Configuration type for bound useActorMethod hook (reactor omitted).
|
|
80
|
+
* For use with createActorHooks.
|
|
81
|
+
*/
|
|
82
|
+
export type UseActorMethodConfig<
|
|
83
|
+
A = BaseActor,
|
|
84
|
+
M extends FunctionName<A> = FunctionName<A>,
|
|
85
|
+
T extends TransformKey = "candid",
|
|
86
|
+
> = Omit<UseActorMethodParameters<A, M, T>, "reactor">
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Result type for useActorMethod hook.
|
|
90
|
+
* Provides a unified interface for both query and mutation methods.
|
|
91
|
+
*/
|
|
92
|
+
export interface UseActorMethodResult<
|
|
93
|
+
A = BaseActor,
|
|
94
|
+
M extends FunctionName<A> = FunctionName<A>,
|
|
95
|
+
T extends TransformKey = "candid",
|
|
96
|
+
> {
|
|
97
|
+
/** The returned data from the method call */
|
|
98
|
+
data: ReactorReturnOk<A, M, T> | undefined
|
|
99
|
+
|
|
100
|
+
/** Whether the method is currently executing */
|
|
101
|
+
isLoading: boolean
|
|
102
|
+
|
|
103
|
+
/** Alias for isLoading - whether a mutation is pending */
|
|
104
|
+
isPending: boolean
|
|
105
|
+
|
|
106
|
+
/** Whether there was an error */
|
|
107
|
+
isError: boolean
|
|
108
|
+
|
|
109
|
+
/** Whether the method has successfully completed at least once */
|
|
110
|
+
isSuccess: boolean
|
|
111
|
+
|
|
112
|
+
/** The error if one occurred */
|
|
113
|
+
error: ReactorReturnErr<A, M, T> | null
|
|
114
|
+
|
|
115
|
+
/** Whether this is a query method (true) or mutation method (false) */
|
|
116
|
+
isQuery: boolean
|
|
117
|
+
|
|
118
|
+
/** The function type (query, update, composite_query) */
|
|
119
|
+
functionType: FunctionType
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Call the method with optional arguments.
|
|
123
|
+
* For queries: triggers a refetch
|
|
124
|
+
* For mutations: executes the mutation with the provided args
|
|
125
|
+
*/
|
|
126
|
+
call: (
|
|
127
|
+
args?: ReactorArgs<A, M, T>
|
|
128
|
+
) => Promise<ReactorReturnOk<A, M, T> | undefined>
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reset the state (clear data and error).
|
|
132
|
+
* For queries: removes the query from cache
|
|
133
|
+
* For mutations: resets the mutation state
|
|
134
|
+
*/
|
|
135
|
+
reset: () => void
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* For queries only: Refetch the query
|
|
139
|
+
*/
|
|
140
|
+
refetch: () => Promise<ReactorReturnOk<A, M, T> | undefined>
|
|
141
|
+
|
|
142
|
+
// Expose underlying results for advanced use cases
|
|
143
|
+
/** The raw query result (only available for query methods) */
|
|
144
|
+
queryResult?: UseQueryResult<
|
|
145
|
+
ReactorReturnOk<A, M, T>,
|
|
146
|
+
ReactorReturnErr<A, M, T>
|
|
147
|
+
>
|
|
148
|
+
|
|
149
|
+
/** The raw mutation result (only available for mutation methods) */
|
|
150
|
+
mutationResult?: UseMutationResult<
|
|
151
|
+
ReactorReturnOk<A, M, T>,
|
|
152
|
+
ReactorReturnErr<A, M, T>,
|
|
153
|
+
ReactorArgs<A, M, T>
|
|
154
|
+
>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A unified hook for calling canister methods that automatically handles
|
|
159
|
+
* both query and mutation methods based on the Candid interface.
|
|
160
|
+
*/
|
|
161
|
+
export function useActorMethod<
|
|
162
|
+
A = BaseActor,
|
|
163
|
+
M extends FunctionName<A> = FunctionName<A>,
|
|
164
|
+
T extends TransformKey = "candid",
|
|
165
|
+
>({
|
|
166
|
+
reactor,
|
|
167
|
+
functionName,
|
|
168
|
+
args,
|
|
169
|
+
callConfig,
|
|
170
|
+
queryKey: customQueryKey,
|
|
171
|
+
enabled = true,
|
|
172
|
+
onSuccess,
|
|
173
|
+
onError,
|
|
174
|
+
invalidateQueries,
|
|
175
|
+
...queryOptions
|
|
176
|
+
}: UseActorMethodParameters<A, M, T>): UseActorMethodResult<A, M, T> {
|
|
177
|
+
// Determine if this is a query method by checking the IDL
|
|
178
|
+
const isQuery = useMemo(() => {
|
|
179
|
+
if (!reactor) throw new Error("Reactor instance is required")
|
|
180
|
+
return reactor.isQueryMethod(functionName)
|
|
181
|
+
}, [reactor, functionName])
|
|
182
|
+
|
|
183
|
+
const functionType: FunctionType = isQuery ? "query" : "update"
|
|
184
|
+
|
|
185
|
+
// Generate query key
|
|
186
|
+
const queryKey = useMemo(() => {
|
|
187
|
+
if (customQueryKey) return customQueryKey
|
|
188
|
+
return reactor.generateQueryKey({
|
|
189
|
+
functionName,
|
|
190
|
+
args,
|
|
191
|
+
})
|
|
192
|
+
}, [reactor, functionName, args, customQueryKey])
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Query Implementation
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
const queryResult = useQuery<
|
|
199
|
+
ReactorReturnOk<A, M, T>,
|
|
200
|
+
ReactorReturnErr<A, M, T>
|
|
201
|
+
>(
|
|
202
|
+
{
|
|
203
|
+
queryKey,
|
|
204
|
+
queryFn: async () => {
|
|
205
|
+
try {
|
|
206
|
+
const result = await reactor.callMethod({
|
|
207
|
+
functionName,
|
|
208
|
+
args,
|
|
209
|
+
callConfig,
|
|
210
|
+
})
|
|
211
|
+
onSuccess?.(result)
|
|
212
|
+
return result
|
|
213
|
+
} catch (error) {
|
|
214
|
+
onError?.(error as ReactorReturnErr<A, M, T>)
|
|
215
|
+
throw error
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
enabled: isQuery && enabled,
|
|
219
|
+
...queryOptions,
|
|
220
|
+
},
|
|
221
|
+
reactor.queryClient
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// Mutation Implementation
|
|
226
|
+
// ============================================================================
|
|
227
|
+
|
|
228
|
+
const mutationResult = useMutation<
|
|
229
|
+
ReactorReturnOk<A, M, T>,
|
|
230
|
+
ReactorReturnErr<A, M, T>,
|
|
231
|
+
ReactorArgs<A, M, T>
|
|
232
|
+
>(
|
|
233
|
+
{
|
|
234
|
+
mutationKey: queryKey,
|
|
235
|
+
mutationFn: async (mutationArgs) => {
|
|
236
|
+
const result = await reactor.callMethod({
|
|
237
|
+
functionName,
|
|
238
|
+
args: mutationArgs ?? args,
|
|
239
|
+
callConfig,
|
|
240
|
+
})
|
|
241
|
+
return result
|
|
242
|
+
},
|
|
243
|
+
onSuccess: (data) => {
|
|
244
|
+
onSuccess?.(data)
|
|
245
|
+
// Invalidate specified queries after successful mutation
|
|
246
|
+
if (invalidateQueries && invalidateQueries.length > 0) {
|
|
247
|
+
invalidateQueries.forEach((key) => {
|
|
248
|
+
reactor.queryClient.invalidateQueries({ queryKey: key })
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
onError: (error) => {
|
|
253
|
+
onError?.(error)
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
reactor.queryClient
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Unified Call Function
|
|
261
|
+
// ============================================================================
|
|
262
|
+
|
|
263
|
+
const call = useCallback(
|
|
264
|
+
async (
|
|
265
|
+
callArgs?: ReactorArgs<A, M, T>
|
|
266
|
+
): Promise<ReactorReturnOk<A, M, T> | undefined> => {
|
|
267
|
+
if (isQuery) {
|
|
268
|
+
// For queries, refetch with new args if provided
|
|
269
|
+
if (callArgs !== undefined) {
|
|
270
|
+
try {
|
|
271
|
+
const result = await reactor.queryClient.fetchQuery({
|
|
272
|
+
queryKey,
|
|
273
|
+
queryFn: () =>
|
|
274
|
+
reactor.callMethod({
|
|
275
|
+
functionName,
|
|
276
|
+
args: callArgs,
|
|
277
|
+
callConfig,
|
|
278
|
+
}),
|
|
279
|
+
staleTime: 0,
|
|
280
|
+
})
|
|
281
|
+
onSuccess?.(result)
|
|
282
|
+
return result
|
|
283
|
+
} catch (error) {
|
|
284
|
+
onError?.(error as ReactorReturnErr<A, M, T>)
|
|
285
|
+
return undefined
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Otherwise just refetch
|
|
289
|
+
const { data } = await queryResult.refetch()
|
|
290
|
+
return data
|
|
291
|
+
} else {
|
|
292
|
+
// For mutations, execute with provided args
|
|
293
|
+
return mutationResult
|
|
294
|
+
.mutateAsync(callArgs as ReactorArgs<A, M, T>)
|
|
295
|
+
.catch(() => undefined)
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
[
|
|
299
|
+
isQuery,
|
|
300
|
+
reactor,
|
|
301
|
+
functionName,
|
|
302
|
+
callConfig,
|
|
303
|
+
queryKey,
|
|
304
|
+
queryResult,
|
|
305
|
+
mutationResult,
|
|
306
|
+
onSuccess,
|
|
307
|
+
onError,
|
|
308
|
+
]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Reset Function
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
const reset = useCallback(() => {
|
|
316
|
+
if (isQuery) {
|
|
317
|
+
reactor.queryClient.removeQueries({ queryKey })
|
|
318
|
+
} else {
|
|
319
|
+
mutationResult.reset()
|
|
320
|
+
}
|
|
321
|
+
}, [isQuery, reactor, queryKey, mutationResult])
|
|
322
|
+
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Refetch Function
|
|
325
|
+
// ============================================================================
|
|
326
|
+
|
|
327
|
+
const refetch = useCallback(async () => {
|
|
328
|
+
if (isQuery) {
|
|
329
|
+
const result = await queryResult.refetch()
|
|
330
|
+
return result.data
|
|
331
|
+
}
|
|
332
|
+
return undefined
|
|
333
|
+
}, [isQuery, queryResult])
|
|
334
|
+
|
|
335
|
+
// ============================================================================
|
|
336
|
+
// Return Unified Result
|
|
337
|
+
// ============================================================================
|
|
338
|
+
|
|
339
|
+
if (isQuery) {
|
|
340
|
+
return {
|
|
341
|
+
data: queryResult.data,
|
|
342
|
+
isLoading: queryResult.isLoading,
|
|
343
|
+
isPending: queryResult.isLoading,
|
|
344
|
+
isError: queryResult.isError,
|
|
345
|
+
isSuccess: queryResult.isSuccess,
|
|
346
|
+
error: queryResult.error,
|
|
347
|
+
isQuery: true,
|
|
348
|
+
functionType,
|
|
349
|
+
call,
|
|
350
|
+
reset,
|
|
351
|
+
refetch,
|
|
352
|
+
queryResult,
|
|
353
|
+
} as UseActorMethodResult<A, M, T>
|
|
354
|
+
} else {
|
|
355
|
+
return {
|
|
356
|
+
data: mutationResult.data,
|
|
357
|
+
isLoading: mutationResult.isPending,
|
|
358
|
+
isPending: mutationResult.isPending,
|
|
359
|
+
isError: mutationResult.isError,
|
|
360
|
+
isSuccess: mutationResult.isSuccess,
|
|
361
|
+
error: mutationResult.error,
|
|
362
|
+
isQuery: false,
|
|
363
|
+
functionType,
|
|
364
|
+
call,
|
|
365
|
+
reset,
|
|
366
|
+
refetch,
|
|
367
|
+
mutationResult,
|
|
368
|
+
} as UseActorMethodResult<A, M, T>
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Creates a bound useMethod hook for a specific reactor instance.
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```tsx
|
|
377
|
+
* const { useMethod } = createActorMethodHooks(reactor)
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
export function createActorMethodHooks<
|
|
381
|
+
A = BaseActor,
|
|
382
|
+
T extends TransformKey = "candid",
|
|
383
|
+
>(reactor: Reactor<A, T>) {
|
|
384
|
+
return {
|
|
385
|
+
/**
|
|
386
|
+
* Hook for calling methods on the bound reactor.
|
|
387
|
+
*/
|
|
388
|
+
useMethod: <M extends FunctionName<A>>(
|
|
389
|
+
config: Omit<UseActorMethodParameters<A, M, T>, "reactor">
|
|
390
|
+
) =>
|
|
391
|
+
useActorMethod({ ...config, reactor } as UseActorMethodParameters<
|
|
392
|
+
A,
|
|
393
|
+
M,
|
|
394
|
+
T
|
|
395
|
+
>),
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
+
import { renderHook } from "@testing-library/react"
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { ClientManager, Reactor } from "@ic-reactor/core"
|
|
5
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
|
6
|
+
import { Actor, ActorMethod } from "@icp-sdk/core/agent"
|
|
7
|
+
import { useActorMutation } from "./useActorMutation"
|
|
8
|
+
|
|
9
|
+
// Mock IDL factory
|
|
10
|
+
const idlFactory = ({ IDL }: any) => {
|
|
11
|
+
return IDL.Service({
|
|
12
|
+
create_user: IDL.Func(
|
|
13
|
+
[IDL.Record({ name: IDL.Text })],
|
|
14
|
+
[IDL.Record({ id: IDL.Text, name: IDL.Text })],
|
|
15
|
+
["update"]
|
|
16
|
+
),
|
|
17
|
+
update_user: IDL.Func(
|
|
18
|
+
[IDL.Record({ id: IDL.Text, name: IDL.Text })],
|
|
19
|
+
[IDL.Record({ id: IDL.Text, name: IDL.Text })],
|
|
20
|
+
["update"]
|
|
21
|
+
),
|
|
22
|
+
delete_user: IDL.Func([IDL.Text], [IDL.Bool], ["update"]),
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Define Actor Interface
|
|
27
|
+
interface CreateUserInput {
|
|
28
|
+
name: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UpdateUserInput {
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface User {
|
|
37
|
+
id: string
|
|
38
|
+
name: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Actor type with methods
|
|
42
|
+
interface TestActor {
|
|
43
|
+
get_user: ActorMethod<[string], User>
|
|
44
|
+
create_user: ActorMethod<[CreateUserInput], User>
|
|
45
|
+
update_user: ActorMethod<[UpdateUserInput], User>
|
|
46
|
+
delete_user: ActorMethod<[string], boolean>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Mock data
|
|
50
|
+
const mockUserCreated: User = { id: "user-1", name: "Alice" }
|
|
51
|
+
const mockUserUpdated: User = { id: "user-1", name: "Bob" }
|
|
52
|
+
|
|
53
|
+
// Mock Actor.createActor
|
|
54
|
+
vi.mock("@icp-sdk/core/agent", async () => {
|
|
55
|
+
const actual = await vi.importActual<typeof import("@icp-sdk/core/agent")>(
|
|
56
|
+
"@icp-sdk/core/agent"
|
|
57
|
+
)
|
|
58
|
+
return {
|
|
59
|
+
...actual,
|
|
60
|
+
Actor: class extends actual.Actor {
|
|
61
|
+
static createActor = vi.fn()
|
|
62
|
+
static canisterIdOf = vi.fn().mockReturnValue({
|
|
63
|
+
toString: () => "test-canister-id",
|
|
64
|
+
})
|
|
65
|
+
static interfaceOf = vi.fn().mockReturnValue({
|
|
66
|
+
_fields: [],
|
|
67
|
+
})
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe("useActorMutation", () => {
|
|
73
|
+
let queryClient: QueryClient
|
|
74
|
+
let clientManager: ClientManager
|
|
75
|
+
let mockActor: TestActor
|
|
76
|
+
let reactor: Reactor<TestActor>
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
queryClient = new QueryClient({
|
|
80
|
+
defaultOptions: {
|
|
81
|
+
mutations: {
|
|
82
|
+
retry: false,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
clientManager = new ClientManager({ queryClient })
|
|
88
|
+
|
|
89
|
+
// Setup mock actor
|
|
90
|
+
mockActor = {
|
|
91
|
+
create_user: vi.fn().mockResolvedValue(mockUserCreated),
|
|
92
|
+
update_user: vi.fn().mockResolvedValue(mockUserUpdated),
|
|
93
|
+
delete_user: vi.fn().mockResolvedValue(true),
|
|
94
|
+
} as unknown as TestActor
|
|
95
|
+
;(Actor.createActor as any).mockReturnValue(mockActor)
|
|
96
|
+
|
|
97
|
+
reactor = new Reactor<TestActor>({
|
|
98
|
+
clientManager,
|
|
99
|
+
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
100
|
+
idlFactory,
|
|
101
|
+
name: "test",
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
106
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
describe("basic functionality", () => {
|
|
110
|
+
it("should create a mutation hook", () => {
|
|
111
|
+
const { result } = renderHook(
|
|
112
|
+
() =>
|
|
113
|
+
useActorMutation({
|
|
114
|
+
reactor,
|
|
115
|
+
functionName: "create_user",
|
|
116
|
+
}),
|
|
117
|
+
{ wrapper }
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
expect(result.current).toBeDefined()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("should return correct hook structure", () => {
|
|
124
|
+
const { result } = renderHook(
|
|
125
|
+
() =>
|
|
126
|
+
useActorMutation({
|
|
127
|
+
reactor,
|
|
128
|
+
functionName: "create_user",
|
|
129
|
+
}),
|
|
130
|
+
{ wrapper }
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
expect(result.current.mutate).toBeInstanceOf(Function)
|
|
134
|
+
expect(result.current.mutateAsync).toBeInstanceOf(Function)
|
|
135
|
+
expect(typeof result.current.isPending).toBe("boolean")
|
|
136
|
+
expect(typeof result.current.isSuccess).toBe("boolean")
|
|
137
|
+
expect(typeof result.current.isError).toBe("boolean")
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("should have correct initial state", () => {
|
|
141
|
+
const { result } = renderHook(
|
|
142
|
+
() =>
|
|
143
|
+
useActorMutation({
|
|
144
|
+
reactor,
|
|
145
|
+
functionName: "create_user",
|
|
146
|
+
}),
|
|
147
|
+
{ wrapper }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
expect(result.current.isPending).toBe(false)
|
|
151
|
+
expect(result.current.isSuccess).toBe(false)
|
|
152
|
+
expect(result.current.isError).toBe(false)
|
|
153
|
+
expect(result.current.data).toBeUndefined()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe("hook options", () => {
|
|
158
|
+
it("should accept mutation options like onSuccess and onError", () => {
|
|
159
|
+
const onSuccess = vi.fn()
|
|
160
|
+
const onError = vi.fn()
|
|
161
|
+
|
|
162
|
+
const { result } = renderHook(
|
|
163
|
+
() =>
|
|
164
|
+
useActorMutation({
|
|
165
|
+
reactor,
|
|
166
|
+
functionName: "create_user",
|
|
167
|
+
onSuccess,
|
|
168
|
+
onError,
|
|
169
|
+
}),
|
|
170
|
+
{ wrapper }
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
expect(result.current.mutate).toBeInstanceOf(Function)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it("should expose mutate and mutateAsync methods", () => {
|
|
177
|
+
const { result } = renderHook(
|
|
178
|
+
() =>
|
|
179
|
+
useActorMutation({
|
|
180
|
+
reactor,
|
|
181
|
+
functionName: "create_user",
|
|
182
|
+
}),
|
|
183
|
+
{ wrapper }
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
expect(result.current.mutate).toBeInstanceOf(Function)
|
|
187
|
+
expect(result.current.mutateAsync).toBeInstanceOf(Function)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe("invalidateQueries functionality", () => {
|
|
192
|
+
it("should accept invalidateQueries option", () => {
|
|
193
|
+
const { result } = renderHook(
|
|
194
|
+
() =>
|
|
195
|
+
useActorMutation({
|
|
196
|
+
reactor,
|
|
197
|
+
functionName: "update_user",
|
|
198
|
+
invalidateQueries: [["test-canister", "get_user"]],
|
|
199
|
+
}),
|
|
200
|
+
{ wrapper }
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
expect(result.current.mutate).toBeInstanceOf(Function)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it("should support invalidateQueries with args", () => {
|
|
207
|
+
const { result } = renderHook(
|
|
208
|
+
() =>
|
|
209
|
+
useActorMutation({
|
|
210
|
+
reactor,
|
|
211
|
+
functionName: "update_user",
|
|
212
|
+
invalidateQueries: [["test-canister", "get_user", "user-1"]],
|
|
213
|
+
}),
|
|
214
|
+
{ wrapper }
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
expect(result.current.mutate).toBeInstanceOf(Function)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
})
|