@ic-reactor/react 3.7.0 → 3.8.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 (86) hide show
  1. package/README.md +93 -9
  2. package/dist/auth/authentication-manager.d.ts +37 -5
  3. package/dist/auth/authentication-manager.d.ts.map +1 -1
  4. package/dist/auth/authentication-manager.js +111 -37
  5. package/dist/auth/authentication-manager.js.map +1 -1
  6. package/dist/auth/createIdentityAttributeHooks.d.ts +2 -2
  7. package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
  8. package/dist/auth/identity-attributes-manager.d.ts +12 -5
  9. package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
  10. package/dist/auth/identity-attributes-manager.js +42 -28
  11. package/dist/auth/identity-attributes-manager.js.map +1 -1
  12. package/dist/auth/identity-attributes.d.ts +1 -1
  13. package/dist/auth/identity-attributes.d.ts.map +1 -1
  14. package/dist/auth/identity-attributes.js +1 -1
  15. package/dist/auth/identity-attributes.js.map +1 -1
  16. package/dist/auth/index.d.ts +7 -7
  17. package/dist/auth/index.d.ts.map +1 -1
  18. package/dist/auth/index.js +7 -7
  19. package/dist/auth/index.js.map +1 -1
  20. package/dist/auth/types.d.ts +123 -16
  21. package/dist/auth/types.d.ts.map +1 -1
  22. package/dist/createActorHooks.d.ts +4 -4
  23. package/dist/createActorHooks.d.ts.map +1 -1
  24. package/dist/createActorHooks.js +6 -6
  25. package/dist/createActorHooks.js.map +1 -1
  26. package/dist/createInfiniteQuery.d.ts +1 -1
  27. package/dist/createInfiniteQuery.d.ts.map +1 -1
  28. package/dist/createInfiniteQuery.js +1 -1
  29. package/dist/createInfiniteQuery.js.map +1 -1
  30. package/dist/createMutation.d.ts +1 -1
  31. package/dist/createMutation.d.ts.map +1 -1
  32. package/dist/createQuery.d.ts +1 -1
  33. package/dist/createQuery.d.ts.map +1 -1
  34. package/dist/createQuery.js +1 -1
  35. package/dist/createQuery.js.map +1 -1
  36. package/dist/createSuspenseInfiniteQuery.d.ts +1 -1
  37. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  38. package/dist/createSuspenseInfiniteQuery.js +1 -1
  39. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  40. package/dist/createSuspenseQuery.d.ts +1 -1
  41. package/dist/createSuspenseQuery.d.ts.map +1 -1
  42. package/dist/createSuspenseQuery.js +1 -1
  43. package/dist/createSuspenseQuery.js.map +1 -1
  44. package/dist/defineReactor.d.ts +54 -4
  45. package/dist/defineReactor.d.ts.map +1 -1
  46. package/dist/defineReactor.js +77 -7
  47. package/dist/defineReactor.js.map +1 -1
  48. package/dist/hooks/createAuthHooks.d.ts +2 -2
  49. package/dist/hooks/createAuthHooks.d.ts.map +1 -1
  50. package/dist/hooks/createAuthHooks.js +7 -2
  51. package/dist/hooks/createAuthHooks.js.map +1 -1
  52. package/dist/hooks/useActorInfiniteQuery.js +1 -1
  53. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  54. package/dist/hooks/useActorSuspenseInfiniteQuery.js +1 -1
  55. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  56. package/dist/index.d.ts +11 -11
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +11 -11
  59. package/dist/index.js.map +1 -1
  60. package/llms.txt +1 -1
  61. package/package.json +14 -9
  62. package/src/auth/authentication-manager.ts +121 -49
  63. package/src/auth/createIdentityAttributeHooks.ts +2 -2
  64. package/src/auth/identity-attributes-manager.ts +53 -44
  65. package/src/auth/identity-attributes.ts +2 -2
  66. package/src/auth/index.ts +7 -7
  67. package/src/auth/types.ts +134 -18
  68. package/src/createActorHooks.ts +9 -9
  69. package/src/createInfiniteQuery.ts +2 -2
  70. package/src/createMutation.ts +1 -1
  71. package/src/createQuery.ts +2 -2
  72. package/src/createSuspenseInfiniteQuery.ts +2 -2
  73. package/src/createSuspenseQuery.ts +2 -2
  74. package/src/defineReactor.ts +136 -14
  75. package/src/hooks/createAuthHooks.ts +8 -3
  76. package/src/hooks/useActorInfiniteQuery.ts +1 -1
  77. package/src/hooks/useActorSuspenseInfiniteQuery.ts +1 -1
  78. package/src/index.ts +11 -11
  79. package/src/defineReactor.test.ts +0 -70
  80. package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
  81. package/src/hooks/useActorMethod.test.tsx +0 -798
  82. package/src/hooks/useActorMutation.test.tsx +0 -220
  83. package/src/hooks/useActorQuery.test.tsx +0 -287
  84. package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
  85. package/src/hooks/useActorSuspenseQuery.test.tsx +0 -254
  86. package/src/utils.test.ts +0 -14
