@sanity/sdk-react 2.8.0 → 3.0.0-rc.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 (87) hide show
  1. package/README.md +125 -63
  2. package/dist/index.d.ts +381 -571
  3. package/dist/index.js +435 -366
  4. package/dist/index.js.map +1 -1
  5. package/package.json +7 -9
  6. package/src/_exports/index.ts +4 -0
  7. package/src/_exports/sdk-react.ts +16 -0
  8. package/src/components/SDKProvider.test.tsx +23 -58
  9. package/src/components/SDKProvider.tsx +38 -30
  10. package/src/components/SanityApp.test.tsx +12 -68
  11. package/src/components/SanityApp.tsx +88 -65
  12. package/src/components/auth/AuthBoundary.test.tsx +8 -26
  13. package/src/components/auth/LoginError.tsx +5 -5
  14. package/src/config/handles.ts +53 -0
  15. package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
  16. package/src/context/DefaultResourceContext.ts +10 -0
  17. package/src/context/PerspectiveContext.ts +12 -0
  18. package/src/context/ResourceProvider.test.tsx +99 -19
  19. package/src/context/ResourceProvider.tsx +103 -37
  20. package/src/context/ResourcesContext.tsx +7 -0
  21. package/src/context/SDKStudioContext.test.tsx +33 -28
  22. package/src/context/SDKStudioContext.ts +6 -0
  23. package/src/context/renderSanityApp.test.tsx +49 -151
  24. package/src/context/renderSanityApp.tsx +8 -12
  25. package/src/hooks/agent/agentActions.test.tsx +1 -1
  26. package/src/hooks/agent/agentActions.ts +56 -19
  27. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
  28. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
  29. package/src/hooks/client/useClient.test.tsx +4 -1
  30. package/src/hooks/client/useClient.ts +0 -1
  31. package/src/hooks/context/useDefaultResource.test.tsx +25 -0
  32. package/src/hooks/context/useDefaultResource.ts +30 -0
  33. package/src/hooks/context/useSanityInstance.test.tsx +2 -140
  34. package/src/hooks/context/useSanityInstance.ts +9 -53
  35. package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
  36. package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
  37. package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
  38. package/src/hooks/dashboard/useManageFavorite.ts +16 -10
  39. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
  40. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
  41. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
  42. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
  43. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
  44. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
  45. package/src/hooks/datasets/useDatasets.test.ts +8 -22
  46. package/src/hooks/datasets/useDatasets.ts +8 -16
  47. package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
  48. package/src/hooks/document/useApplyDocumentActions.ts +35 -37
  49. package/src/hooks/document/useDocument.test.tsx +8 -37
  50. package/src/hooks/document/useDocument.ts +78 -129
  51. package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
  52. package/src/hooks/document/useDocumentEvent.ts +21 -19
  53. package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
  54. package/src/hooks/document/useDocumentPermissions.ts +41 -28
  55. package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
  56. package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
  57. package/src/hooks/document/useEditDocument.test.tsx +28 -70
  58. package/src/hooks/document/useEditDocument.ts +29 -149
  59. package/src/hooks/documents/useDocuments.test.tsx +44 -64
  60. package/src/hooks/documents/useDocuments.ts +19 -25
  61. package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
  62. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  63. package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
  64. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
  65. package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
  66. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
  67. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
  68. package/src/hooks/presence/usePresence.test.tsx +71 -9
  69. package/src/hooks/presence/usePresence.ts +28 -3
  70. package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
  71. package/src/hooks/preview/useDocumentPreview.tsx +42 -62
  72. package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
  73. package/src/hooks/projection/useDocumentProjection.ts +9 -82
  74. package/src/hooks/projects/useProject.test.ts +1 -2
  75. package/src/hooks/projects/useProject.ts +7 -8
  76. package/src/hooks/query/useQuery.test.tsx +5 -6
  77. package/src/hooks/query/useQuery.ts +12 -91
  78. package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
  79. package/src/hooks/releases/useActiveReleases.ts +25 -13
  80. package/src/hooks/releases/usePerspective.test.tsx +9 -17
  81. package/src/hooks/releases/usePerspective.ts +29 -18
  82. package/src/hooks/users/useUser.test.tsx +9 -3
  83. package/src/hooks/users/useUser.ts +1 -1
  84. package/src/hooks/users/useUsers.test.tsx +5 -2
  85. package/src/hooks/users/useUsers.ts +1 -1
  86. package/src/context/SourcesContext.tsx +0 -7
  87. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
