@ic-reactor/react 3.7.0 → 3.9.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 +158 -9
- package/dist/auth/authentication-manager.d.ts +37 -5
- package/dist/auth/authentication-manager.d.ts.map +1 -1
- package/dist/auth/authentication-manager.js +242 -52
- package/dist/auth/authentication-manager.js.map +1 -1
- package/dist/auth/createIdentityAttributeHooks.d.ts +2 -2
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.d.ts +12 -5
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.js +42 -28
- package/dist/auth/identity-attributes-manager.js.map +1 -1
- package/dist/auth/identity-attributes.d.ts +1 -1
- package/dist/auth/identity-attributes.d.ts.map +1 -1
- package/dist/auth/identity-attributes.js +1 -1
- package/dist/auth/identity-attributes.js.map +1 -1
- package/dist/auth/index.d.ts +7 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +7 -7
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/types.d.ts +123 -16
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/createActorHooks.d.ts +4 -4
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js +6 -6
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts +12 -10
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +20 -17
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts +1 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +22 -2
- package/dist/createMutation.js.map +1 -1
- package/dist/createQuery.d.ts +1 -1
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +6 -2
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +11 -10
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +18 -16
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts +1 -1
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js +6 -2
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/defineReactor.d.ts +54 -4
- package/dist/defineReactor.d.ts.map +1 -1
- package/dist/defineReactor.js +77 -7
- package/dist/defineReactor.js.map +1 -1
- package/dist/hooks/createAuthHooks.d.ts +2 -2
- package/dist/hooks/createAuthHooks.d.ts.map +1 -1
- package/dist/hooks/createAuthHooks.js +7 -2
- package/dist/hooks/createAuthHooks.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +16 -2
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts.map +1 -1
- package/dist/hooks/useActorMethod.js +66 -33
- package/dist/hooks/useActorMethod.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts +8 -1
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +9 -1
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +16 -2
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +14 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +21 -10
- package/dist/utils.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +14 -9
- package/src/auth/authentication-manager.ts +267 -64
- package/src/auth/createIdentityAttributeHooks.ts +2 -2
- package/src/auth/identity-attributes-manager.ts +53 -44
- package/src/auth/identity-attributes.ts +2 -2
- package/src/auth/index.ts +7 -7
- package/src/auth/types.ts +134 -18
- package/src/createActorHooks.ts +9 -9
- package/src/createInfiniteQuery.ts +49 -35
- package/src/createMutation.ts +33 -3
- package/src/createQuery.ts +10 -3
- package/src/createSuspenseInfiniteQuery.ts +45 -33
- package/src/createSuspenseQuery.ts +10 -3
- package/src/defineReactor.ts +136 -14
- package/src/hooks/createAuthHooks.ts +8 -3
- package/src/hooks/useActorInfiniteQuery.ts +25 -3
- package/src/hooks/useActorMethod.ts +82 -34
- package/src/hooks/useActorMutation.ts +19 -4
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +25 -3
- package/src/index.ts +11 -11
- package/src/types.ts +8 -2
- package/src/utils.ts +21 -10
- package/src/defineReactor.test.ts +0 -70
- package/src/hooks/useActorInfiniteQuery.test.tsx +0 -457
- package/src/hooks/useActorMethod.test.tsx +0 -798
- package/src/hooks/useActorMutation.test.tsx +0 -220
- package/src/hooks/useActorQuery.test.tsx +0 -287
- package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +0 -472
- package/src/hooks/useActorSuspenseQuery.test.tsx +0 -254
- package/src/utils.test.ts +0 -14
package/src/utils.ts
CHANGED
|
@@ -46,20 +46,31 @@ export function mergeFactoryQueryKey(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Build a chained select
|
|
50
|
-
*
|
|
49
|
+
* Build a chained select that applies the config-level select (if any) and then
|
|
50
|
+
* the hook-level select (if any).
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Returns the caller's own function untouched when only one of the two is
|
|
53
|
+
* present, and `undefined` when neither is — allocating a wrapper only for the
|
|
54
|
+
* genuinely chained case. Identity matters here: `QueryObserver` memoizes a
|
|
55
|
+
* select result on `options.select === previousSelectFn`, so a wrapper rebuilt
|
|
56
|
+
* on every render defeats that check. The select then re-runs each render, and
|
|
57
|
+
* for a select returning a non-plain value (a `Map`, a `Date`, a `Principal`)
|
|
58
|
+
* `replaceEqualDeep` cannot structurally share the result either, so `data`
|
|
59
|
+
* gets a fresh reference every render and a `useEffect([data])` that sets state
|
|
60
|
+
* becomes an unbounded loop.
|
|
61
|
+
*
|
|
62
|
+
* Callers should still memoize the result, since the chained case allocates.
|
|
54
63
|
*/
|
|
55
64
|
export function buildChainedSelect<TData, TSelected, TFinal = TSelected>(
|
|
56
65
|
configSelect: ((data: TData) => TSelected) | undefined,
|
|
57
66
|
hookSelect: ((data: TSelected) => TFinal) | undefined
|
|
58
|
-
): (rawData: TData) => TSelected | TFinal {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
): ((rawData: TData) => TSelected | TFinal) | undefined {
|
|
68
|
+
if (!configSelect) {
|
|
69
|
+
// `hookSelect` receives the raw data when there is no config-level select,
|
|
70
|
+
// matching the previous behaviour.
|
|
71
|
+
return hookSelect as unknown as
|
|
72
|
+
((rawData: TData) => TSelected | TFinal) | undefined
|
|
64
73
|
}
|
|
74
|
+
if (!hookSelect) return configSelect
|
|
75
|
+
return (rawData: TData) => hookSelect(configSelect(rawData))
|
|
65
76
|
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import { ClientManager, Reactor, DisplayReactor } from "@ic-reactor/core"
|
|
3
|
-
import { QueryClient } from "@tanstack/react-query"
|
|
4
|
-
import { defineReactor } from "./defineReactor"
|
|
5
|
-
import { ActorMethod } from "@icp-sdk/core/agent"
|
|
6
|
-
|
|
7
|
-
const idlFactory = ({ IDL }: any) =>
|
|
8
|
-
IDL.Service({
|
|
9
|
-
greet: IDL.Func([IDL.Text], [IDL.Text], ["query"]),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
interface TestActor {
|
|
13
|
-
greet: ActorMethod<[string], string>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const baseConfig = {
|
|
17
|
-
name: "test",
|
|
18
|
-
idlFactory,
|
|
19
|
-
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("defineReactor", () => {
|
|
23
|
-
it("bootstraps a standard Reactor with hooks and infrastructure", () => {
|
|
24
|
-
const result = defineReactor<TestActor>(baseConfig)
|
|
25
|
-
|
|
26
|
-
expect(result.reactor).toBeInstanceOf(Reactor)
|
|
27
|
-
expect(result.reactor).not.toBeInstanceOf(DisplayReactor)
|
|
28
|
-
expect(result.clientManager).toBeInstanceOf(ClientManager)
|
|
29
|
-
expect(result.queryClient).toBeInstanceOf(QueryClient)
|
|
30
|
-
expect(typeof result.useActorQuery).toBe("function")
|
|
31
|
-
expect(typeof result.useActorMutation).toBe("function")
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it("creates a DisplayReactor when display is true", () => {
|
|
35
|
-
const result = defineReactor<TestActor>({ ...baseConfig, display: true })
|
|
36
|
-
|
|
37
|
-
expect(result.reactor).toBeInstanceOf(DisplayReactor)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("wires the reactor to the created ClientManager and QueryClient", () => {
|
|
41
|
-
const { reactor, clientManager, queryClient } =
|
|
42
|
-
defineReactor<TestActor>(baseConfig)
|
|
43
|
-
|
|
44
|
-
expect(reactor.clientManager).toBe(clientManager)
|
|
45
|
-
expect(clientManager.queryClient).toBe(queryClient)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it("reuses an existing ClientManager and its QueryClient", () => {
|
|
49
|
-
const queryClient = new QueryClient()
|
|
50
|
-
const clientManager = new ClientManager({ queryClient })
|
|
51
|
-
|
|
52
|
-
const result = defineReactor<TestActor>({ ...baseConfig, clientManager })
|
|
53
|
-
|
|
54
|
-
expect(result.clientManager).toBe(clientManager)
|
|
55
|
-
expect(result.queryClient).toBe(queryClient)
|
|
56
|
-
expect(result.reactor.clientManager).toBe(clientManager)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it("lets multiple reactors share one ClientManager", () => {
|
|
60
|
-
const ledger = defineReactor<TestActor>({ ...baseConfig, name: "ledger" })
|
|
61
|
-
const index = defineReactor<TestActor>({
|
|
62
|
-
...baseConfig,
|
|
63
|
-
name: "index",
|
|
64
|
-
clientManager: ledger.clientManager,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
expect(index.clientManager).toBe(ledger.clientManager)
|
|
68
|
-
expect(index.reactor).not.toBe(ledger.reactor)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
-
import { renderHook, waitFor, act } from "@testing-library/react"
|
|
3
|
-
import React from "react"
|
|
4
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
|
5
|
-
import { useActorInfiniteQuery } from "./useActorInfiniteQuery"
|
|
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
|
-
return {
|
|
65
|
-
queryClient,
|
|
66
|
-
callMethod,
|
|
67
|
-
generateQueryKey: vi
|
|
68
|
-
.fn()
|
|
69
|
-
.mockImplementation(({ functionName, args }) => [
|
|
70
|
-
"test-canister",
|
|
71
|
-
functionName,
|
|
72
|
-
...(args ? [JSON.stringify(args)] : []),
|
|
73
|
-
]),
|
|
74
|
-
} as unknown as Reactor<TestActor>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
describe("useActorInfiniteQuery", () => {
|
|
78
|
-
let queryClient: QueryClient
|
|
79
|
-
let mockReactor: ReturnType<typeof createMockReactor>
|
|
80
|
-
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
queryClient = new QueryClient({
|
|
83
|
-
defaultOptions: {
|
|
84
|
-
queries: {
|
|
85
|
-
retry: false,
|
|
86
|
-
gcTime: 0,
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
mockReactor = createMockReactor(queryClient)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
94
|
-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
describe("basic functionality", () => {
|
|
98
|
-
it("should fetch initial page", async () => {
|
|
99
|
-
const { result } = renderHook(
|
|
100
|
-
() =>
|
|
101
|
-
useActorInfiniteQuery({
|
|
102
|
-
reactor: mockReactor,
|
|
103
|
-
functionName: "getPosts",
|
|
104
|
-
initialPageParam: 1,
|
|
105
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
106
|
-
getNextPageParam: (lastPage, allPages) => {
|
|
107
|
-
if (lastPage.length < 10) return undefined
|
|
108
|
-
return allPages.length + 1
|
|
109
|
-
},
|
|
110
|
-
}),
|
|
111
|
-
{ wrapper }
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
await waitFor(() => {
|
|
115
|
-
expect(result.current.isSuccess).toBe(true)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
expect(result.current.data?.pages).toHaveLength(1)
|
|
119
|
-
expect(result.current.data?.pages[0]).toHaveLength(10)
|
|
120
|
-
expect(result.current.data?.pages[0][0]).toBe("Post 1")
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it("should support fetchNextPage", async () => {
|
|
124
|
-
const { result } = renderHook(
|
|
125
|
-
() =>
|
|
126
|
-
useActorInfiniteQuery({
|
|
127
|
-
reactor: mockReactor,
|
|
128
|
-
functionName: "getPosts",
|
|
129
|
-
initialPageParam: 1,
|
|
130
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
131
|
-
getNextPageParam: (lastPage, allPages) => {
|
|
132
|
-
if (lastPage.length < 10) return undefined
|
|
133
|
-
return allPages.length + 1
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
{ wrapper }
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
await waitFor(() => {
|
|
140
|
-
expect(result.current.isSuccess).toBe(true)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
expect(result.current.hasNextPage).toBe(true)
|
|
144
|
-
|
|
145
|
-
// fetchNextPage should be available and callable
|
|
146
|
-
expect(typeof result.current.fetchNextPage).toBe("function")
|
|
147
|
-
expect(result.current.isFetchingNextPage).toBe(false)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it("should call reactor.callMethod with correct args", async () => {
|
|
151
|
-
const { result } = renderHook(
|
|
152
|
-
() =>
|
|
153
|
-
useActorInfiniteQuery({
|
|
154
|
-
reactor: mockReactor,
|
|
155
|
-
functionName: "getPosts",
|
|
156
|
-
initialPageParam: 1,
|
|
157
|
-
getArgs: (page) => [{ page, limit: 5 }] as const,
|
|
158
|
-
getNextPageParam: () => undefined,
|
|
159
|
-
}),
|
|
160
|
-
{ wrapper }
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
await waitFor(() => {
|
|
164
|
-
expect(result.current.isSuccess).toBe(true)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
expect(mockReactor.callMethod).toHaveBeenCalledWith({
|
|
168
|
-
functionName: "getPosts",
|
|
169
|
-
args: [{ page: 1, limit: 5 }],
|
|
170
|
-
callConfig: undefined,
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
describe("cursor-based pagination", () => {
|
|
176
|
-
it("should work with cursor-based pagination", async () => {
|
|
177
|
-
const { result } = renderHook(
|
|
178
|
-
() =>
|
|
179
|
-
useActorInfiniteQuery<TestActor, "getItems", "candid", number>({
|
|
180
|
-
reactor: mockReactor as unknown as Reactor<TestActor, "candid">,
|
|
181
|
-
functionName: "getItems",
|
|
182
|
-
initialPageParam: 0,
|
|
183
|
-
getArgs: (cursor) => [cursor],
|
|
184
|
-
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
185
|
-
}),
|
|
186
|
-
{ wrapper }
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
await waitFor(() => {
|
|
190
|
-
expect(result.current.isSuccess).toBe(true)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
expect(result.current.data?.pages[0].items[0]).toBe("Item 1")
|
|
194
|
-
expect(result.current.hasNextPage).toBe(true)
|
|
195
|
-
|
|
196
|
-
await act(async () => {
|
|
197
|
-
await result.current.fetchNextPage()
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
await waitFor(() => {
|
|
201
|
-
expect(result.current.data?.pages).toHaveLength(2)
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
expect(result.current.data?.pages[1].items[0]).toBe("Item 11")
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
it("should determine hasNextPage based on getNextPageParam", async () => {
|
|
208
|
-
// Create a mock that returns null nextCursor to indicate no more pages
|
|
209
|
-
const noMorePagesMock = createMockReactor(queryClient)
|
|
210
|
-
;(
|
|
211
|
-
noMorePagesMock.callMethod as ReturnType<typeof vi.fn>
|
|
212
|
-
).mockImplementation(async () => {
|
|
213
|
-
return { items: ["Last Item"], nextCursor: null }
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
const { result } = renderHook(
|
|
217
|
-
() =>
|
|
218
|
-
useActorInfiniteQuery<TestActor, "getItems", "candid", number>({
|
|
219
|
-
reactor: noMorePagesMock as unknown as Reactor<TestActor, "candid">,
|
|
220
|
-
functionName: "getItems",
|
|
221
|
-
initialPageParam: 0,
|
|
222
|
-
getArgs: (cursor) => [cursor],
|
|
223
|
-
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
224
|
-
}),
|
|
225
|
-
{ wrapper }
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
await waitFor(() => {
|
|
229
|
-
expect(result.current.isSuccess).toBe(true)
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
// When getNextPageParam returns null, hasNextPage should be false
|
|
233
|
-
expect(result.current.hasNextPage).toBe(false)
|
|
234
|
-
})
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
describe("token-based pagination", () => {
|
|
238
|
-
it("should work with string token pagination", async () => {
|
|
239
|
-
const { result } = renderHook(
|
|
240
|
-
() =>
|
|
241
|
-
useActorInfiniteQuery<TestActor, "getMessages", "candid", string>({
|
|
242
|
-
reactor: mockReactor as unknown as Reactor<TestActor, "candid">,
|
|
243
|
-
functionName: "getMessages",
|
|
244
|
-
initialPageParam: "start",
|
|
245
|
-
getArgs: (token) => [token],
|
|
246
|
-
getNextPageParam: (lastPage) => lastPage.nextToken,
|
|
247
|
-
}),
|
|
248
|
-
{ wrapper }
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
await waitFor(() => {
|
|
252
|
-
expect(result.current.isSuccess).toBe(true)
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
expect(result.current.data?.pages[0].messages[0]).toBe("Message 1")
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
describe("query options", () => {
|
|
260
|
-
it("should respect enabled: false option", async () => {
|
|
261
|
-
const { result } = renderHook(
|
|
262
|
-
() =>
|
|
263
|
-
useActorInfiniteQuery({
|
|
264
|
-
reactor: mockReactor,
|
|
265
|
-
functionName: "getPosts",
|
|
266
|
-
initialPageParam: 1,
|
|
267
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
268
|
-
getNextPageParam: () => undefined,
|
|
269
|
-
enabled: false,
|
|
270
|
-
}),
|
|
271
|
-
{ wrapper }
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
expect(result.current.isPending).toBe(true)
|
|
275
|
-
expect(result.current.fetchStatus).toBe("idle")
|
|
276
|
-
expect(mockReactor.callMethod).not.toHaveBeenCalled()
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it("should use custom staleTime", async () => {
|
|
280
|
-
const { result } = renderHook(
|
|
281
|
-
() =>
|
|
282
|
-
useActorInfiniteQuery({
|
|
283
|
-
reactor: mockReactor,
|
|
284
|
-
functionName: "getPosts",
|
|
285
|
-
initialPageParam: 1,
|
|
286
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
287
|
-
getNextPageParam: () => undefined,
|
|
288
|
-
staleTime: 1000 * 60 * 10, // 10 minutes
|
|
289
|
-
}),
|
|
290
|
-
{ wrapper }
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
await waitFor(() => {
|
|
294
|
-
expect(result.current.isSuccess).toBe(true)
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
expect(result.current.isStale).toBe(false)
|
|
298
|
-
})
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
describe("getPreviousPageParam", () => {
|
|
302
|
-
it("should support bi-directional pagination", async () => {
|
|
303
|
-
const { result } = renderHook(
|
|
304
|
-
() =>
|
|
305
|
-
useActorInfiniteQuery({
|
|
306
|
-
reactor: mockReactor,
|
|
307
|
-
functionName: "getPosts",
|
|
308
|
-
initialPageParam: 3,
|
|
309
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
310
|
-
getNextPageParam: (lastPage, allPages) => {
|
|
311
|
-
if (lastPage.length < 10) return undefined
|
|
312
|
-
const lastPageParam = allPages.length + 2 // Started at page 3
|
|
313
|
-
return lastPageParam + 1
|
|
314
|
-
},
|
|
315
|
-
getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => {
|
|
316
|
-
if (firstPageParam <= 1) return undefined
|
|
317
|
-
return firstPageParam - 1
|
|
318
|
-
},
|
|
319
|
-
}),
|
|
320
|
-
{ wrapper }
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
await waitFor(() => {
|
|
324
|
-
expect(result.current.isSuccess).toBe(true)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
// Started at page 3, should have previous pages
|
|
328
|
-
expect(result.current.hasPreviousPage).toBe(true)
|
|
329
|
-
})
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
describe("select function", () => {
|
|
333
|
-
it("should apply select transformation to infinite data", async () => {
|
|
334
|
-
const { result } = renderHook(
|
|
335
|
-
() =>
|
|
336
|
-
useActorInfiniteQuery({
|
|
337
|
-
reactor: mockReactor,
|
|
338
|
-
functionName: "getPosts",
|
|
339
|
-
initialPageParam: 1,
|
|
340
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
341
|
-
getNextPageParam: () => undefined,
|
|
342
|
-
select: (data) => ({
|
|
343
|
-
...data,
|
|
344
|
-
pages: data.pages.map((page) =>
|
|
345
|
-
page.map((post) => post.toUpperCase())
|
|
346
|
-
),
|
|
347
|
-
}),
|
|
348
|
-
}),
|
|
349
|
-
{ wrapper }
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
await waitFor(() => {
|
|
353
|
-
expect(result.current.isSuccess).toBe(true)
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
expect(result.current.data?.pages[0][0]).toBe("POST 1")
|
|
357
|
-
})
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
describe("refetching", () => {
|
|
361
|
-
it("should support manual refetch", async () => {
|
|
362
|
-
const { result } = renderHook(
|
|
363
|
-
() =>
|
|
364
|
-
useActorInfiniteQuery({
|
|
365
|
-
reactor: mockReactor,
|
|
366
|
-
functionName: "getPosts",
|
|
367
|
-
initialPageParam: 1,
|
|
368
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
369
|
-
getNextPageParam: () => undefined,
|
|
370
|
-
}),
|
|
371
|
-
{ wrapper }
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
await waitFor(() => {
|
|
375
|
-
expect(result.current.isSuccess).toBe(true)
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
const initialCallCount = (
|
|
379
|
-
mockReactor.callMethod as ReturnType<typeof vi.fn>
|
|
380
|
-
).mock.calls.length
|
|
381
|
-
|
|
382
|
-
await act(async () => {
|
|
383
|
-
await result.current.refetch()
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
expect(
|
|
387
|
-
(mockReactor.callMethod as ReturnType<typeof vi.fn>).mock.calls.length
|
|
388
|
-
).toBeGreaterThan(initialCallCount)
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
describe("maxPages option", () => {
|
|
393
|
-
it("should accept maxPages option", async () => {
|
|
394
|
-
const { result } = renderHook(
|
|
395
|
-
() =>
|
|
396
|
-
useActorInfiniteQuery({
|
|
397
|
-
reactor: mockReactor,
|
|
398
|
-
functionName: "getPosts",
|
|
399
|
-
initialPageParam: 1,
|
|
400
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
401
|
-
getNextPageParam: (lastPage, allPages) => {
|
|
402
|
-
if (lastPage.length < 10) return undefined
|
|
403
|
-
return allPages.length + 1
|
|
404
|
-
},
|
|
405
|
-
maxPages: 2,
|
|
406
|
-
}),
|
|
407
|
-
{ wrapper }
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
await waitFor(() => {
|
|
411
|
-
expect(result.current.isSuccess).toBe(true)
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
// Initial page should be loaded
|
|
415
|
-
expect(result.current.data?.pages).toHaveLength(1)
|
|
416
|
-
// maxPages option should be respected by tanstack query
|
|
417
|
-
expect(result.current.hasNextPage).toBe(true)
|
|
418
|
-
})
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
describe("data structure", () => {
|
|
422
|
-
it("should return data in InfiniteData format", async () => {
|
|
423
|
-
const { result } = renderHook(
|
|
424
|
-
() =>
|
|
425
|
-
useActorInfiniteQuery({
|
|
426
|
-
reactor: mockReactor,
|
|
427
|
-
functionName: "getPosts",
|
|
428
|
-
initialPageParam: 1,
|
|
429
|
-
getArgs: (page) => [{ page, limit: 10 }] as const,
|
|
430
|
-
getNextPageParam: (lastPage, allPages) => {
|
|
431
|
-
if (lastPage.length < 10) return undefined
|
|
432
|
-
return allPages.length + 1
|
|
433
|
-
},
|
|
434
|
-
}),
|
|
435
|
-
{ wrapper }
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
await waitFor(() => {
|
|
439
|
-
expect(result.current.isSuccess).toBe(true)
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
// Data should have pages array and pageParams array
|
|
443
|
-
expect(result.current.data).toBeDefined()
|
|
444
|
-
expect(Array.isArray(result.current.data?.pages)).toBe(true)
|
|
445
|
-
expect(Array.isArray(result.current.data?.pageParams)).toBe(true)
|
|
446
|
-
|
|
447
|
-
// Initial page should be loaded
|
|
448
|
-
expect(result.current.data?.pages).toHaveLength(1)
|
|
449
|
-
expect(result.current.data?.pages[0]).toHaveLength(10)
|
|
450
|
-
|
|
451
|
-
// Pages can be flattened
|
|
452
|
-
const allPosts = result.current.data?.pages.flat()
|
|
453
|
-
expect(allPosts).toHaveLength(10)
|
|
454
|
-
expect(allPosts?.[0]).toBe("Post 1")
|
|
455
|
-
})
|
|
456
|
-
})
|
|
457
|
-
})
|