@@ -1,472 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest"
2
- import { renderHook, waitFor, act } from "@testing-library/react"
3
- import React, { Suspense } from "react"
4
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
5
- import { useActorSuspenseInfiniteQuery } from "./useActorSuspenseInfiniteQuery"
6
- import { ActorMethod } from "@icp-sdk/core/agent"
7
- import { Reactor } from "@ic-reactor/core"
8
-
9
- // Define a test actor type with paginated methods
10
- type TestActor = {
11
- getPosts: ActorMethod<[{ page: number; limit: number }], string[]>
12
- getItems: ActorMethod<
13
- [number],
14
- { items: string[]; nextCursor: number | null }
15
- >
16
- getMessages: ActorMethod<
17
- [string],
18
- { messages: string[]; nextToken: string | null }
19
- >
20
- }
21
-
22
- // Mock data generators
23
- const generatePosts = (page: number, limit: number): string[] => {
24
- const start = (page - 1) * limit
25
- return Array.from({ length: limit }, (_, i) => `Post ${start + i + 1}`)
26
- }
27
-
28
- const generateItems = (
29
- cursor: number
30
- ): { items: string[]; nextCursor: number | null } => {
31
- if (cursor >= 50) {
32
- return { items: [], nextCursor: null }
33
- }
34
- const items = Array.from({ length: 10 }, (_, i) => `Item ${cursor + i + 1}`)
35
- return { items, nextCursor: cursor + 10 }
36
- }
37
-
38
- // Mock Reactor
39
- const createMockReactor = (queryClient: QueryClient) => {
40
- const callMethod = vi
41
- .fn()
42
- .mockImplementation(async ({ functionName, args }) => {
43
- if (functionName === "getPosts") {
44
- const { page, limit } = args[0]
45
- return generatePosts(page, limit)
46
- }
47
- if (functionName === "getItems") {
48
- const cursor = args[0]
49
- return generateItems(cursor)
50
- }
51
- if (functionName === "getMessages") {
52
- const token = args[0]
53
- const pageNum = token === "start" ? 0 : parseInt(token, 10)
54
- const messages = Array.from(
55
- { length: 5 },
56
- (_, i) => `Message ${pageNum * 5 + i + 1}`
57
- )
58
- const nextToken = pageNum < 3 ? String(pageNum + 1) : null
59
- return { messages, nextToken }
60
- }
61
- return null
62
- })
63
-
64
- // Mock ensureInfiniteQueryData for suspense behavior if needed,
65
- // but useSuspenseInfiniteQuery handles its own suspense mechanism via options.
66
- // We just need to make sure callMethod returns a promise.
67
-
68
- return {
69
- queryClient,
70
- callMethod,
71
- generateQueryKey: vi
72
- .fn()
73
- .mockImplementation(({ functionName, args }) => [
74
- "test-canister",
75
- functionName,
76
- ...(args ? [JSON.stringify(args)] : []),
77
- ]),
78
- } as unknown as Reactor<TestActor>
79
- }
80
-
81
- describe("useActorSuspenseInfiniteQuery", () => {
82
- let queryClient: QueryClient
83
- let mockReactor: Reactor<TestActor>
84
-
85
- beforeEach(() => {
86
- queryClient = new QueryClient({
87
- defaultOptions: {
88
- queries: {
89
- retry: false,
90
- gcTime: 0,
91
- },
92
- },
93
- })
94
- mockReactor = createMockReactor(queryClient)
95
- })
96
-
97
- const wrapper = ({ children }: { children: React.ReactNode }) => (
98
- <QueryClientProvider client={queryClient}>
99
- <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
100
- </QueryClientProvider>
101
- )
102
-
103
- describe("basic functionality", () => {
104
- it("should fetch initial page", async () => {
105
- const { result } = renderHook(
106
- () =>
107
- useActorSuspenseInfiniteQuery({
108
- reactor: mockReactor,
109
- functionName: "getPosts",
110
- initialPageParam: 1,
111
- getArgs: (page) => [{ page, limit: 10 }] as const,
112
- getNextPageParam: (lastPage, allPages) => {
113
- if (lastPage.length < 10) return undefined
114
- return allPages.length + 1
115
- },
116
- }),
117
- { wrapper }
118
- )
119
-
120
- await waitFor(() => {
121
- expect(result.current.isSuccess).toBe(true)
122
- })
123
-
124
- expect(result.current.data.pages).toHaveLength(1)
125
- expect(result.current.data.pages[0]).toHaveLength(10)
126
- expect(result.current.data.pages[0][0]).toBe("Post 1")
127
- })
128
-
129
- it("should support fetchNextPage", async () => {
130
- const { result } = renderHook(
131
- () =>
132
- useActorSuspenseInfiniteQuery({
133
- reactor: mockReactor,
134
- functionName: "getPosts",
135
- initialPageParam: 1,
136
- getArgs: (page) => [{ page, limit: 10 }] as const,
137
- getNextPageParam: (lastPage, allPages) => {
138
- if (lastPage.length < 10) return undefined
139
- return allPages.length + 1
140
- },
141
- }),
142
- { wrapper }
143
- )
144
-
145
- await waitFor(() => {
146
- expect(result.current.isSuccess).toBe(true)
147
- })
148
-
149
- expect(result.current.hasNextPage).toBe(true)
150
-
151
- // fetchNextPage should be available and callable
152
- expect(typeof result.current.fetchNextPage).toBe("function")
153
- expect(result.current.isFetchingNextPage).toBe(false)
154
- })
155
-
156
- it("should call reactor.callMethod with correct args", async () => {
157
- const { result } = renderHook(
158
- () =>
159
- useActorSuspenseInfiniteQuery({
160
- reactor: mockReactor,
161
- functionName: "getPosts",
162
- initialPageParam: 1,
163
- getArgs: (page) => [{ page, limit: 5 }] as const,
164
- getNextPageParam: () => undefined,
165
- }),
166
- { wrapper }
167
- )
168
-
169
- await waitFor(() => {
170
- expect(result.current.isSuccess).toBe(true)
171
- })
172
-
173
- expect(mockReactor.callMethod).toHaveBeenCalledWith({
174
- functionName: "getPosts",
175
- args: [{ page: 1, limit: 5 }],
176
- callConfig: undefined,
177
- })
178
- })
179
- })
180
-
181
- describe("cursor-based pagination", () => {
182
- it("should work with cursor-based pagination", async () => {
183
- const { result } = renderHook(
184
- () =>
185
- useActorSuspenseInfiniteQuery<
186
- TestActor,
187
- "getItems",
188
- "candid",
189
- number
190
- >({
191
- reactor: mockReactor as unknown as Reactor<TestActor>,
192
- functionName: "getItems",
193
- initialPageParam: 0,
194
- getArgs: (cursor) => [cursor],
195
- getNextPageParam: (lastPage) => lastPage.nextCursor,
196
- }),
197
- { wrapper }
198
- )
199
-
200
- await waitFor(() => {
201
- expect(result.current.isSuccess).toBe(true)
202
- })
203
-
204
- expect(result.current.data.pages[0].items[0]).toBe("Item 1")
205
- expect(result.current.hasNextPage).toBe(true)
206
-
207
- await act(async () => {
208
- await result.current.fetchNextPage()
209
- })
210
-
211
- await waitFor(() => {
212
- expect(result.current.data.pages).toHaveLength(2)
213
- })
214
-
215
- expect(result.current.data.pages[1].items[0]).toBe("Item 11")
216
- })
217
-
218
- it("should determine hasNextPage based on getNextPageParam", async () => {
219
- // Create a mock that returns null nextCursor to indicate no more pages
220
- const noMorePagesMock = createMockReactor(
221
- queryClient
222
- ) as unknown as Reactor<TestActor>
223
- ;(
224
- noMorePagesMock.callMethod as ReturnType<typeof vi.fn>
225
- ).mockImplementation(async () => {
226
- return { items: ["Last Item"], nextCursor: null }
227
- })
228
-
229
- const { result } = renderHook(
230
- () =>
231
- useActorSuspenseInfiniteQuery<
232
- TestActor,
233
- "getItems",
234
- "candid",
235
- number
236
- >({
237
- reactor: noMorePagesMock,
238
- functionName: "getItems",
239
- initialPageParam: 0,
240
- getArgs: (cursor) => [cursor],
241
- getNextPageParam: (lastPage) => lastPage.nextCursor,
242
- }),
243
- { wrapper }
244
- )
245
-
246
- await waitFor(() => {
247
- expect(result.current.isSuccess).toBe(true)
248
- })
249
-
250
- // When getNextPageParam returns null, hasNextPage should be false
251
- expect(result.current.hasNextPage).toBe(false)
252
- })
253
- })
254
-
255
- describe("token-based pagination", () => {
256
- it("should work with string token pagination", async () => {
257
- const { result } = renderHook(
258
- () =>
259
- useActorSuspenseInfiniteQuery({
260
- reactor: mockReactor,
261
- functionName: "getMessages",
262
- initialPageParam: "start",
263
- getArgs: (token) => [token] as const,
264
- getNextPageParam: (lastPage) => lastPage.nextToken,
265
- }),
266
- { wrapper }
267
- )
268
-
269
- await waitFor(() => {
270
- expect(result.current.isSuccess).toBe(true)
271
- })
272
-
273
- expect(result.current.data.pages[0].messages[0]).toBe("Message 1")
274
- expect(result.current.data.pages[0].messages[1]).toBe("Message 2")
275
- })
276
- })
277
-
278
- describe("query options", () => {
279
- it("should use custom staleTime", async () => {
280
- const { result } = renderHook(
281
- () =>
282
- useActorSuspenseInfiniteQuery({
283
- reactor: mockReactor,
284
- functionName: "getPosts",
285
- initialPageParam: 1,
286
- getArgs: (page) => [{ page, limit: 10 }] as const,
287
- getNextPageParam: () => undefined,
288
- staleTime: 12345,
289
- }),
290
- { wrapper }
291
- )
292
-
293
- await waitFor(() => {
294
- expect(result.current.isSuccess).toBe(true)
295
- })
296
-
297
- const queryState = queryClient.getQueryCache().find({
298
- queryKey: result.current.data ? ["test-canister", "getPosts"] : [],
299
- })
300
-
301
- expect(queryState).toBeDefined()
302
-
303
- // Need to find the exact query. The mock key generator produces ["test-canister", "getPosts", args...]
304
- // But queryKey logic in hook adds parameters.
305
- // Actually mock generateQueryKey adds args if present, but the hook passes method only to generateQueryKey
306
- // `const baseQueryKey = queryKey ?? reactor.generateQueryKey({ method })`
307
- // So the key should be ["test-canister", "getPosts"].
308
- // Wait, `useInfiniteQuery` appends parameters? No, `queryKey` is fixed base, `pageParam` handled internally.
309
-
310
- const queries = queryClient.getQueryCache().findAll()
311
- const query = queries[0]
312
- expect(query.observers[0].options.staleTime).toBe(12345)
313
- })
314
- })
315
-
316
- describe("getPreviousPageParam", () => {
317
- it("should support bi-directional pagination", async () => {
318
- const { result } = renderHook(
319
- () =>
320
- useActorSuspenseInfiniteQuery({
321
- reactor: mockReactor,
322
- functionName: "getPosts",
323
- initialPageParam: 3,
324
- getArgs: (page) => [{ page, limit: 10 }] as const,
325
- getNextPageParam: (lastPage, allPages) => {
326
- if (lastPage.length < 10) return undefined
327
- const lastPageParam = allPages.length + 2 // Started at page 3
328
- return lastPageParam + 1
329
- },
330
- getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => {
331
- if (firstPageParam <= 1) return undefined
332
- return firstPageParam - 1
333
- },
334
- }),
335
- { wrapper }
336
- )
337
-
338
- await waitFor(() => {
339
- expect(result.current.isSuccess).toBe(true)
340
- })
341
-
342
- // Started at page 3, should have previous pages
343
- expect(result.current.hasPreviousPage).toBe(true)
344
- })
345
- })
346
-
347
- describe("select function", () => {
348
- it("should apply select transformation to infinite data", async () => {
349
- const { result } = renderHook(
350
- () =>
351
- useActorSuspenseInfiniteQuery({
352
- reactor: mockReactor,
353
- functionName: "getPosts",
354
- initialPageParam: 1,
355
- getArgs: (page) => [{ page, limit: 10 }] as const,
356
- getNextPageParam: () => undefined,
357
- select: (data) => ({
358
- ...data,
359
- pages: data.pages.map((page) =>
360
- page.map((post) => post.toUpperCase())
361
- ),
362
- }),
363
- }),
364
- { wrapper }
365
- )
366
-
367
- await waitFor(() => {
368
- expect(result.current.isSuccess).toBe(true)
369
- })
370
-
371
- expect(result.current.data.pages[0][0]).toBe("POST 1")
372
- })
373
- })
374
-
375
- describe("refetching", () => {
376
- it("should support manual refetch", async () => {
377
- const { result } = renderHook(
378
- () =>
379
- useActorSuspenseInfiniteQuery({
380
- reactor: mockReactor,
381
- functionName: "getPosts",
382
- initialPageParam: 1,
383
- getArgs: (page) => [{ page, limit: 10 }] as const,
384
- getNextPageParam: () => undefined,
385
- }),
386
- { wrapper }
387
- )
388
-
389
- await waitFor(() => {
390
- expect(result.current.isSuccess).toBe(true)
391
- })
392
-
393
- const initialCallCount = (
394
- mockReactor.callMethod as ReturnType<typeof vi.fn>
395
- ).mock.calls.length
396
-
397
- await act(async () => {
398
- await result.current.refetch()
399
- })
400
-
401
- expect(
402
- (mockReactor.callMethod as ReturnType<typeof vi.fn>).mock.calls.length
403
- ).toBeGreaterThan(initialCallCount)
404
- })
405
- })
406
-
407
- describe("maxPages option", () => {
408
- it("should accept maxPages option", async () => {
409
- const { result } = renderHook(
410
- () =>
411
- useActorSuspenseInfiniteQuery({
412
- reactor: mockReactor,
413
- functionName: "getPosts",
414
- initialPageParam: 1,
415
- getArgs: (page) => [{ page, limit: 10 }] as const,
416
- getNextPageParam: (lastPage, allPages) => {
417
- if (lastPage.length < 10) return undefined
418
- return allPages.length + 1
419
- },
420
- maxPages: 2,
421
- }),
422
- { wrapper }
423
- )
424
-
425
- await waitFor(() => {
426
- expect(result.current.isSuccess).toBe(true)
427
- })
428
-
429
- // Initial page should be loaded
430
- expect(result.current.data.pages).toHaveLength(1)
431
- // maxPages option should be respected by tanstack query
432
- expect(result.current.hasNextPage).toBe(true)
433
- })
434
- })
435
-
436
- describe("data structure", () => {
437
- it("should return data in InfiniteData format", async () => {
438
- const { result } = renderHook(
439
- () =>
440
- useActorSuspenseInfiniteQuery({
441
- reactor: mockReactor,
442
- functionName: "getPosts",
443
- initialPageParam: 1,
444
- getArgs: (page) => [{ page, limit: 10 }] as const,
445
- getNextPageParam: (lastPage, allPages) => {
446
- if (lastPage.length < 10) return undefined
447
- return allPages.length + 1
448
- },
449
- }),
450
- { wrapper }
451
- )
452
-
453
- await waitFor(() => {
454
- expect(result.current.isSuccess).toBe(true)
455
- })
456
-
457
- // Data should have pages array and pageParams array
458
- expect(result.current.data).toBeDefined()
459
- expect(Array.isArray(result.current.data.pages)).toBe(true)
460
- expect(Array.isArray(result.current.data.pageParams)).toBe(true)
461
-
462
- // Initial page should be loaded
463
- expect(result.current.data.pages).toHaveLength(1)
464
- expect(result.current.data.pages[0]).toHaveLength(10)
465
-
466
- // Pages can be flattened
467
- const allPosts = result.current.data.pages.flat()
468
- expect(allPosts).toHaveLength(10)
469
- expect(allPosts?.[0]).toBe("Post 1")
470
- })
471
- })
472
- })