@@ -1,8 +1,8 @@
1
1
  import {type DocumentAction, type DocumentPermissionsResult, getPermissionsState} from '@sanity/sdk'
2
- import {act, renderHook, waitFor} from '@testing-library/react'
3
- import {BehaviorSubject, firstValueFrom} from 'rxjs'
2
+ import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'
4
3
  import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
5
4
 
5
+ import {act, renderHook, waitFor} from '../../../test/test-utils'
6
6
  import {ResourceProvider} from '../../context/ResourceProvider'
7
7
  import {useDocumentPermissions} from './useDocumentPermissions'
8
8
 
@@ -24,12 +24,12 @@ vi.mock('rxjs', async (importOriginal) => {
24
24
  })
25
25
 
26
26
  describe('usePermissions', () => {
27
+ const mockResource = {projectId: 'project1', dataset: 'dataset1'}
27
28
  const mockAction: DocumentAction = {
28
29
  type: 'document.publish',
29
30
  documentId: 'doc1',
30
31
  documentType: 'article',
31
- projectId: 'project1',
32
- dataset: 'dataset1',
32
+ resource: mockResource,
33
33
  }
34
34
 
35
35
  const mockPermissionAllowed: DocumentPermissionsResult = {allowed: true}
@@ -68,7 +68,8 @@ describe('usePermissions', () => {
68
68
 
69
69
  // Set up the getPermissionsState mock
70
70
  vi.mocked(getPermissionsState).mockReturnValue({
71
- observable: permissionsSubject.asObservable(),
71
+ observable:
72
+ permissionsSubject.asObservable() as unknown as Observable<DocumentPermissionsResult>,
72
73
  subscribe: mockSubscribe,
73
74
  getCurrent: mockGetCurrent,
74
75
  })
@@ -84,20 +85,13 @@ describe('usePermissions', () => {
84
85
  permissionsSubject.next(mockPermissionAllowed)
85
86
  })
86
87
 
87
- const {result} = renderHook(() => useDocumentPermissions(mockAction), {
88
- wrapper: ({children}) => (
89
- <ResourceProvider
90
- projectId={mockAction.projectId}
91
- dataset={mockAction.dataset}
92
- fallback={null}
93
- >
94
- {children}
95
- </ResourceProvider>
96
- ),
97
- })
88
+ const {result} = renderHook(() => useDocumentPermissions(mockAction))
98
89
 
99
90
  // ResourceProvider handles the instance configuration
100
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions: [mockAction]})
91
+ expect(getPermissionsState).toHaveBeenCalledWith(
92
+ expect.any(Object),
93
+ expect.objectContaining({actions: [mockAction], resource: mockResource}),
94
+ )
101
95
  expect(result.current).toEqual(mockPermissionAllowed)
102
96
  })
103
97
 
@@ -107,17 +101,7 @@ describe('usePermissions', () => {
107
101
  permissionsSubject.next(mockPermissionDenied)
108
102
  })
109
103
 
110
- const {result} = renderHook(() => useDocumentPermissions(mockAction), {
111
- wrapper: ({children}) => (
112
- <ResourceProvider
113
- projectId={mockAction.projectId}
114
- dataset={mockAction.dataset}
115
- fallback={null}
116
- >
117
- {children}
118
- </ResourceProvider>
119
- ),
120
- })
104
+ const {result} = renderHook(() => useDocumentPermissions(mockAction))
121
105
 
122
106
  expect(result.current).toEqual(mockPermissionDenied)
123
107
  expect(result.current.allowed).toBe(false)
@@ -128,58 +112,58 @@ describe('usePermissions', () => {
128
112
  it('should accept an array of actions', () => {
129
113
  const actions = [mockAction, {...mockAction, documentId: 'doc2'}]
130
114
 
131
- renderHook(() => useDocumentPermissions(actions), {
132
- wrapper: ({children}) => (
133
- <ResourceProvider
134
- projectId={mockAction.projectId}
135
- dataset={mockAction.dataset}
136
- fallback={null}
137
- >
138
- {children}
139
- </ResourceProvider>
140
- ),
141
- })
115
+ renderHook(() => useDocumentPermissions(actions))
142
116
 
143
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions})
117
+ expect(getPermissionsState).toHaveBeenCalledWith(
118
+ expect.any(Object),
119
+ expect.objectContaining({actions, resource: mockResource}),
120
+ )
144
121
  })
