@sanity/sdk-react 0.0.0-alpha.20 → 0.0.0-alpha.4

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 (97) hide show
  1. package/README.md +73 -34
  2. package/dist/_chunks-es/context.js +8 -0
  3. package/dist/_chunks-es/context.js.map +1 -0
  4. package/dist/_chunks-es/useLogOut.js +44 -0
  5. package/dist/_chunks-es/useLogOut.js.map +1 -0
  6. package/dist/components.d.ts +83 -0
  7. package/dist/components.js +132 -0
  8. package/dist/components.js.map +1 -0
  9. package/dist/context.d.ts +39 -0
  10. package/dist/context.js +5 -0
  11. package/dist/context.js.map +1 -0
  12. package/dist/hooks.d.ts +277 -0
  13. package/dist/hooks.js +153 -0
  14. package/dist/hooks.js.map +1 -0
  15. package/dist/index.d.ts +2 -4742
  16. package/dist/index.js +2 -1054
  17. package/dist/index.js.map +1 -1
  18. package/package.json +43 -22
  19. package/src/_exports/components.ts +2 -0
  20. package/src/_exports/context.ts +2 -0
  21. package/src/_exports/hooks.ts +21 -0
  22. package/src/_exports/index.ts +10 -66
  23. package/src/components/Login/LoginLinks.test.tsx +3 -2
  24. package/src/components/SanityApp.test.tsx +2 -104
  25. package/src/components/SanityApp.tsx +16 -80
  26. package/src/components/auth/AuthBoundary.test.tsx +10 -4
  27. package/src/components/auth/AuthBoundary.tsx +2 -18
  28. package/src/components/auth/Login.test.tsx +9 -2
  29. package/src/components/auth/Login.tsx +0 -1
  30. package/src/components/auth/LoginCallback.test.tsx +9 -2
  31. package/src/components/auth/LoginError.test.tsx +10 -2
  32. package/src/components/auth/LoginFooter.test.tsx +9 -2
  33. package/src/components/auth/LoginLayout.test.tsx +9 -2
  34. package/src/context/SanityProvider.test.tsx +1 -1
  35. package/src/context/SanityProvider.tsx +13 -21
  36. package/src/hooks/auth/useAuthState.tsx +5 -4
  37. package/src/hooks/auth/useAuthToken.tsx +1 -1
  38. package/src/hooks/auth/useCurrentUser.tsx +4 -27
  39. package/src/hooks/auth/useHandleCallback.tsx +0 -1
  40. package/src/hooks/auth/useLogOut.tsx +1 -1
  41. package/src/hooks/auth/useLoginUrls.tsx +0 -1
  42. package/src/hooks/client/useClient.test.tsx +130 -0
  43. package/src/hooks/client/useClient.ts +30 -8
  44. package/src/hooks/comlink/useFrameConnection.test.tsx +10 -55
  45. package/src/hooks/comlink/useFrameConnection.ts +47 -43
  46. package/src/hooks/comlink/useWindowConnection.test.ts +12 -53
  47. package/src/hooks/comlink/useWindowConnection.ts +33 -73
  48. package/src/hooks/context/useSanityInstance.test.tsx +1 -1
  49. package/src/hooks/context/useSanityInstance.ts +7 -23
  50. package/src/hooks/documentCollection/useDocuments.test.ts +130 -0
  51. package/src/hooks/documentCollection/useDocuments.ts +87 -0
  52. package/src/hooks/helpers/createCallbackHook.tsx +2 -3
  53. package/src/hooks/helpers/createStateSourceHook.test.tsx +0 -66
  54. package/src/hooks/helpers/createStateSourceHook.tsx +10 -29
  55. package/src/hooks/preview/usePreview.test.tsx +10 -19
  56. package/src/hooks/preview/usePreview.tsx +12 -66
  57. package/src/components/SDKProvider.test.tsx +0 -79
  58. package/src/components/SDKProvider.tsx +0 -42
  59. package/src/components/auth/authTestHelpers.tsx +0 -11
  60. package/src/components/utils.ts +0 -22
  61. package/src/context/SanityInstanceContext.ts +0 -4
  62. package/src/hooks/_synchronous-groq-js.mjs +0 -4
  63. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +0 -42
  64. package/src/hooks/auth/useDashboardOrganizationId.tsx +0 -29
  65. package/src/hooks/comlink/useManageFavorite.test.ts +0 -106
  66. package/src/hooks/comlink/useManageFavorite.ts +0 -101
  67. package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +0 -77
  68. package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +0 -79
  69. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +0 -97
  70. package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +0 -274
  71. package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +0 -91
  72. package/src/hooks/datasets/useDatasets.ts +0 -37
  73. package/src/hooks/document/useApplyActions.test.ts +0 -25
  74. package/src/hooks/document/useApplyActions.ts +0 -74
  75. package/src/hooks/document/useDocument.test.ts +0 -81
  76. package/src/hooks/document/useDocument.ts +0 -107
  77. package/src/hooks/document/useDocumentEvent.test.ts +0 -63
  78. package/src/hooks/document/useDocumentEvent.ts +0 -54
  79. package/src/hooks/document/useDocumentSyncStatus.test.ts +0 -16
  80. package/src/hooks/document/useDocumentSyncStatus.ts +0 -30
  81. package/src/hooks/document/useEditDocument.test.ts +0 -179
  82. package/src/hooks/document/useEditDocument.ts +0 -195
  83. package/src/hooks/document/usePermissions.ts +0 -82
  84. package/src/hooks/infiniteList/useInfiniteList.test.tsx +0 -152
  85. package/src/hooks/infiniteList/useInfiniteList.ts +0 -174
  86. package/src/hooks/paginatedList/usePaginatedList.test.tsx +0 -259
  87. package/src/hooks/paginatedList/usePaginatedList.ts +0 -290
  88. package/src/hooks/projection/useProjection.test.tsx +0 -218
  89. package/src/hooks/projection/useProjection.ts +0 -147
  90. package/src/hooks/projects/useProject.ts +0 -45
  91. package/src/hooks/projects/useProjects.ts +0 -41
  92. package/src/hooks/query/useQuery.test.tsx +0 -188
  93. package/src/hooks/query/useQuery.ts +0 -103
  94. package/src/hooks/users/useUsers.test.ts +0 -163
  95. package/src/hooks/users/useUsers.ts +0 -107
  96. package/src/utils/getEnv.ts +0 -21
  97. package/src/version.ts +0 -8
