@sanity/sdk-react 2.9.0 → 2.11.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/dist/index.d.ts +338 -215
- package/dist/index.js +564 -342
- package/dist/index.js.map +1 -1
- package/package.json +9 -14
- package/src/_exports/index.ts +2 -0
- package/src/_exports/sdk-react.ts +8 -0
- package/src/components/SDKProvider.test.tsx +5 -12
- package/src/components/SDKProvider.tsx +58 -28
- package/src/components/SanityApp.tsx +2 -2
- package/src/components/auth/AuthBoundary.tsx +8 -1
- package/src/components/auth/DashboardAccessRequest.tsx +37 -0
- package/src/components/auth/LoginError.test.tsx +191 -5
- package/src/components/auth/LoginError.tsx +100 -56
- package/src/components/errors/ChunkLoadError.test.tsx +59 -0
- package/src/components/errors/ChunkLoadError.tsx +56 -0
- package/src/components/errors/chunkReloadStorage.ts +57 -0
- package/src/config/handles.ts +55 -0
- package/src/constants.ts +5 -0
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +2 -2
- package/src/context/ResourceProvider.tsx +56 -51
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SanityInstanceProvider.test.tsx +100 -0
- package/src/context/SanityInstanceProvider.tsx +71 -0
- package/src/hooks/agent/agentActions.ts +55 -38
- package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
- package/src/hooks/context/useResource.test.tsx +32 -0
- package/src/hooks/context/useResource.ts +24 -0
- package/src/hooks/context/useSanityInstance.test.tsx +42 -111
- package/src/hooks/context/useSanityInstance.ts +28 -50
- package/src/hooks/dashboard/useDispatchIntent.test.ts +11 -7
- package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
- package/src/hooks/dashboard/useManageFavorite.test.tsx +16 -12
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +15 -15
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +13 -17
- package/src/hooks/document/{useApplyDocumentActions.test.ts → useApplyDocumentActions.test.tsx} +46 -81
- package/src/hooks/document/useApplyDocumentActions.ts +33 -67
- package/src/hooks/document/useDocument.ts +4 -6
- package/src/hooks/document/useDocumentEvent.ts +8 -7
- package/src/hooks/document/useDocumentPermissions.test.tsx +60 -152
- package/src/hooks/document/useDocumentPermissions.ts +78 -55
- package/src/hooks/document/useDocumentSyncStatus.ts +2 -2
- package/src/hooks/document/useEditDocument.test.tsx +25 -60
- package/src/hooks/document/useEditDocument.ts +3 -3
- package/src/hooks/documents/useDocuments.ts +19 -11
- package/src/hooks/helpers/createStateSourceHook.tsx +1 -2
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +169 -0
- package/src/hooks/helpers/useTrackHookUsage.ts +2 -2
- package/src/hooks/organizations/useOrganization.test-d.ts +53 -0
- package/src/hooks/organizations/useOrganization.test.ts +65 -0
- package/src/hooks/organizations/useOrganization.ts +40 -0
- package/src/hooks/organizations/useOrganizations.test-d.ts +55 -0
- package/src/hooks/organizations/useOrganizations.test.ts +85 -0
- package/src/hooks/organizations/useOrganizations.ts +45 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +29 -14
- package/src/hooks/presence/usePresence.test.tsx +56 -9
- package/src/hooks/presence/usePresence.ts +16 -4
- package/src/hooks/preview/useDocumentPreview.tsx +8 -10
- package/src/hooks/projection/useDocumentProjection.ts +7 -9
- package/src/hooks/projects/useProject.test-d.ts +49 -0
- package/src/hooks/projects/useProject.ts +33 -41
- package/src/hooks/projects/useProjects.test-d.ts +49 -0
- package/src/hooks/projects/useProjects.ts +17 -23
- package/src/hooks/query/useQuery.ts +11 -10
- package/src/hooks/releases/useActiveReleases.ts +14 -14
- package/src/hooks/releases/usePerspective.ts +11 -16
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.ts +1 -1
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -107
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {createSanityInstance, type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {act, render, screen} from '@testing-library/react'
|
|
3
|
+
import {use, useEffect} from 'react'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {SanityInstanceContext} from './SanityInstanceContext'
|
|
7
|
+
import {SanityInstanceProvider} from './SanityInstanceProvider'
|
|
8
|
+
|
|
9
|
+
function promiseWithResolvers<T = void>(): {
|
|
10
|
+
promise: Promise<T>
|
|
11
|
+
resolve: (t: T) => void
|
|
12
|
+
reject: (error: unknown) => void
|
|
13
|
+
} {
|
|
14
|
+
let resolve!: (t: T) => void
|
|
15
|
+
let reject!: (error: unknown) => void
|
|
16
|
+
const promise = new Promise<T>((res, rej) => {
|
|
17
|
+
resolve = res
|
|
18
|
+
reject = rej
|
|
19
|
+
})
|
|
20
|
+
return {resolve, reject, promise}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('SanityInstanceProvider', () => {
|
|
24
|
+
it('renders children', () => {
|
|
25
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
26
|
+
|
|
27
|
+
render(
|
|
28
|
+
<SanityInstanceProvider instance={instance} fallback={<div>Loading...</div>}>
|
|
29
|
+
<div data-testid="test-child">Child Component</div>
|
|
30
|
+
</SanityInstanceProvider>,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expect(screen.getByTestId('test-child')).toBeInTheDocument()
|
|
34
|
+
instance.dispose()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('provides the given instance via context', async () => {
|
|
38
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
39
|
+
const {promise, resolve} = promiseWithResolvers<SanityInstance | null>()
|
|
40
|
+
|
|
41
|
+
const CaptureInstance = () => {
|
|
42
|
+
const ctx = use(SanityInstanceContext)
|
|
43
|
+
useEffect(() => resolve(ctx), [ctx])
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render(
|
|
48
|
+
<SanityInstanceProvider instance={instance} fallback={null}>
|
|
49
|
+
<CaptureInstance />
|
|
50
|
+
</SanityInstanceProvider>,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const provided = await promise
|
|
54
|
+
expect(provided).toBe(instance)
|
|
55
|
+
instance.dispose()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('shows fallback during suspense', async () => {
|
|
59
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
60
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
61
|
+
const {promise, resolve} = promiseWithResolvers()
|
|
62
|
+
|
|
63
|
+
function SuspendingChild(): React.ReactNode {
|
|
64
|
+
throw promise
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render(
|
|
68
|
+
<SanityInstanceProvider
|
|
69
|
+
instance={instance}
|
|
70
|
+
fallback={<div data-testid="fallback">Loading...</div>}
|
|
71
|
+
>
|
|
72
|
+
<SuspendingChild />
|
|
73
|
+
</SanityInstanceProvider>,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId('fallback')).toBeInTheDocument()
|
|
77
|
+
act(() => {
|
|
78
|
+
resolve()
|
|
79
|
+
})
|
|
80
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
81
|
+
instance.dispose()
|
|
82
|
+
consoleSpy.mockRestore()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('does not dispose the instance on unmount', async () => {
|
|
86
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
87
|
+
|
|
88
|
+
const {unmount} = render(
|
|
89
|
+
<SanityInstanceProvider instance={instance} fallback={null}>
|
|
90
|
+
<div />
|
|
91
|
+
</SanityInstanceProvider>,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
unmount()
|
|
95
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
96
|
+
|
|
97
|
+
expect(instance.isDisposed()).toBe(false)
|
|
98
|
+
instance.dispose()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {Suspense} from 'react'
|
|
3
|
+
|
|
4
|
+
import {SanityInstanceContext} from './SanityInstanceContext'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for the SanityInstanceProvider component
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface SanityInstanceProviderProps {
|
|
11
|
+
/**
|
|
12
|
+
* A pre-created SanityInstance to provide to child components.
|
|
13
|
+
* The caller owns the instance lifecycle — SanityInstanceProvider
|
|
14
|
+
* will not dispose it on unmount.
|
|
15
|
+
*/
|
|
16
|
+
instance: SanityInstance
|
|
17
|
+
/**
|
|
18
|
+
* React node to show while content is loading.
|
|
19
|
+
* Used as the fallback for the internal Suspense boundary.
|
|
20
|
+
*/
|
|
21
|
+
fallback: React.ReactNode
|
|
22
|
+
children: React.ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Provides an externally-created Sanity instance to child components through React Context.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Unlike {@link ResourceProvider}, this component does not create or dispose a SanityInstance.
|
|
32
|
+
* The caller is responsible for creating the instance via `createSanityInstance` and disposing
|
|
33
|
+
* it when appropriate. This is useful when a non-React system layer (e.g. a state machine)
|
|
34
|
+
* owns the instance and the React tree should consume it without managing its lifecycle.
|
|
35
|
+
*
|
|
36
|
+
* All SDK hooks (`useSanityInstance`, `useDocuments`, etc.) will read from the provided instance.
|
|
37
|
+
*
|
|
38
|
+
* @example Providing a pre-created instance
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { createSanityInstance, type SanityConfig } from '@sanity/sdk'
|
|
41
|
+
* import { SanityInstanceProvider } from '@sanity/sdk-react'
|
|
42
|
+
*
|
|
43
|
+
* const config: SanityConfig = {
|
|
44
|
+
* projectId: 'my-project-id',
|
|
45
|
+
* dataset: 'production',
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const instance = createSanityInstance(config)
|
|
49
|
+
*
|
|
50
|
+
* function App() {
|
|
51
|
+
* return (
|
|
52
|
+
* <SanityInstanceProvider instance={instance} fallback={<div>Loading...</div>}>
|
|
53
|
+
* <MyApp />
|
|
54
|
+
* </SanityInstanceProvider>
|
|
55
|
+
* )
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @category Components
|
|
60
|
+
*/
|
|
61
|
+
export function SanityInstanceProvider({
|
|
62
|
+
instance,
|
|
63
|
+
fallback,
|
|
64
|
+
children,
|
|
65
|
+
}: SanityInstanceProviderProps): React.ReactNode {
|
|
66
|
+
return (
|
|
67
|
+
<SanityInstanceContext.Provider value={instance}>
|
|
68
|
+
<Suspense fallback={fallback}>{children}</Suspense>
|
|
69
|
+
</SanityInstanceContext.Provider>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
type AgentTransformOptions,
|
|
12
12
|
agentTranslate,
|
|
13
13
|
type AgentTranslateOptions,
|
|
14
|
-
type SanityInstance,
|
|
15
14
|
} from '@sanity/sdk'
|
|
15
|
+
import {useCallback} from 'react'
|
|
16
16
|
import {firstValueFrom} from 'rxjs'
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {type ResourceHandle} from '../../config/handles'
|
|
19
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
20
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
19
21
|
|
|
20
22
|
interface Subscription {
|
|
21
23
|
unsubscribe(): void
|
|
@@ -103,10 +105,17 @@ interface Subscribable<T> {
|
|
|
103
105
|
*
|
|
104
106
|
* @category Agent Actions
|
|
105
107
|
*/
|
|
106
|
-
export
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
export function useAgentGenerate(
|
|
109
|
+
resourceHandle?: ResourceHandle,
|
|
110
|
+
): (options: AgentGenerateOptions) => Subscribable<unknown> {
|
|
111
|
+
const instance = useSanityInstance()
|
|
112
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
113
|
+
return useCallback(
|
|
114
|
+
(options: AgentGenerateOptions) =>
|
|
115
|
+
agentGenerate(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
116
|
+
[instance, resource],
|
|
117
|
+
)
|
|
118
|
+
}
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
121
|
* @alpha
|
|
@@ -179,10 +188,17 @@ export const useAgentGenerate: () => (options: AgentGenerateOptions) => Subscrib
|
|
|
179
188
|
*
|
|
180
189
|
* @category Agent Actions
|
|
181
190
|
*/
|
|
182
|
-
export
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
export function useAgentTransform(
|
|
192
|
+
resourceHandle?: ResourceHandle,
|
|
193
|
+
): (options: AgentTransformOptions) => Subscribable<unknown> {
|
|
194
|
+
const instance = useSanityInstance()
|
|
195
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
196
|
+
return useCallback(
|
|
197
|
+
(options: AgentTransformOptions) =>
|
|
198
|
+
agentTransform(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
199
|
+
[instance, resource],
|
|
200
|
+
)
|
|
201
|
+
}
|
|
186
202
|
|
|
187
203
|
/**
|
|
188
204
|
* @alpha
|
|
@@ -274,20 +290,16 @@ export const useAgentTransform: () => (options: AgentTransformOptions) => Subscr
|
|
|
274
290
|
*
|
|
275
291
|
* @category Agent Actions
|
|
276
292
|
*/
|
|
277
|
-
export
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
instance: SanityInstance,
|
|
288
|
-
options: AgentPromptOptions,
|
|
289
|
-
): Promise<AgentPromptResult> {
|
|
290
|
-
return firstValueFrom(agentPrompt(instance, options))
|
|
293
|
+
export function useAgentTranslate(
|
|
294
|
+
resourceHandle?: ResourceHandle,
|
|
295
|
+
): (options: AgentTranslateOptions) => Subscribable<unknown> {
|
|
296
|
+
const instance = useSanityInstance()
|
|
297
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
298
|
+
return useCallback(
|
|
299
|
+
(options: AgentTranslateOptions) =>
|
|
300
|
+
agentTranslate(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
301
|
+
[instance, resource],
|
|
302
|
+
)
|
|
291
303
|
}
|
|
292
304
|
|
|
293
305
|
/**
|
|
@@ -384,18 +396,15 @@ function promptAdapter(
|
|
|
384
396
|
*
|
|
385
397
|
* @category Agent Actions
|
|
386
398
|
*/
|
|
387
|
-
export
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
options: AgentPatchOptions,
|
|
397
|
-
): Promise<AgentPatchResult> {
|
|
398
|
-
return firstValueFrom(agentPatch(instance, options))
|
|
399
|
+
export function useAgentPrompt(
|
|
400
|
+
resourceHandle?: ResourceHandle,
|
|
401
|
+
): (options: AgentPromptOptions) => Promise<AgentPromptResult> {
|
|
402
|
+
const instance = useSanityInstance()
|
|
403
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
404
|
+
return useCallback(
|
|
405
|
+
(options: AgentPromptOptions) => firstValueFrom(agentPrompt(instance, options, resource)),
|
|
406
|
+
[instance, resource],
|
|
407
|
+
)
|
|
399
408
|
}
|
|
400
409
|
|
|
401
410
|
/**
|
|
@@ -547,5 +556,13 @@ function patchAdapter(
|
|
|
547
556
|
*
|
|
548
557
|
* @category Agent Actions
|
|
549
558
|
*/
|
|
550
|
-
export
|
|
551
|
-
|
|
559
|
+
export function useAgentPatch(
|
|
560
|
+
resourceHandle?: ResourceHandle,
|
|
561
|
+
): (options: AgentPatchOptions) => Promise<AgentPatchResult> {
|
|
562
|
+
const instance = useSanityInstance()
|
|
563
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
564
|
+
return useCallback(
|
|
565
|
+
(options: AgentPatchOptions) => firstValueFrom(agentPatch(instance, options, resource)),
|
|
566
|
+
[instance, resource],
|
|
567
|
+
)
|
|
568
|
+
}
|
|
@@ -27,13 +27,20 @@ export function useVerifyOrgProjects(disabled = false, projectIds?: string[]): s
|
|
|
27
27
|
const instance = useSanityInstance()
|
|
28
28
|
const [error, setError] = useState<string | null>(null)
|
|
29
29
|
|
|
30
|
+
const isInactive = disabled || !projectIds || projectIds.length === 0
|
|
31
|
+
|
|
32
|
+
// Reset stale errors when verification turns off so the next activation
|
|
33
|
+
// doesn't briefly leak the previous result.
|
|
34
|
+
const [prevInactive, setPrevInactive] = useState(isInactive)
|
|
35
|
+
if (prevInactive !== isInactive) {
|
|
36
|
+
setPrevInactive(isInactive)
|
|
37
|
+
if (isInactive) setError(null)
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
useEffect(() => {
|
|
31
|
-
if (
|
|
32
|
-
if (error !== null) setError(null)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
41
|
+
if (isInactive) return
|
|
35
42
|
|
|
36
|
-
const verificationObservable$ = observeOrganizationVerificationState(instance, projectIds)
|
|
43
|
+
const verificationObservable$ = observeOrganizationVerificationState(instance, projectIds!)
|
|
37
44
|
|
|
38
45
|
const subscription = verificationObservable$.subscribe((result: OrgVerificationResult) => {
|
|
39
46
|
setError(result.error)
|
|
@@ -42,7 +49,7 @@ export function useVerifyOrgProjects(disabled = false, projectIds?: string[]): s
|
|
|
42
49
|
return () => {
|
|
43
50
|
subscription.unsubscribe()
|
|
44
51
|
}
|
|
45
|
-
}, [instance,
|
|
52
|
+
}, [instance, isInactive, projectIds])
|
|
46
53
|
|
|
47
54
|
return error
|
|
48
55
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {renderHook as reactRenderHook} from '@testing-library/react'
|
|
2
|
+
import {type ReactNode} from 'react'
|
|
3
|
+
import {describe, expect, it} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {renderHook} from '../../../test/test-utils'
|
|
6
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
7
|
+
import {useResource} from './useResource'
|
|
8
|
+
|
|
9
|
+
describe('useResource', () => {
|
|
10
|
+
it('returns the resource from the instance config when no explicit resource is set', () => {
|
|
11
|
+
// test-utils wraps with ResourceProvider projectId="test" dataset="test"
|
|
12
|
+
const {result} = renderHook(() => useResource())
|
|
13
|
+
expect(result.current).toEqual({projectId: 'test', dataset: 'test'})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns the explicit resource when ResourceProvider has a resource prop', () => {
|
|
17
|
+
const resource = {projectId: 'explicit-project', dataset: 'explicit-dataset'}
|
|
18
|
+
const {result} = reactRenderHook(() => useResource(), {
|
|
19
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
20
|
+
<ResourceProvider resource={resource} fallback={null}>
|
|
21
|
+
{children}
|
|
22
|
+
</ResourceProvider>
|
|
23
|
+
),
|
|
24
|
+
})
|
|
25
|
+
expect(result.current).toEqual(resource)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns undefined when no resource or instance config is available', () => {
|
|
29
|
+
const {result} = reactRenderHook(() => useResource())
|
|
30
|
+
expect(result.current).toBeUndefined()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {type DocumentResource} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
import {useEffectiveContextResource} from '../helpers/useNormalizedResourceOptions'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the currently active `DocumentResource` for the nearest resource context.
|
|
7
|
+
*
|
|
8
|
+
* Resolves in priority order:
|
|
9
|
+
* 1. A `resource` prop on the nearest `<ResourceProvider>`
|
|
10
|
+
* 2. The `projectId`/`dataset` from the current `SanityInstance` config
|
|
11
|
+
* 3. `undefined` when neither is available
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
* @category Platform
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const resource = useResource()
|
|
19
|
+
* console.log(resource?.projectId, resource?.dataset)
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function useResource(): DocumentResource | undefined {
|
|
23
|
+
return useEffectiveContextResource()
|
|
24
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {createSanityInstance, type SanityConfig, type SanityInstance} from '@sanity/sdk'
|
|
2
2
|
import {renderHook} from '@testing-library/react'
|
|
3
3
|
import {type ReactNode} from 'react'
|
|
4
|
-
import {describe, expect, it} from 'vitest'
|
|
4
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
5
5
|
|
|
6
6
|
import {SanityInstanceContext} from '../../context/SanityInstanceContext'
|
|
7
7
|
import {useSanityInstance} from './useSanityInstance'
|
|
@@ -37,137 +37,68 @@ describe('useSanityInstance', () => {
|
|
|
37
37
|
}).toThrow('SanityInstance context not found')
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
it('should include the requested config in error message when no instance found', () => {
|
|
41
|
-
const requestedConfig = {projectId: 'test', dataset: 'test'}
|
|
42
|
-
|
|
43
|
-
// Expect the hook to throw and include the requested config in the error
|
|
44
|
-
expect(() => {
|
|
45
|
-
renderHook(() => useSanityInstance(requestedConfig), {
|
|
46
|
-
wrapper: createWrapper(null),
|
|
47
|
-
})
|
|
48
|
-
}).toThrow(JSON.stringify(requestedConfig, null, 2))
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should find a matching instance with provided config', () => {
|
|
52
|
-
// Create a parent instance
|
|
53
|
-
const parentInstance = createSanityInstance({
|
|
54
|
-
projectId: 'parent-project',
|
|
55
|
-
dataset: 'parent-dataset',
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// Create a child instance
|
|
59
|
-
const childInstance = parentInstance.createChild({dataset: 'child-dataset'})
|
|
60
|
-
|
|
61
|
-
// Render the hook with the child instance and request the parent config
|
|
62
|
-
const {result} = renderHook(
|
|
63
|
-
() => useSanityInstance({projectId: 'parent-project', dataset: 'parent-dataset'}),
|
|
64
|
-
{wrapper: createWrapper(childInstance)},
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// Should match and return the parent instance
|
|
68
|
-
expect(result.current).toBe(parentInstance)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should throw an error if no matching instance is found for config', () => {
|
|
72
|
-
// Create an instance
|
|
73
|
-
const instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
74
|
-
|
|
75
|
-
// Request a config that doesn't match
|
|
76
|
-
const requestedConfig: SanityConfig = {
|
|
77
|
-
projectId: 'non-existent',
|
|
78
|
-
dataset: 'not-found',
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Expect the hook to throw for a non-matching config
|
|
82
|
-
expect(() => {
|
|
83
|
-
renderHook(() => useSanityInstance(requestedConfig), {
|
|
84
|
-
wrapper: createWrapper(instance),
|
|
85
|
-
})
|
|
86
|
-
}).toThrow('Could not find a matching Sanity instance')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('should include the requested config in error message when no matching instance', () => {
|
|
90
|
-
const instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
91
|
-
const requestedConfig = {projectId: 'different', dataset: 'different'}
|
|
92
|
-
|
|
93
|
-
// Expect the error to include the requested config details
|
|
94
|
-
expect(() => {
|
|
95
|
-
renderHook(() => useSanityInstance(requestedConfig), {
|
|
96
|
-
wrapper: createWrapper(instance),
|
|
97
|
-
})
|
|
98
|
-
}).toThrow(JSON.stringify(requestedConfig, null, 2))
|
|
99
|
-
})
|
|
100
|
-
|
|
101
40
|
it('should return the current instance when no config is provided', () => {
|
|
102
|
-
// Create a
|
|
103
|
-
const
|
|
104
|
-
const parent = grandparent.createChild({projectId: 'p'})
|
|
105
|
-
const child = parent.createChild({dataset: 'child-ds'})
|
|
41
|
+
// Create a Sanity instance
|
|
42
|
+
const instance = createSanityInstance({projectId: 'gp', dataset: 'gp-ds'})
|
|
106
43
|
|
|
107
|
-
// Render the hook with the
|
|
44
|
+
// Render the hook with the wrapper that provides the context
|
|
108
45
|
const {result} = renderHook(() => useSanityInstance(), {
|
|
109
|
-
wrapper: createWrapper(
|
|
46
|
+
wrapper: createWrapper(instance),
|
|
110
47
|
})
|
|
111
48
|
|
|
112
|
-
// Should return the
|
|
113
|
-
expect(result.current).toBe(
|
|
49
|
+
// Should return the instance
|
|
50
|
+
expect(result.current).toBe(instance)
|
|
114
51
|
})
|
|
115
52
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const parent = createSanityInstance({projectId: 'parent', dataset: 'parent-ds'})
|
|
53
|
+
describe('deprecated config parameter', () => {
|
|
54
|
+
let warnSpy: ReturnType<typeof vi.spyOn>
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
58
|
+
})
|
|
122
59
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
wrapper: createWrapper(child),
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
warnSpy.mockRestore()
|
|
126
62
|
})
|
|
127
63
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
64
|
+
it('should return the current context instance regardless of config', () => {
|
|
65
|
+
const instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
66
|
+
const requestedConfig: SanityConfig = {projectId: 'test-project', dataset: 'test-dataset'}
|
|
131
67
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
68
|
+
const {result} = renderHook(() => useSanityInstance(requestedConfig), {
|
|
69
|
+
wrapper: createWrapper(instance),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(result.current).toBe(instance)
|
|
137
73
|
})
|
|
138
74
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
75
|
+
it('should throw if no instance in context even when config is provided', () => {
|
|
76
|
+
expect(() => {
|
|
77
|
+
renderHook(() => useSanityInstance({projectId: 'test'}), {
|
|
78
|
+
wrapper: createWrapper(null),
|
|
79
|
+
})
|
|
80
|
+
}).toThrow('SanityInstance context not found')
|
|
142
81
|
})
|
|
143
82
|
|
|
144
|
-
|
|
145
|
-
|
|
83
|
+
it('warns once when a config argument is passed', () => {
|
|
84
|
+
const instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
85
|
+
const {rerender} = renderHook(() => useSanityInstance({projectId: 'test-project'}), {
|
|
86
|
+
wrapper: createWrapper(instance),
|
|
87
|
+
})
|
|
146
88
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const root = createSanityInstance({projectId: 'root', dataset: 'root-ds'})
|
|
150
|
-
const middle = root.createChild({projectId: 'middle'})
|
|
151
|
-
const leaf = middle.createChild({dataset: 'leaf-ds'})
|
|
89
|
+
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
90
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('[useSanityInstance]'))
|
|
152
91
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
92
|
+
rerender()
|
|
93
|
+
rerender()
|
|
94
|
+
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
156
95
|
})
|
|
157
96
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
97
|
+
it('does not warn when no config is passed', () => {
|
|
98
|
+
const instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
99
|
+
renderHook(() => useSanityInstance(), {wrapper: createWrapper(instance)})
|
|
161
100
|
|
|
162
|
-
|
|
163
|
-
// Create instance with only projectId
|
|
164
|
-
const rootInstance = createSanityInstance({projectId: 'test'})
|
|
165
|
-
|
|
166
|
-
// Match specifically looking for undefined dataset
|
|
167
|
-
const {result} = renderHook(() => useSanityInstance({dataset: undefined}), {
|
|
168
|
-
wrapper: createWrapper(rootInstance),
|
|
101
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
169
102
|
})
|
|
170
|
-
|
|
171
|
-
expect(result.current).toBe(rootInstance)
|
|
172
103
|
})
|
|
173
104
|
})
|