145
122
 
146
- it('should throw an error if actions have mismatched project IDs', () => {
123
+ it('should throw an error if actions have mismatched resources', () => {
147
124
  const actions = [
148
125
  mockAction,
149
- {...mockAction, projectId: 'different-project', documentId: 'doc2'},
126
+ {
127
+ ...mockAction,
128
+ resource: {projectId: 'different-project', dataset: 'dataset1'},
129
+ documentId: 'doc2',
130
+ },
150
131
  ]
151
132
 
152
133
  expect(() => {
153
- renderHook(() => useDocumentPermissions(actions), {
154
- wrapper: ({children}) => (
155
- <ResourceProvider
156
- projectId={mockAction.projectId}
157
- dataset={mockAction.dataset}
158
- fallback={null}
159
- >
160
- {children}
161
- </ResourceProvider>
162
- ),
163
- })
164
- }).toThrow(/Mismatched project IDs found in actions/)
134
+ renderHook(() => useDocumentPermissions(actions))
135
+ }).toThrow(/Mismatched resources found in actions/)
165
136
  })
166
137
 
167
138
  it('should throw an error if actions have mismatched datasets', () => {
168
- const actions = [mockAction, {...mockAction, dataset: 'different-dataset', documentId: 'doc2'}]
139
+ const actions = [
140
+ mockAction,
141
+ {
142
+ ...mockAction,
143
+ resource: {projectId: 'project1', dataset: 'different-dataset'},
144
+ documentId: 'doc2',
145
+ },
146
+ ]
169
147
 
170
148
  expect(() => {
171
- renderHook(() => useDocumentPermissions(actions), {
172
- wrapper: ({children}) => (
173
- <ResourceProvider
174
- projectId={mockAction.projectId}
175
- dataset={mockAction.dataset}
176
- fallback={null}
177
- >
178
- {children}
179
- </ResourceProvider>
180
- ),
181
- })
182
- }).toThrow(/Mismatched datasets found in actions/)
149
+ renderHook(() => useDocumentPermissions(actions))
150
+ }).toThrow(/Mismatched resources found in actions/)
151
+ })
152
+
153
+ it('should throw an error when mixing different resources', () => {
154
+ const actions = [
155
+ mockAction,
156
+ {
157
+ type: 'document.publish' as const,
158
+ documentId: 'doc2',
159
+ documentType: 'article',
160
+ resource: {projectId: 'p', dataset: 'd'},
161
+ },
162
+ ]
163
+
164
+ expect(() => {
165
+ renderHook(() => useDocumentPermissions(actions))
166
+ }).toThrow(/Mismatched resources found in actions/)
183
167
  })
184
168
 
