@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.
- package/README.md +125 -63
- package/dist/index.d.ts +381 -571
- package/dist/index.js +435 -366
- package/dist/index.js.map +1 -1
- package/package.json +7 -9
- package/src/_exports/index.ts +4 -0
- package/src/_exports/sdk-react.ts +16 -0
- package/src/components/SDKProvider.test.tsx +23 -58
- package/src/components/SDKProvider.tsx +38 -30
- package/src/components/SanityApp.test.tsx +12 -68
- package/src/components/SanityApp.tsx +88 -65
- package/src/components/auth/AuthBoundary.test.tsx +8 -26
- package/src/components/auth/LoginError.tsx +5 -5
- package/src/config/handles.ts +53 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +99 -19
- package/src/context/ResourceProvider.tsx +103 -37
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SDKStudioContext.test.tsx +33 -28
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/context/renderSanityApp.test.tsx +49 -151
- package/src/context/renderSanityApp.tsx +8 -12
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +56 -19
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
- package/src/hooks/client/useClient.test.tsx +4 -1
- package/src/hooks/client/useClient.ts +0 -1
- package/src/hooks/context/useDefaultResource.test.tsx +25 -0
- package/src/hooks/context/useDefaultResource.ts +30 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -140
- package/src/hooks/context/useSanityInstance.ts +9 -53
- package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
- package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
- package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
- package/src/hooks/dashboard/useManageFavorite.ts +16 -10
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
- package/src/hooks/datasets/useDatasets.test.ts +8 -22
- package/src/hooks/datasets/useDatasets.ts +8 -16
- package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
- package/src/hooks/document/useApplyDocumentActions.ts +35 -37
- package/src/hooks/document/useDocument.test.tsx +8 -37
- package/src/hooks/document/useDocument.ts +78 -129
- package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
- package/src/hooks/document/useDocumentEvent.ts +21 -19
- package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
- package/src/hooks/document/useDocumentPermissions.ts +41 -28
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
- package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
- package/src/hooks/document/useEditDocument.test.tsx +28 -70
- package/src/hooks/document/useEditDocument.ts +29 -149
- package/src/hooks/documents/useDocuments.test.tsx +44 -64
- package/src/hooks/documents/useDocuments.ts +19 -25
- package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
- package/src/hooks/presence/usePresence.test.tsx +71 -9
- package/src/hooks/presence/usePresence.ts +28 -3
- package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
- package/src/hooks/preview/useDocumentPreview.tsx +42 -62
- package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
- package/src/hooks/projection/useDocumentProjection.ts +9 -82
- package/src/hooks/projects/useProject.test.ts +1 -2
- package/src/hooks/projects/useProject.ts +7 -8
- package/src/hooks/query/useQuery.test.tsx +5 -6
- package/src/hooks/query/useQuery.ts +12 -91
- package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
- package/src/hooks/releases/useActiveReleases.ts +25 -13
- package/src/hooks/releases/usePerspective.test.tsx +9 -17
- package/src/hooks/releases/usePerspective.ts +29 -18
- package/src/hooks/users/useUser.test.tsx +9 -3
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.test.tsx +5 -2
- package/src/hooks/users/useUsers.ts +1 -1
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {type SanityConfig, type SanityInstance} from '@sanity/sdk'
|
|
1
|
+
import {type DocumentResource, type SanityConfig, type SanityInstance} from '@sanity/sdk'
|
|
2
2
|
import {act, render, screen} from '@testing-library/react'
|
|
3
|
-
import {StrictMode, use, useEffect} from 'react'
|
|
4
|
-
import {describe, expect, it} from 'vitest'
|
|
3
|
+
import {StrictMode, use, useContext, useEffect} from 'react'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
5
|
|
|
6
|
+
import {ResourceContext} from './DefaultResourceContext'
|
|
7
|
+
import {PerspectiveContext} from './PerspectiveContext'
|
|
6
8
|
import {ResourceProvider} from './ResourceProvider'
|
|
7
9
|
import {SanityInstanceContext} from './SanityInstanceContext'
|
|
8
10
|
|
|
9
|
-
const testConfig
|
|
10
|
-
projectId: 'test-project',
|
|
11
|
-
dataset: 'test-dataset',
|
|
11
|
+
const testConfig = {
|
|
12
|
+
resource: {projectId: 'test-project', dataset: 'test-dataset'} as DocumentResource,
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function promiseWithResolvers<T = void>(): {
|
|
@@ -37,6 +38,7 @@ describe('ResourceProvider', () => {
|
|
|
37
38
|
})
|
|
38
39
|
|
|
39
40
|
it('shows fallback during loading', async () => {
|
|
41
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
40
42
|
const {promise, resolve} = promiseWithResolvers()
|
|
41
43
|
function SuspendingChild(): React.ReactNode {
|
|
42
44
|
throw promise
|
|
@@ -52,6 +54,8 @@ describe('ResourceProvider', () => {
|
|
|
52
54
|
act(() => {
|
|
53
55
|
resolve()
|
|
54
56
|
})
|
|
57
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
58
|
+
consoleSpy.mockRestore()
|
|
55
59
|
})
|
|
56
60
|
|
|
57
61
|
it('creates root instance when no parent context exists', async () => {
|
|
@@ -70,32 +74,105 @@ describe('ResourceProvider', () => {
|
|
|
70
74
|
)
|
|
71
75
|
|
|
72
76
|
await expect(promise).resolves.toMatchObject({
|
|
73
|
-
config:
|
|
77
|
+
config: {} as SanityConfig,
|
|
74
78
|
isDisposed: expect.any(Function),
|
|
75
79
|
})
|
|
76
80
|
})
|
|
77
81
|
|
|
78
|
-
it('
|
|
79
|
-
const
|
|
80
|
-
|
|
82
|
+
it('provides ResourceContext and PerspectiveContext at root', async () => {
|
|
83
|
+
const captured = promiseWithResolvers<{
|
|
84
|
+
resource: DocumentResource | undefined
|
|
85
|
+
perspective: unknown
|
|
86
|
+
}>()
|
|
81
87
|
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
88
|
+
const CaptureContexts = () => {
|
|
89
|
+
const resource = useContext(ResourceContext)
|
|
90
|
+
const perspective = useContext(PerspectiveContext)
|
|
91
|
+
useEffect(() => captured.resolve({resource, perspective}), [resource, perspective])
|
|
85
92
|
return null
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
render(
|
|
89
|
-
<ResourceProvider
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
<ResourceProvider
|
|
97
|
+
resource={{projectId: 'abc', dataset: 'prod'}}
|
|
98
|
+
perspective="drafts"
|
|
99
|
+
fallback={null}
|
|
100
|
+
>
|
|
101
|
+
<CaptureContexts />
|
|
102
|
+
</ResourceProvider>,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const result = await captured.promise
|
|
106
|
+
expect(result.resource).toEqual({projectId: 'abc', dataset: 'prod'})
|
|
107
|
+
expect(result.perspective).toBe('drafts')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('nested provider overrides resource and perspective via context', async () => {
|
|
111
|
+
const captured = promiseWithResolvers<{
|
|
112
|
+
instance: SanityInstance | null
|
|
113
|
+
resource: DocumentResource | undefined
|
|
114
|
+
perspective: unknown
|
|
115
|
+
}>()
|
|
116
|
+
|
|
117
|
+
const CaptureAll = () => {
|
|
118
|
+
const instance = use(SanityInstanceContext)
|
|
119
|
+
const resource = useContext(ResourceContext)
|
|
120
|
+
const perspective = useContext(PerspectiveContext)
|
|
121
|
+
useEffect(
|
|
122
|
+
() => captured.resolve({instance, resource, perspective}),
|
|
123
|
+
[instance, resource, perspective],
|
|
124
|
+
)
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
render(
|
|
129
|
+
<ResourceProvider resource={{projectId: 'parent-proj', dataset: 'parent-ds'}} fallback={null}>
|
|
130
|
+
<ResourceProvider
|
|
131
|
+
resource={{projectId: 'child-proj', dataset: 'child-ds'}}
|
|
132
|
+
perspective="drafts"
|
|
133
|
+
fallback={null}
|
|
134
|
+
>
|
|
135
|
+
<CaptureAll />
|
|
92
136
|
</ResourceProvider>
|
|
93
137
|
</ResourceProvider>,
|
|
94
138
|
)
|
|
95
139
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
expect(
|
|
140
|
+
const result = await captured.promise
|
|
141
|
+
// Instance is the parent's (nested provider does not create a new one)
|
|
142
|
+
expect(result.instance?.instanceId).toBeDefined()
|
|
143
|
+
// Resource and perspective come from the nested provider's context
|
|
144
|
+
expect(result.resource).toEqual({projectId: 'child-proj', dataset: 'child-ds'})
|
|
145
|
+
expect(result.perspective).toBe('drafts')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('nested provider inherits parent context when not overridden', async () => {
|
|
149
|
+
const captured = promiseWithResolvers<{
|
|
150
|
+
resource: DocumentResource | undefined
|
|
151
|
+
perspective: unknown
|
|
152
|
+
}>()
|
|
153
|
+
|
|
154
|
+
const CaptureContexts = () => {
|
|
155
|
+
const resource = useContext(ResourceContext)
|
|
156
|
+
const perspective = useContext(PerspectiveContext)
|
|
157
|
+
useEffect(() => captured.resolve({resource, perspective}), [resource, perspective])
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
render(
|
|
162
|
+
<ResourceProvider
|
|
163
|
+
resource={{projectId: 'parent-proj', dataset: 'parent-ds'}}
|
|
164
|
+
perspective="drafts"
|
|
165
|
+
fallback={null}
|
|
166
|
+
>
|
|
167
|
+
<ResourceProvider fallback={null}>
|
|
168
|
+
<CaptureContexts />
|
|
169
|
+
</ResourceProvider>
|
|
170
|
+
</ResourceProvider>,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const result = await captured.promise
|
|
174
|
+
expect(result.resource).toEqual({projectId: 'parent-proj', dataset: 'parent-ds'})
|
|
175
|
+
expect(result.perspective).toBe('drafts')
|
|
99
176
|
})
|
|
100
177
|
|
|
101
178
|
it('disposes instance when unmounted', async () => {
|
|
@@ -141,6 +218,7 @@ describe('ResourceProvider', () => {
|
|
|
141
218
|
})
|
|
142
219
|
|
|
143
220
|
it('uses default fallback when none provided', async () => {
|
|
221
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
144
222
|
const {promise, resolve} = promiseWithResolvers()
|
|
145
223
|
function SuspendingChild(): React.ReactNode {
|
|
146
224
|
throw promise
|
|
@@ -157,5 +235,7 @@ describe('ResourceProvider', () => {
|
|
|
157
235
|
act(() => {
|
|
158
236
|
resolve()
|
|
159
237
|
})
|
|
238
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
239
|
+
consoleSpy.mockRestore()
|
|
160
240
|
})
|
|
161
241
|
})
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createSanityInstance,
|
|
3
|
+
type DocumentResource,
|
|
4
|
+
type PerspectiveHandle,
|
|
5
|
+
type SanityConfig,
|
|
6
|
+
type SanityInstance,
|
|
7
|
+
} from '@sanity/sdk'
|
|
2
8
|
import {Suspense, useContext, useEffect, useMemo, useRef} from 'react'
|
|
3
9
|
|
|
10
|
+
import {ResourceContext} from './DefaultResourceContext'
|
|
11
|
+
import {PerspectiveContext} from './PerspectiveContext'
|
|
4
12
|
import {SanityInstanceContext} from './SanityInstanceContext'
|
|
5
13
|
|
|
6
14
|
const DEFAULT_FALLBACK = (
|
|
@@ -10,74 +18,102 @@ const DEFAULT_FALLBACK = (
|
|
|
10
18
|
)
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
|
-
* Props for the ResourceProvider component
|
|
21
|
+
* Props for the ResourceProvider component.
|
|
22
|
+
*
|
|
23
|
+
* Extends `SanityConfig` (minus `defaultResource`) so new config fields are
|
|
24
|
+
* automatically forwarded. The `resource` prop replaces `defaultResource`
|
|
25
|
+
* with a name that better describes its role at the React layer.
|
|
26
|
+
*
|
|
14
27
|
* @internal
|
|
15
28
|
*/
|
|
16
|
-
export interface ResourceProviderProps extends SanityConfig {
|
|
29
|
+
export interface ResourceProviderProps extends Omit<SanityConfig, 'defaultResource'> {
|
|
30
|
+
/**
|
|
31
|
+
* The document resource (project/dataset, media library, or canvas)
|
|
32
|
+
* for this subtree. Hooks that don't specify an explicit resource will
|
|
33
|
+
* use this value.
|
|
34
|
+
*/
|
|
35
|
+
resource?: DocumentResource
|
|
17
36
|
/**
|
|
18
|
-
* React node to show while content is loading
|
|
19
|
-
* Used as the fallback for the internal Suspense boundary
|
|
37
|
+
* React node to show while content is loading.
|
|
38
|
+
* Used as the fallback for the internal Suspense boundary.
|
|
20
39
|
*/
|
|
21
40
|
fallback: React.ReactNode
|
|
22
41
|
children: React.ReactNode
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
/**
|
|
26
|
-
* Provides
|
|
45
|
+
* Provides Sanity configuration to child components through React Context.
|
|
27
46
|
*
|
|
28
47
|
* @internal
|
|
29
48
|
*
|
|
30
49
|
* @remarks
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* -
|
|
34
|
-
*
|
|
50
|
+
* - **Root usage** (no parent instance): creates a `SanityInstance` with the
|
|
51
|
+
* given config and provides it via `SanityInstanceContext`.
|
|
52
|
+
* - **Nested usage** (inside an existing provider): sets
|
|
53
|
+
* `ResourceContext` and `PerspectiveContext` so hooks in the subtree
|
|
54
|
+
* resolve the correct resource/perspective without creating a new instance.
|
|
35
55
|
*
|
|
36
|
-
*
|
|
37
|
-
* - Automatically manages the lifecycle of Sanity instances
|
|
38
|
-
* - Disposes instances when the component unmounts
|
|
39
|
-
* - Includes a Suspense boundary for data loading
|
|
40
|
-
* - Enables hierarchical configuration inheritance
|
|
41
|
-
*
|
|
42
|
-
* Use this component to:
|
|
43
|
-
* - Set up project/dataset configuration for an application
|
|
44
|
-
* - Override specific configuration values in a section of your app
|
|
45
|
-
* - Create isolated instance hierarchies for different features
|
|
46
|
-
*
|
|
47
|
-
* @example Creating a root provider
|
|
56
|
+
* @example Root provider
|
|
48
57
|
* ```tsx
|
|
49
58
|
* <ResourceProvider
|
|
50
|
-
* projectId
|
|
51
|
-
* dataset="production"
|
|
59
|
+
* resource={{ projectId: 'your-project-id', dataset: 'production' }}
|
|
52
60
|
* fallback={<LoadingSpinner />}
|
|
53
61
|
* >
|
|
54
62
|
* <YourApp />
|
|
55
63
|
* </ResourceProvider>
|
|
56
64
|
* ```
|
|
57
65
|
*
|
|
58
|
-
* @example
|
|
66
|
+
* @example Nested override
|
|
59
67
|
* ```tsx
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* <
|
|
63
|
-
*
|
|
64
|
-
* <
|
|
65
|
-
* <PreviewFeatures />
|
|
66
|
-
* </ResourceProvider>
|
|
68
|
+
* <ResourceProvider
|
|
69
|
+
* resource={{ projectId: 'other-project', dataset: 'staging' }}
|
|
70
|
+
* fallback={<LoadingSpinner />}
|
|
71
|
+
* >
|
|
72
|
+
* <SubSection />
|
|
67
73
|
* </ResourceProvider>
|
|
68
74
|
* ```
|
|
69
75
|
*/
|
|
70
76
|
export function ResourceProvider({
|
|
71
77
|
children,
|
|
72
78
|
fallback,
|
|
73
|
-
|
|
79
|
+
resource,
|
|
80
|
+
...rest
|
|
74
81
|
}: ResourceProviderProps): React.ReactNode {
|
|
75
82
|
const parent = useContext(SanityInstanceContext)
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
const {perspective, auth, studio} = rest
|
|
84
|
+
const config: SanityConfig = useMemo(
|
|
85
|
+
() => ({perspective, auth, studio}),
|
|
86
|
+
[perspective, auth, studio],
|
|
79
87
|
)
|
|
80
88
|
|
|
89
|
+
if (parent) {
|
|
90
|
+
return (
|
|
91
|
+
<NestedResourceProvider resource={resource} perspective={perspective} fallback={fallback}>
|
|
92
|
+
{children}
|
|
93
|
+
</NestedResourceProvider>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<RootResourceProvider config={config} resource={resource} fallback={fallback}>
|
|
99
|
+
{children}
|
|
100
|
+
</RootResourceProvider>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function RootResourceProvider({
|
|
105
|
+
children,
|
|
106
|
+
fallback,
|
|
107
|
+
config,
|
|
108
|
+
resource,
|
|
109
|
+
}: {
|
|
110
|
+
children: React.ReactNode
|
|
111
|
+
fallback: React.ReactNode
|
|
112
|
+
config: SanityConfig
|
|
113
|
+
resource?: DocumentResource
|
|
114
|
+
}): React.ReactNode {
|
|
115
|
+
const instance = useMemo(() => createSanityInstance(config), [config])
|
|
116
|
+
|
|
81
117
|
// Ref to hold the scheduled disposal timer.
|
|
82
118
|
const disposal = useRef<{
|
|
83
119
|
instance: SanityInstance
|
|
@@ -105,7 +141,37 @@ export function ResourceProvider({
|
|
|
105
141
|
|
|
106
142
|
return (
|
|
107
143
|
<SanityInstanceContext.Provider value={instance}>
|
|
108
|
-
<
|
|
144
|
+
<ResourceContext.Provider value={resource}>
|
|
145
|
+
<PerspectiveContext.Provider value={config.perspective}>
|
|
146
|
+
<Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
|
|
147
|
+
</PerspectiveContext.Provider>
|
|
148
|
+
</ResourceContext.Provider>
|
|
109
149
|
</SanityInstanceContext.Provider>
|
|
110
150
|
)
|
|
111
151
|
}
|
|
152
|
+
|
|
153
|
+
function NestedResourceProvider({
|
|
154
|
+
children,
|
|
155
|
+
fallback,
|
|
156
|
+
resource,
|
|
157
|
+
perspective,
|
|
158
|
+
}: {
|
|
159
|
+
children: React.ReactNode
|
|
160
|
+
fallback: React.ReactNode
|
|
161
|
+
resource?: DocumentResource
|
|
162
|
+
perspective?: PerspectiveHandle['perspective']
|
|
163
|
+
}): React.ReactNode {
|
|
164
|
+
const parentResource = useContext(ResourceContext)
|
|
165
|
+
const parentPerspective = useContext(PerspectiveContext)
|
|
166
|
+
|
|
167
|
+
const resolvedResource = resource ?? parentResource
|
|
168
|
+
const resolvedPerspective = perspective ?? parentPerspective
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<ResourceContext.Provider value={resolvedResource}>
|
|
172
|
+
<PerspectiveContext.Provider value={resolvedPerspective}>
|
|
173
|
+
<Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
|
|
174
|
+
</PerspectiveContext.Provider>
|
|
175
|
+
</ResourceContext.Provider>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -5,7 +5,7 @@ import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
5
5
|
import {SanityApp} from '../components/SanityApp'
|
|
6
6
|
import {SDKStudioContext, type StudioWorkspaceHandle} from './SDKStudioContext'
|
|
7
7
|
|
|
8
|
-
// Mock SDKProvider to capture the config it receives
|
|
8
|
+
// Mock SDKProvider to capture the config and resources it receives
|
|
9
9
|
const mockSDKProvider = vi.hoisted(() => vi.fn())
|
|
10
10
|
vi.mock('../components/SDKProvider', () => ({
|
|
11
11
|
SDKProvider: mockSDKProvider.mockImplementation(({children}) => (
|
|
@@ -45,24 +45,31 @@ describe('SDKStudioContext', () => {
|
|
|
45
45
|
|
|
46
46
|
expect(mockSDKProvider).toHaveBeenCalled()
|
|
47
47
|
const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
|
|
48
|
+
// config carries studio auth only; no resource fields
|
|
48
49
|
expect(receivedConfig).toMatchObject({
|
|
49
|
-
projectId: 'studio-project-id',
|
|
50
|
-
dataset: 'production',
|
|
51
50
|
studio: {
|
|
52
51
|
auth: {token: mockWorkspace.auth.token},
|
|
53
52
|
},
|
|
54
53
|
})
|
|
54
|
+
expect(receivedConfig).not.toHaveProperty('defaultResource')
|
|
55
|
+
// resource goes into the resources map, not config
|
|
56
|
+
const receivedResources = mockSDKProvider.mock.calls[0][0].resources
|
|
57
|
+
expect(receivedResources).toEqual({
|
|
58
|
+
default: {projectId: 'studio-project-id', dataset: 'production'},
|
|
59
|
+
})
|
|
55
60
|
})
|
|
56
61
|
|
|
57
62
|
it('explicit config takes precedence over SDKStudioContext', () => {
|
|
58
|
-
const explicitConfig: SanityConfig = {
|
|
59
|
-
|
|
60
|
-
dataset: 'staging',
|
|
61
|
-
}
|
|
63
|
+
const explicitConfig: SanityConfig = {}
|
|
64
|
+
const explicitResources = {default: {projectId: 'explicit-project', dataset: 'staging'}}
|
|
62
65
|
|
|
63
66
|
render(
|
|
64
67
|
<SDKStudioContext.Provider value={mockWorkspace}>
|
|
65
|
-
<SanityApp
|
|
68
|
+
<SanityApp
|
|
69
|
+
config={explicitConfig}
|
|
70
|
+
resources={explicitResources}
|
|
71
|
+
fallback={<div>Loading</div>}
|
|
72
|
+
>
|
|
66
73
|
<div>Child</div>
|
|
67
74
|
</SanityApp>
|
|
68
75
|
</SDKStudioContext.Provider>,
|
|
@@ -70,32 +77,31 @@ describe('SDKStudioContext', () => {
|
|
|
70
77
|
|
|
71
78
|
expect(mockSDKProvider).toHaveBeenCalled()
|
|
72
79
|
const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
|
|
73
|
-
expect(receivedConfig).
|
|
74
|
-
projectId: 'explicit-project',
|
|
75
|
-
dataset: 'staging',
|
|
76
|
-
})
|
|
77
|
-
// Should NOT have studio config from the context
|
|
80
|
+
expect(receivedConfig).toEqual(explicitConfig)
|
|
78
81
|
expect(receivedConfig.studio).toBeUndefined()
|
|
82
|
+
const receivedResources = mockSDKProvider.mock.calls[0][0].resources
|
|
83
|
+
expect(receivedResources).toEqual(explicitResources)
|
|
79
84
|
})
|
|
80
85
|
|
|
81
86
|
it('SanityApp works without SDKStudioContext (standalone mode)', () => {
|
|
82
|
-
const standaloneConfig: SanityConfig = {
|
|
83
|
-
|
|
84
|
-
dataset: 'production',
|
|
85
|
-
}
|
|
87
|
+
const standaloneConfig: SanityConfig = {}
|
|
88
|
+
const standaloneResources = {default: {projectId: 'standalone-project', dataset: 'production'}}
|
|
86
89
|
|
|
87
90
|
render(
|
|
88
|
-
<SanityApp
|
|
91
|
+
<SanityApp
|
|
92
|
+
config={standaloneConfig}
|
|
93
|
+
resources={standaloneResources}
|
|
94
|
+
fallback={<div>Loading</div>}
|
|
95
|
+
>
|
|
89
96
|
<div>Child</div>
|
|
90
97
|
</SanityApp>,
|
|
91
98
|
)
|
|
92
99
|
|
|
93
100
|
expect(mockSDKProvider).toHaveBeenCalled()
|
|
94
101
|
const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
|
|
95
|
-
expect(receivedConfig).
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
})
|
|
102
|
+
expect(receivedConfig).toEqual(standaloneConfig)
|
|
103
|
+
const receivedResources = mockSDKProvider.mock.calls[0][0].resources
|
|
104
|
+
expect(receivedResources).toEqual(standaloneResources)
|
|
99
105
|
})
|
|
100
106
|
|
|
101
107
|
it('handles workspace without auth.token (older Studio)', () => {
|
|
@@ -115,12 +121,11 @@ describe('SDKStudioContext', () => {
|
|
|
115
121
|
|
|
116
122
|
expect(mockSDKProvider).toHaveBeenCalled()
|
|
117
123
|
const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
|
|
118
|
-
expect(receivedConfig).toMatchObject({
|
|
119
|
-
projectId: 'older-studio',
|
|
120
|
-
dataset: 'production',
|
|
121
|
-
})
|
|
122
|
-
// studio config should be present but auth.token should be undefined
|
|
123
|
-
expect(receivedConfig.studio).toBeDefined()
|
|
124
|
+
expect(receivedConfig).toMatchObject({studio: expect.any(Object)})
|
|
124
125
|
expect(receivedConfig.studio?.auth).toBeUndefined()
|
|
126
|
+
const receivedResources = mockSDKProvider.mock.calls[0][0].resources
|
|
127
|
+
expect(receivedResources).toEqual({
|
|
128
|
+
default: {projectId: 'older-studio', dataset: 'production'},
|
|
129
|
+
})
|
|
125
130
|
})
|
|
126
131
|
})
|
|
@@ -13,6 +13,12 @@ export interface StudioWorkspaceHandle {
|
|
|
13
13
|
projectId: string
|
|
14
14
|
/** The dataset name for this workspace. */
|
|
15
15
|
dataset: string
|
|
16
|
+
/**
|
|
17
|
+
* Whether the Studio has determined the user is authenticated.
|
|
18
|
+
* When `true` and the token source emits `null`, the SDK infers
|
|
19
|
+
* cookie-based auth is in use and skips the logged-out state.
|
|
20
|
+
*/
|
|
21
|
+
authenticated?: boolean
|
|
16
22
|
/** Authentication state for this workspace. */
|
|
17
23
|
auth: {
|
|
18
24
|
/**
|