@@ -1,82 +0,0 @@
1
- import {
2
- type DocumentAction,
3
- getPermissionsState,
4
- getResourceId,
5
- type PermissionsResult,
6
- } from '@sanity/sdk'
7
- import {useCallback, useMemo, useSyncExternalStore} from 'react'
8
- import {filter, firstValueFrom} from 'rxjs'
9
-
10
- import {useSanityInstance} from '../context/useSanityInstance'
11
-
12
- /**
13
- *
14
- * @beta
15
- *
16
- * Check if the current user has the specified permissions for the given document actions.
17
- *
18
- * @category Permissions
19
- * @param actions - One more more calls to a particular document action function for a given document
20
- * @returns An object that specifies whether the action is allowed; if the action is not allowed, an explanatory message and list of reasons is also provided.
21
- *
22
- * @example Checking for permission to publish a document
23
- * ```ts
24
- * import {usePermissions, useApplyActions} from '@sanity/sdk-react'
25
- * import {publishDocument} from '@sanity/sdk'
26
- *
27
- * export function PublishButton({doc}: {doc: DocumentHandle}) {
28
- * const publishPermissions = usePermissions(publishDocument(doc))
29
- * const applyAction = useApplyActions()
30
- *
31
- * return (
32
- * <>
33
- * <button
34
- * disabled={!publishPermissions.allowed}
35
- * onClick={() => applyAction(publishDocument(doc))}
36
- * popoverTarget={`${publishPermissions.allowed ? undefined : 'publishButtonPopover'}`}
37
- * >
38
- * Publish
39
- * </button>
40
- * {!publishPermissions.allowed && (
41
- * <div popover id="publishButtonPopover">
42
- * {publishPermissions.message}
43
- * </div>
44
- * )}
45
- * </>
46
- * )
47
- * }
48
- * ```
49
- */
50
- export function usePermissions(actions: DocumentAction | DocumentAction[]): PermissionsResult {
51
- // if actions is an array, we need to check each action to see if the resourceId is the same
52
- if (Array.isArray(actions)) {
53
- const resourceIds = actions.map((action) => action.resourceId)
54
- const uniqueResourceIds = new Set(resourceIds)
55
- if (uniqueResourceIds.size !== 1) {
56
- throw new Error('All actions must have the same resourceId')
57
- }
58
- }
59
- const resourceId = Array.isArray(actions)
60
- ? getResourceId(actions[0].resourceId)
61
- : getResourceId(actions.resourceId)
62
-
63
- const instance = useSanityInstance(resourceId)
64
- const isDocumentReady = useCallback(
65
- () => getPermissionsState(instance, actions).getCurrent() !== undefined,
66
- [actions, instance],
67
- )
68
- if (!isDocumentReady()) {
69
- throw firstValueFrom(
70
- getPermissionsState(instance, actions).observable.pipe(
71
- filter((result) => result !== undefined),
72
- ),
73
- )
74
- }
75
-
76
- const {subscribe, getCurrent} = useMemo(
77
- () => getPermissionsState(instance, actions),
78
- [actions, instance],
79
- )
80
-
81
- return useSyncExternalStore(subscribe, getCurrent) as PermissionsResult
82
- }
@@ -1,152 +0,0 @@
1
- import {act, renderHook} from '@testing-library/react'
2
- import {describe, vi} from 'vitest'
3
-
4
- import {evaluateSync, parse} from '../_synchronous-groq-js.mjs'
5
- import {useQuery} from '../query/useQuery'
6
- import {useInfiniteList} from './useInfiniteList'
7
-
8
- vi.mock('../query/useQuery')
9
-
10
- describe('useInfiniteList', () => {
11
- beforeEach(() => {
12
- const dataset = [
13
- {
14
- _id: 'movie1',
15
- _type: 'movie',
16
- title: 'The Matrix',
17
- releaseYear: 1999,
18
- _createdAt: '2021-03-09T00:00:00.000Z',
19
- _updatedAt: '2021-03-09T00:00:00.000Z',
20
- _rev: 'tx0',
21
- },
22
- {
23
- _id: 'movie2',
24
- _type: 'movie',
25
- title: 'Inception',
26
- releaseYear: 2010,
27
- _createdAt: '2021-03-10T00:00:00.000Z',
28
- _updatedAt: '2021-03-10T00:00:00.000Z',
29
- _rev: 'tx1',
30
- },
31
- {
32
- _id: 'movie3',
33
- _type: 'movie',
34
- title: 'Interstellar',
35
- releaseYear: 2014,
36
- _createdAt: '2021-03-11T00:00:00.000Z',
37
- _updatedAt: '2021-03-11T00:00:00.000Z',
38
- _rev: 'tx2',
39
- },
40
- {
41
- _id: 'book1',
42
- _type: 'book',
43
- title: 'Dune',
44
- _createdAt: '2021-03-12T00:00:00.000Z',
45
- _updatedAt: '2021-03-12T00:00:00.000Z',
46
- _rev: 'tx3',
47
- },
48
- {
49
- _id: 'movie4',
50
- _type: 'movie',
51
- title: 'The Dark Knight',
52
- releaseYear: 2008,
53
- _createdAt: '2021-03-13T00:00:00.000Z',
54
- _updatedAt: '2021-03-13T00:00:00.000Z',
55
- _rev: 'tx4',
56
- },
57
- {
58
- _id: 'movie5',
59
- _type: 'movie',
60
- title: 'Pulp Fiction',
61
- releaseYear: 1994,
62
- _createdAt: '2021-03-14T00:00:00.000Z',
63
- _updatedAt: '2021-03-14T00:00:00.000Z',
64
- _rev: 'tx5',
65
- },
66
- ]
67
-
68
- vi.mocked(useQuery).mockImplementation((query, options) => {
69
- const result = evaluateSync(parse(query), {dataset, params: options?.params}).get()
70
- return {
71
- data: result,
72
- isPending: false,
73
- }
74
- })
75
- })
76
-
77
- it('should respect custom page size', () => {
78
- const customBatchSize = 2
79
- const {result} = renderHook(() => useInfiniteList({batchSize: customBatchSize}))
80
-
81
- expect(result.current.data.length).toBe(customBatchSize)
82
- })
83
-
84
- it('should filter by document type', () => {
85
- const {result} = renderHook(() => useInfiniteList({filter: '_type == "movie"'}))
86
-
87
- expect(result.current.data.every((doc) => doc._type === 'movie')).toBe(true)
88
- expect(result.current.count).toBe(5) // 5 movies in the dataset
89
- })
90
-
91
- // groq-js doesn't support search filters yet
92
- it.skip('should apply search filter', () => {
93
- const {result} = renderHook(() => useInfiniteList({search: 'inter'}))
94
-
95
- // Should match "Interstellar"
96
- expect(result.current.data.some((doc) => doc._id === 'movie3')).toBe(true)
97
- })
98
-
99
- it('should apply ordering', () => {
100
- const {result} = renderHook(() =>
101
- useInfiniteList({
102
- filter: '_type == "movie"',
103
- orderings: [{field: 'releaseYear', direction: 'desc'}],
104
- }),
105
- )
106
-
107
- // First item should be the most recent movie (Interstellar, 2014)
108
- expect(result.current.data[0]._id).toBe('movie3')
109
- })
110
-
111
- it('should load more data when loadMore is called', () => {
112
- const batchSize = 2
113
- const {result} = renderHook(() => useInfiniteList({batchSize: batchSize}))
114
-
115
- expect(result.current.data.length).toBe(batchSize)
116
-
117
- act(() => {
118
- result.current.loadMore()
119
- })
120
-
121
- expect(result.current.data.length).toBe(batchSize * 2)
122
- })
123
-
124
- it('should indicate when there is more data to load', () => {
125
- const {result} = renderHook(() => useInfiniteList({batchSize: 3}))
126
- expect(result.current.hasMore).toBe(true)
127
- // Load all remaining data
128
- act(() => {
129
- result.current.loadMore()
130
- })
131
- expect(result.current.hasMore).toBe(false)
132
- })
133
-
134
- // New test case for resetting limit when filter changes
135
- it('should reset limit when filter changes', () => {
136
- const {result, rerender} = renderHook((props) => useInfiniteList(props), {
137
- initialProps: {batchSize: 2, filter: ''},
138
- })
139
- // Initially, data length equals pageSize (2)
140
- expect(result.current.data.length).toBe(2)
141
- // Load more to increase limit
142
- act(() => {
143
- result.current.loadMore()
144
- })
145
- // After loadMore, data length should be increased (2 + 2 = 4)
146
- expect(result.current.data.length).toBe(4)
147
- // Now update filter to trigger resetting the limit
148
- rerender({batchSize: 2, filter: '_type == "movie"'})
149
- // With the filter applied, the limit is reset to pageSize (i.e. 2)
150
- expect(result.current.data.length).toBe(2)
151
- })
152
- })
@@ -1,174 +0,0 @@
1
- import {type DocumentHandle, type QueryOptions} from '@sanity/sdk'
2
- import {type SortOrderingItem} from '@sanity/types'
3
- import {useCallback, useEffect, useMemo, useState} from 'react'
4
-
5
- import {useQuery} from '../query/useQuery'
6
-
7
- const DEFAULT_BATCH_SIZE = 25
8
- const DEFAULT_PERSPECTIVE = 'drafts'
9
-
10
- /**
11
- * Result structure returned from the infinite list query
12
- * @internal
13
- */
14
- interface InfiniteListQueryResult {
15
- count: number
16
- data: DocumentHandle[]
17
- }
18
-
19
- /**
20
- * Configuration options for the useInfiniteList hook
21
- *
22
- * @beta
23
- * @category Types
24
- */
25
- export interface InfiniteListOptions extends QueryOptions {
26
- /**
27
- * GROQ filter expression to apply to the query
28
- */
29
- filter?: string
30
- /**
31
- * Number of items to load per batch (defaults to 25)
32
- */
33
- batchSize?: number
34
- /**
35
- * Sorting configuration for the results
36
- */
37
- orderings?: SortOrderingItem[]
38
- /**
39
- * Text search query to filter results
40
- */
41
- search?: string
42
- }
43
-
44
- /**
45
- * Return value from the useInfiniteList hook
46
- *
47
- * @beta
48
- * @category Types
49
- */
50
- export interface InfiniteList {
51
- /**
52
- * Array of document handles for the current batch
53
- */
54
- data: DocumentHandle[]
55
- /**
56
- * Whether there are more items available to load
57
- */
58
- hasMore: boolean
59
- /**
60
- * Total count of items matching the query
61
- */
62
- count: number
63
- /**
64
- * Whether a query is currently in progress
65
- */
66
- isPending: boolean
67
- /**
68
- * Function to load the next batch of results
69
- */
70
- loadMore: () => void
71
- }
72
-
73
- /**
74
- * Retrieves batches of {@link DocumentHandle}s, narrowed by optional filters, text searches, and custom ordering,
75
- * with infinite scrolling support. The number of document handles returned per batch is customizable,
76
- * and additional batches can be loaded using the supplied `loadMore` function.
77
- *
78
- * @beta
79
- * @category Documents
80
- * @param options - Configuration options for the infinite list
81
- * @returns An object containing the list of document handles, the loading state, the total count of retrived document handles, and a function to load more
82
- * @example
83
- * ```tsx
84
- * const {data, hasMore, isPending, loadMore} = useInfiniteList({
85
- * filter: '_type == "post"',
86
- * search: searchTerm,
87
- * batchSize: 10,
88
- * orderings: [{field: '_createdAt', direction: 'desc'}]
89
- * })
90
- *
91
- * return (
92
- * <div>
93
- * Total documents: {count}
94
- * <ol>
95
- * {data.map((doc) => (
96
- * <li key={doc._id}>
97
- * <MyDocumentComponent doc={doc} />
98
- * </li>
99
- * ))}
100
- * </ol>
101
- * {hasMore && <button onClick={loadMore}>Load More</button>}
102
- * </div>
103
- * )
104
- * ```
105
- *
106
- */
107
- export function useInfiniteList({
108
- batchSize = DEFAULT_BATCH_SIZE,
109
- params,
110
- search,
111
- filter,
112
- orderings,
113
- ...options
114
- }: InfiniteListOptions): InfiniteList {
115
- const perspective = options.perspective ?? DEFAULT_PERSPECTIVE
116
- const [limit, setLimit] = useState(batchSize)
117
-
118
- // Reset the limit to the current batchSize whenever any query parameters
119
- // (filter, search, params, orderings) or batchSize changes
120
- const key = JSON.stringify({filter, search, params, orderings, batchSize})
121
- useEffect(() => {
122
- setLimit(batchSize)
123
- }, [key, batchSize])
124
-
125
- const filterClause = useMemo(() => {
126
- const conditions: string[] = []
127
-
128
- // Add search query if specified
129
- if (search?.trim()) {
130
- conditions.push(`[@] match text::query("${search.trim()}")`)
131
- }
132
-
133
- // Add additional filter if specified
134
- if (filter) {
135
- conditions.push(`(${filter})`)
136
- }
137
-
138
- return conditions.length ? `[${conditions.join(' && ')}]` : ''
139
- }, [filter, search])
140
-
141
- const orderClause = orderings
142
- ? `| order(${orderings
143
- .map((ordering) =>
144
- [ordering.field, ordering.direction.toLowerCase()]
145
- .map((str) => str.trim())
146
- .filter(Boolean)
147
- .join(' '),
148
- )
149
- .join(',')})`
150
- : ''
151
-
152
- const dataQuery = `*${filterClause}${orderClause}[0...${limit}]{_id,_type}`
153
- const countQuery = `count(*${filterClause})`
154
-
155
- const {
156
- data: {count, data},
157
- isPending,
158
- } = useQuery<InfiniteListQueryResult>(`{"count":${countQuery},"data":${dataQuery}}`, {
159
- ...options,
160
- params,
161
- perspective,
162
- })
163
-
164
- const hasMore = data.length < count
165
-
166
- const loadMore = useCallback(() => {
167
- setLimit((prev) => Math.min(prev + batchSize, count))
168
- }, [count, batchSize])
169
-
170
- return useMemo(
171
- () => ({data, hasMore, count, isPending, loadMore}),
172
- [data, hasMore, count, isPending, loadMore],
173
- )
174
- }
@@ -1,259 +0,0 @@
1
- import {act, renderHook} from '@testing-library/react'
2
- import {describe, vi} from 'vitest'
3
-
4
- import {evaluateSync, parse} from '../_synchronous-groq-js.mjs'
5
- import {useQuery} from '../query/useQuery'
6
- import {usePaginatedList} from './usePaginatedList'
7
-
8
- vi.mock('../query/useQuery')
9
-
10
- describe('usePaginatedList', () => {
11
- beforeEach(() => {
12
- const dataset = [
13
- {
14
- _id: 'movie1',
15
- _type: 'movie',
16
- title: 'The Matrix',
17
- releaseYear: 1999,
18
- _createdAt: '2021-03-09T00:00:00.000Z',
19
- _updatedAt: '2021-03-09T00:00:00.000Z',
20
- _rev: 'tx0',
21
- },
22
- {
23
- _id: 'movie2',
24
- _type: 'movie',
25
- title: 'Inception',
26
- releaseYear: 2010,
27
- _createdAt: '2021-03-10T00:00:00.000Z',
28
- _updatedAt: '2021-03-10T00:00:00.000Z',
29
- _rev: 'tx1',
30
- },
31
- {
32
- _id: 'movie3',
33
- _type: 'movie',
34
- title: 'Interstellar',
35
- releaseYear: 2014,
36
- _createdAt: '2021-03-11T00:00:00.000Z',
37
- _updatedAt: '2021-03-11T00:00:00.000Z',
38
- _rev: 'tx2',
39
- },
40
- {
41
- _id: 'book1',
42
- _type: 'book',
43
- title: 'Dune',
44
- _createdAt: '2021-03-12T00:00:00.000Z',
45
- _updatedAt: '2021-03-12T00:00:00.000Z',
46
- _rev: 'tx3',
47
- },
48
- {
49
- _id: 'movie4',
50
- _type: 'movie',
51
- title: 'The Dark Knight',
52
- releaseYear: 2008,
53
- _createdAt: '2021-03-13T00:00:00.000Z',
54
- _updatedAt: '2021-03-13T00:00:00.000Z',
55
- _rev: 'tx4',
56
- },
57
- {
58
- _id: 'movie5',
59
- _type: 'movie',
60
- title: 'Pulp Fiction',
61
- releaseYear: 1994,
62
- _createdAt: '2021-03-14T00:00:00.000Z',
63
- _updatedAt: '2021-03-14T00:00:00.000Z',
64
- _rev: 'tx5',
65
- },
66
- ]
67
-
68
- vi.mocked(useQuery).mockImplementation((query, options) => {
69
- const result = evaluateSync(parse(query), {dataset, params: options?.params}).get()
70
- return {
71
- data: result,
72
- isPending: false,
73
- }
74
- })
75
- })
76
-
77
- it('should respect custom page size', () => {
78
- const customPageSize = 2
79
- const {result} = renderHook(() => usePaginatedList({pageSize: customPageSize}))
80
-
81
- expect(result.current.pageSize).toBe(customPageSize)
82
- expect(result.current.data.length).toBeLessThanOrEqual(customPageSize)
83
- })
84
-
85
- it('should filter by document type', () => {
86
- const {result} = renderHook(() => usePaginatedList({filter: '_type == "movie"'}))
87
-
88
- expect(result.current.data.every((doc) => doc._type === 'movie')).toBe(true)
89
- expect(result.current.count).toBe(5) // 5 movies in the dataset
90
- })
91
-
92
- // groq-js doesn't support search filters yet
93
- it.skip('should apply search filter', () => {
94
- const {result} = renderHook(() => usePaginatedList({search: 'inter'}))
95
-
96
- // Should match "Interstellar"
97
- expect(result.current.data.some((doc) => doc._id === 'movie3')).toBe(true)
98
- })
99
-
100
- it('should apply ordering', () => {
101
- const {result} = renderHook(() =>
102
- usePaginatedList({
103
- filter: '_type == "movie"',
104
- orderings: [{field: 'releaseYear', direction: 'desc'}],
105
- }),
106
- )
107
-
108
- // First item should be the most recent movie (Interstellar, 2014)
109
- expect(result.current.data[0]._id).toBe('movie3')
110
- })
111
-
112
- it('should calculate pagination values correctly', () => {
113
- const pageSize = 2
114
- const {result} = renderHook(() => usePaginatedList({pageSize}))
115
-
116
- expect(result.current.currentPage).toBe(1)
117
- expect(result.current.totalPages).toBe(3) // 6 items with page size 2
118
- expect(result.current.startIndex).toBe(0)
119
- expect(result.current.endIndex).toBe(2)
120
- expect(result.current.count).toBe(6)
121
- })
122
-
123
- it('should navigate to next page', () => {
124
- const pageSize = 2
125
- const {result} = renderHook(() => usePaginatedList({pageSize}))
126
-
127
- expect(result.current.currentPage).toBe(1)
128
- expect(result.current.data.length).toBe(pageSize)
129
-
130
- act(() => {
131
- result.current.nextPage()
132
- })
133
-
134
- expect(result.current.currentPage).toBe(2)
135
- expect(result.current.startIndex).toBe(pageSize)
136
- expect(result.current.endIndex).toBe(pageSize * 2)
137
- })
138
-
139
- it('should navigate to previous page', () => {
140
- const pageSize = 2
141
- const {result} = renderHook(() => usePaginatedList({pageSize}))
142
-
143
- // Go to page 2 first
144
- act(() => {
145
- result.current.nextPage()
146
- })
147
-
148
- expect(result.current.currentPage).toBe(2)
149
-
150
- // Then go back to page 1
151
- act(() => {
152
- result.current.previousPage()
153
- })
154
-
155
- expect(result.current.currentPage).toBe(1)
156
- expect(result.current.startIndex).toBe(0)
157
- })
158
-
159
- it('should navigate to first page', () => {
160
- const pageSize = 2
161
- const {result} = renderHook(() => usePaginatedList({pageSize}))
162
-
163
- // Go to last page first
164
- act(() => {
165
- result.current.lastPage()
166
- })
167
-
168
- expect(result.current.currentPage).toBe(3) // Last page (3rd page)
169
-
170
- // Then go back to first page
171
- act(() => {
172
- result.current.firstPage()
173
- })
174
-
175
- expect(result.current.currentPage).toBe(1)
176
- expect(result.current.startIndex).toBe(0)
177
- })
178
-
179
- it('should navigate to last page', () => {
180
- const pageSize = 2
181
- const {result} = renderHook(() => usePaginatedList({pageSize}))
182
-
183
- act(() => {
184
- result.current.lastPage()
185
- })
186
-
187
- expect(result.current.currentPage).toBe(3) // Last page (3rd page)
188
- expect(result.current.startIndex).toBe(4) // Index 4-5 for the last page
189
- })
190
-
191
- it('should navigate to specific page', () => {
192
- const pageSize = 2
193
- const {result} = renderHook(() => usePaginatedList({pageSize}))
194
-
195
- act(() => {
196
- result.current.goToPage(2) // Go to page 2
197
- })
198
-
199
- expect(result.current.currentPage).toBe(2)
200
- expect(result.current.startIndex).toBe(2) // Index 2-3 for page 2
201
-
202
- // Should not navigate to invalid page numbers
203
- act(() => {
204
- result.current.goToPage(0) // Invalid page
205
- })
206
-
207
- expect(result.current.currentPage).toBe(2) // Should remain on page 2
208
-
209
- act(() => {
210
- result.current.goToPage(10) // Invalid page
211
- })
212
-
213
- expect(result.current.currentPage).toBe(2) // Should remain on page 2
214
- })
215
-
216
- it('should set page availability flags correctly', () => {
217
- const pageSize = 2
218
- const {result} = renderHook(() => usePaginatedList({pageSize}))
219
- // On first page
220
- expect(result.current.hasFirstPage).toBe(false)
221
- expect(result.current.hasPreviousPage).toBe(false)
222
- expect(result.current.hasNextPage).toBe(true)
223
- expect(result.current.hasLastPage).toBe(true)
224
- // Go to middle page
225
- act(() => {
226
- result.current.nextPage()
227
- })
228
- expect(result.current.hasFirstPage).toBe(true)
229
- expect(result.current.hasPreviousPage).toBe(true)
230
- expect(result.current.hasNextPage).toBe(true)
231
- expect(result.current.hasLastPage).toBe(true)
232
- // Go to last page
233
- act(() => {
234
- result.current.lastPage()
235
- })
236
- expect(result.current.hasFirstPage).toBe(true)
237
- expect(result.current.hasPreviousPage).toBe(true)
238
- expect(result.current.hasNextPage).toBe(false)
239
- expect(result.current.hasLastPage).toBe(false)
240
- })
241
-
242
- // New test case for resetting the current page when filter changes
243
- it('should reset current page when filter changes', () => {
244
- const {result, rerender} = renderHook((props) => usePaginatedList(props), {
245
- initialProps: {pageSize: 2, filter: ''},
246
- })
247
- // Initially, current page should be 1
248
- expect(result.current.currentPage).toBe(1)
249
- // Navigate to next page
250
- act(() => {
251
- result.current.nextPage()
252
- })
253
- expect(result.current.currentPage).toBe(2)
254
- // Now update filter, which should reset the page to the first page
255
- rerender({pageSize: 2, filter: '_type == "movie"'})
256
- expect(result.current.currentPage).toBe(1)
257
- expect(result.current.startIndex).toBe(0)
258
- })
259
- })