185
169
  it('should wait for permissions to be ready before rendering', async () => {
@@ -206,11 +190,7 @@ describe('usePermissions', () => {
206
190
  },
207
191
  {
208
192
  wrapper: ({children}) => (
209
- <ResourceProvider
210
- projectId={mockAction.projectId}
211
- dataset={mockAction.dataset}
212
- fallback={null}
213
- >
193
+ <ResourceProvider resource={mockResource} fallback={null}>
214
194
  {children}
215
195
  </ResourceProvider>
216
196
  ),
@@ -226,27 +206,38 @@ describe('usePermissions', () => {
226
206
 
227
207
  // Now it should render properly
228
208
  await waitFor(() => {
229
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions: [mockAction]})
209
+ expect(getPermissionsState).toHaveBeenCalledWith(
210
+ expect.any(Object),
211
+ expect.objectContaining({actions: [mockAction], resource: mockResource}),
212
+ )
230
213
  })
231
214
  })
232
215
 
216
+ it('throws when no resource is found from action or context', () => {
217
+ // Provide SanityInstance via ResourceProvider but no resource, so contextResource is undefined
218
+ expect(() => {
219
+ renderHook(
220
+ () =>
221
+ useDocumentPermissions({
222
+ type: 'document.publish',
223
+ documentId: 'doc1',
224
+ documentType: 'article',
225
+ // no resource
226
+ } as never),
227
+ {
228
+ wrapper: ({children}) => <ResourceProvider fallback={null}>{children}</ResourceProvider>,
229
+ },
230
+ )
231
+ }).toThrow(/resource is required/)
232
+ })
233
+
233
234
  it('should react to permission state changes', async () => {
234
235
  // Start with permission allowed
235
236
  act(() => {
236
237
  permissionsSubject.next(mockPermissionAllowed)
237
238
  })
238
239
 
239
- const {result, rerender} = renderHook(() => useDocumentPermissions(mockAction), {
240
- wrapper: ({children}) => (
241
- <ResourceProvider
242
- projectId={mockAction.projectId}
243
- dataset={mockAction.dataset}
244
- fallback={null}
245
- >
246
- {children}
247
- </ResourceProvider>
248
- ),
249
- })
240
+ const {result, rerender} = renderHook(() => useDocumentPermissions(mockAction))
250
241
 
251
242
  expect(result.current).toEqual(mockPermissionAllowed)
252
243
 
@@ -1,8 +1,11 @@
1
1
  import {type DocumentAction, type DocumentPermissionsResult, getPermissionsState} from '@sanity/sdk'
2
- import {useCallback, useMemo, useSyncExternalStore} from 'react'
2
+ import {useCallback, useContext, useMemo, useSyncExternalStore} from 'react'
3
3
  import {filter, firstValueFrom} from 'rxjs'
4
4
 
5
+ import {ResourceContext} from '../../context/DefaultResourceContext'
6
+ import {ResourcesContext} from '../../context/ResourcesContext'
5
7
  import {useSanityInstance} from '../context/useSanityInstance'
8
+ import {normalizeResourceOptions} from '../helpers/useNormalizedResourceOptions'
6
9
 
7
10
  /**
8
11
  *
@@ -84,50 +87,60 @@ import {useSanityInstance} from '../context/useSanityInstance'
84
87
  export function useDocumentPermissions(
85
88
  actionOrActions: DocumentAction | DocumentAction[],
86
89
  ): DocumentPermissionsResult {
87
- const actions = useMemo(
88
- () => (Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]),
89
- [actionOrActions],
90
- )
91
- // if actions is an array, we need to check that all actions belong to the same project and dataset
92
- let projectId
93
- let dataset
90
+ const instance = useSanityInstance()
91
+ const contextResource = useContext(ResourceContext)
92
+ const resources = useContext(ResourcesContext)
94
93
 
95
- for (const action of actions) {
96
- if (action.projectId) {
97
- if (!projectId) projectId = action.projectId
98
- if (action.projectId !== projectId) {
99
- throw new Error(
100
- `Mismatched project IDs found in actions. All actions must belong to the same project. Found "${action.projectId}" but expected "${projectId}".`,
94
+ const normalizedActions = useMemo(() => {
95
+ return Array.isArray(actionOrActions)
96
+ ? actionOrActions.map((action) =>
97
+ normalizeResourceOptions(action, resources, contextResource),
101
98
  )
102
- }
99
+ : [normalizeResourceOptions(actionOrActions, resources, contextResource)]
100
+ }, [actionOrActions, resources, contextResource])
103
101
 
104
- if (action.dataset) {
105
- if (!dataset) dataset = action.dataset
106
- if (action.dataset !== dataset) {
107
- throw new Error(
108
- `Mismatched datasets found in actions. All actions must belong to the same dataset. Found "${action.dataset}" but expected "${dataset}".`,
109
- )
110
- }
102
+ // if actions is an array, we need to check that all actions belong to the same resource
103
+ let resource
104
+
105
+ for (const action of normalizedActions) {
106
+ if (action.resource) {
107
+ if (!resource) resource = action.resource
108
+ if (action.resource !== resource) {
109
+ throw new Error(
110
+ `Mismatched resources found in actions. All actions must belong to the same resource. Found "${JSON.stringify(action.resource)}" but expected "${JSON.stringify(resource)}".`,
111
+ )
111
112
  }
112
113
  }
113
114
  }
114
115
 
115
- const instance = useSanityInstance({projectId, dataset})
116
+ const effectiveResource = resource ?? contextResource
117
+
118
+ if (!effectiveResource) {
119
+ throw new Error(
120
+ 'No resource found. Provide a resource via the action handle or wrap with a resource context.',
121
+ )
122
+ }
123
+
124
+ const permissionsOptions = useMemo(
125
+ () => ({resource: effectiveResource, actions: normalizedActions as DocumentAction[]}),
126
+ [effectiveResource, normalizedActions],
127
+ )
128
+
116
129
  const isDocumentReady = useCallback(
117
- () => getPermissionsState(instance, {actions}).getCurrent() !== undefined,
118
- [actions, instance],
130
+ () => getPermissionsState(instance, permissionsOptions).getCurrent() !== undefined,
131
+ [permissionsOptions, instance],
119
132
  )
120
133
  if (!isDocumentReady()) {
121
134
  throw firstValueFrom(
122
- getPermissionsState(instance, {actions}).observable.pipe(
135
+ getPermissionsState(instance, permissionsOptions).observable.pipe(
123
136
  filter((result) => result !== undefined),
124
137
  ),
125
138
  )
126
139
  }
127
140
 
128
141
  const {subscribe, getCurrent} = useMemo(
129
- () => getPermissionsState(instance, {actions}),
130
- [actions, instance],
142
+ () => getPermissionsState(instance, permissionsOptions),
143
+ [permissionsOptions, instance],
131
144
  )
132
145
 
133
146
  return useSyncExternalStore(subscribe, getCurrent) as DocumentPermissionsResult
@@ -1,23 +1,33 @@
1
1
  import {getDocumentSyncStatus} from '@sanity/sdk'
2
2
  import {describe, it} from 'vitest'
3
3
 
4
+ import {renderHook} from '../../../test/test-utils'
4
5
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
5
6
 
6
7
  const mockHook = vi.fn()
7
8
  vi.mock('../helpers/createStateSourceHook', () => ({createStateSourceHook: vi.fn(() => mockHook)}))
8
- vi.mock('@sanity/sdk', () => ({getDocumentSyncStatus: vi.fn()}))
9
+ vi.mock('@sanity/sdk', async (importOriginal) => {
10
+ const original = await importOriginal<typeof import('@sanity/sdk')>()
11
+ return {
12
+ ...original,
13
+ getDocumentSyncStatus: vi.fn(),
14
+ }
15
+ })
16
+
17
+ vi.mock('../context/useSanityInstance')
9
18
 
10
19
  describe('useDocumentSyncStatus', () => {
11
20
  it('calls `createStateSourceHook` with `getDocumentSyncStatus`', async () => {
12
21
  const {useDocumentSyncStatus} = await import('./useDocumentSyncStatus')
22
+ renderHook(() => useDocumentSyncStatus({documentId: '1', documentType: 'test'}))
13
23
  expect(createStateSourceHook).toHaveBeenCalledWith(
14
24
  expect.objectContaining({
15
25
  getState: getDocumentSyncStatus,
16
26
  shouldSuspend: expect.any(Function),
17
27
  suspender: expect.any(Function),
18
- getConfig: expect.any(Function),
19
28
  }),
20
29
  )
21
- expect(useDocumentSyncStatus).toBe(mockHook)
30
+ // Verify that the hook was created and can be called
31
+ expect(mockHook).toHaveBeenCalled()
22
32
  })
23
33
  })
@@ -1,22 +1,23 @@
1
1
  import {
2
- type DocumentHandle,
2
+ type DocumentHandle as StrictDocumentHandle,
3
3
  getDocumentSyncStatus,
4
4
  resolveDocument,
5
5
  type SanityInstance,
6
6
  type StateSource,
7
7
  } from '@sanity/sdk'
8
- import {identity} from 'rxjs'
9
8
 
9
+ import {type DocumentHandle} from '../../config/handles'
10
10
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
11
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
11
12
 
12
13
  type UseDocumentSyncStatus = {
13
14
  /**
14
15
  * Exposes the document's sync status between local and remote document states.
15
16
  *
16
17
  * @category Documents
17
- * @param doc - The document handle to get sync status for. If you pass a `DocumentHandle` with specified `projectId` and `dataset`,
18
- * the document will be read from the specified Sanity project and dataset that is included in the handle. If no `projectId` or `dataset` is provided,
19
- * the document will use the nearest instance from context.
18
+ * @param doc - The document handle to get sync status for. If you pass a `resource` in the handle,
19
+ * the document will be read from the specified resource. If no `resource` is provided,
20
+ * the resource will be resolved from context.
20
21
  * @returns `true` if local changes are synced with remote, `false` if changes are pending. Note: Suspense handles loading states.
21
22
  * @example Show sync status indicator
22
23
  * ```tsx
@@ -45,17 +46,21 @@ type UseDocumentSyncStatus = {
45
46
  (doc: DocumentHandle): boolean
46
47
  }
47
48
 
48
- /**
49
- * @public
50
- * @function
51
- */
52
- export const useDocumentSyncStatus: UseDocumentSyncStatus = createStateSourceHook({
49
+ const useDocumentSyncStatusValue = createStateSourceHook({
53
50
  getState: getDocumentSyncStatus as (
54
51
  instance: SanityInstance,
55
- doc: DocumentHandle,
52
+ doc: StrictDocumentHandle,
56
53
  ) => StateSource<boolean>,
57
- shouldSuspend: (instance, doc: DocumentHandle) =>
54
+ shouldSuspend: (instance, doc: StrictDocumentHandle) =>
58
55
  getDocumentSyncStatus(instance, doc).getCurrent() === undefined,
59
- suspender: (instance, doc: DocumentHandle) => resolveDocument(instance, doc),
60
- getConfig: identity,
56
+ suspender: (instance, doc: StrictDocumentHandle) => resolveDocument(instance, doc),
61
57
  })
58
+
59
+ /**
60
+ * @public
61
+ * @function
62
+ */
63
+ export const useDocumentSyncStatus: UseDocumentSyncStatus = (options: DocumentHandle) => {
64
+ const normalizedOptions = useNormalizedResourceOptions(options)
65
+ return useDocumentSyncStatusValue(normalizedOptions)
66
+ }
@@ -7,10 +7,9 @@ import {
7
7
  type StateSource,
8
8
  } from '@sanity/sdk'
9
9
  import {type SanityDocument} from '@sanity/types'
10
- import {renderHook} from '@testing-library/react'
11
10
  import {beforeEach, describe, expect, it, vi} from 'vitest'
12
11
 
13
- import {ResourceProvider} from '../../context/ResourceProvider'
12
+ import {renderHook} from '../../../test/test-utils'
14
13
  import {useApplyDocumentActions} from './useApplyDocumentActions'
15
14
  import {useEditDocument} from './useEditDocument'
16
15
 
@@ -40,7 +39,13 @@ const doc = {
40
39
  const docHandle = createDocumentHandle({
41
40
  documentId: 'doc1',
42
41
  documentType: 'book',
42
+ resource: {projectId: 'test-project', dataset: 'test-dataset'},
43
43
  })
44
+ const normalizedDocHandle = {
45
+ documentId: 'doc1',
46
+ documentType: 'book',
47
+ resource: {projectId: 'test-project', dataset: 'test-dataset'},
48
+ }
44
49
 
45
50
  // Define a single generic TestDocument type
46
51
  interface Book extends SanityDocument {
@@ -50,16 +55,6 @@ interface Book extends SanityDocument {
50
55
  title?: string
51
56
  }
52
57
 
53
- // Scope the TestDocument type to the project/datasets used in tests
54
- type AllTestSchemaTypes = Book
55
-
56
- // Augment the 'groq' module
57
- declare module 'groq' {
58
- interface SanitySchemas {
59
- 'default:default': AllTestSchemaTypes
60
- }
61
- }
62
-
63
58
  describe('useEditDocument hook', () => {
64
59
  beforeEach(() => {
65
60
  vi.clearAllMocks()
@@ -76,16 +71,10 @@ describe('useEditDocument hook', () => {
76
71
  const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
77
72
  vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
78
73
 
79
- const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}), {
80
- wrapper: ({children}) => (
81
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
82
- {children}
83
- </ResourceProvider>
84
- ),
85
- })
74
+ const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}))
86
75
  const promise = result.current('newValue')
87
- expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'newValue'}})
88
- expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'newValue'}}))
76
+ expect(editDocument).toHaveBeenCalledWith(normalizedDocHandle, {set: {foo: 'newValue'}})
77
+ expect(apply).toHaveBeenCalledWith(editDocument(normalizedDocHandle, {set: {foo: 'newValue'}}))
89
78
  const actionsResult = await promise
90
79
  expect(actionsResult).toEqual({transactionId: 'tx1'})
91
80
  })
@@ -103,15 +92,9 @@ describe('useEditDocument hook', () => {
103
92
  const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
104
93
  vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
105
94
 
106
- const {result} = renderHook(() => useEditDocument(docHandle), {
107
- wrapper: ({children}) => (
108
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
109
- {children}
110
- </ResourceProvider>
111
- ),
112
- })
95
+ const {result} = renderHook(() => useEditDocument(docHandle))
113
96
  const promise = result.current({...doc, foo: 'baz', extra: 'old', _id: 'doc1'})
114
- expect(apply).toHaveBeenCalledWith([editDocument(docHandle, {set: {foo: 'baz'}})])
97
+ expect(apply).toHaveBeenCalledWith([editDocument(normalizedDocHandle, {set: {foo: 'baz'}})])
115
98
  const actionsResult = await promise
116
99
  expect(actionsResult).toEqual({transactionId: 'tx2'})
117
100
  })
@@ -127,16 +110,12 @@ describe('useEditDocument hook', () => {
127
110
  const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
128
111
  vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
129
112
 
130
- const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}), {
131
- wrapper: ({children}) => (
132
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
133
- {children}
134
- </ResourceProvider>
135
- ),
136
- })
113
+ const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}))
137
114
  const promise = result.current((prev: unknown) => `${prev}Updated`) // 'bar' becomes 'barUpdated'
138
- expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'barUpdated'}})
139
- expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'barUpdated'}}))
115
+ expect(editDocument).toHaveBeenCalledWith(normalizedDocHandle, {set: {foo: 'barUpdated'}})
116
+ expect(apply).toHaveBeenCalledWith(
117
+ editDocument(normalizedDocHandle, {set: {foo: 'barUpdated'}}),
118
+ )
140
119
  const actionsResult = await promise
141
120
  expect(actionsResult).toEqual({transactionId: 'tx3'})
142
121
  })
@@ -153,15 +132,9 @@ describe('useEditDocument hook', () => {
153
132
  const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
154
133
  vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
155
134
 
156
- const {result} = renderHook(() => useEditDocument(docHandle), {
157
- wrapper: ({children}) => (
158
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
159
- {children}
160
- </ResourceProvider>
161
- ),
162
- })
163
- const promise = result.current((prevDoc) => ({...prevDoc, foo: 'baz'}))
164
- expect(apply).toHaveBeenCalledWith([editDocument(docHandle, {set: {foo: 'baz'}})])
135
+ const {result} = renderHook(() => useEditDocument(docHandle))
136
+ const promise = result.current((prevDoc: Record<string, unknown>) => ({...prevDoc, foo: 'baz'}))
137
+ expect(apply).toHaveBeenCalledWith([editDocument(normalizedDocHandle, {set: {foo: 'baz'}})])
165
138
  const actionsResult = await promise
166
139
  expect(actionsResult).toEqual({transactionId: 'tx4'})
167
140
  })
@@ -177,13 +150,7 @@ describe('useEditDocument hook', () => {
177
150
  const fakeApply = vi.fn()
178
151
  vi.mocked(useApplyDocumentActions).mockReturnValue(fakeApply)
179
152
 
180
- const {result} = renderHook(() => useEditDocument(docHandle), {
181
- wrapper: ({children}) => (
182
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
183
- {children}
184
- </ResourceProvider>
185
- ),
186
- })
153
+ const {result} = renderHook(() => useEditDocument(docHandle))
187
154
  expect(() => result.current('notAnObject' as unknown as Book)).toThrowError(
188
155
  'No path was provided to `useEditDocument` and the value provided was not a document object.',
189
156
  )
@@ -203,22 +170,13 @@ describe('useEditDocument hook', () => {
203
170
  vi.mocked(resolveDocument).mockReturnValue(resolveDocPromise)
204
171
 
205
172
  // Render the hook and capture the thrown promise.
206
- const {result} = renderHook(
207
- () => {
208
- try {
209
- return useEditDocument(docHandle)
210
- } catch (e) {
211
- return e
212
- }
213
- },
214
- {
215
- wrapper: ({children}) => (
216
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
217
- {children}
218
- </ResourceProvider>
219
- ),
220
- },
221
- )
173
+ const {result} = renderHook(() => {
174
+ try {
175
+ return useEditDocument(docHandle)
176
+ } catch (e) {
177
+ return e
178
+ }
179
+ })
222
180
 
223
181
  // When the document is not ready, the hook throws the promise from resolveDocument.
224
182
  expect(result.current).toBe(resolveDocPromise)