@sanity/sdk-react 0.0.0-alpha.20 → 0.0.0-alpha.21
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 +276 -207
- package/dist/index.js +90 -75
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/_exports/index.ts +11 -11
- package/src/components/Login/LoginLinks.test.tsx +2 -2
- package/src/components/Login/LoginLinks.tsx +2 -2
- package/src/components/auth/AuthBoundary.test.tsx +2 -2
- package/src/components/auth/LoginCallback.test.tsx +3 -3
- package/src/components/auth/LoginCallback.tsx +4 -4
- package/src/hooks/auth/useCurrentUser.tsx +1 -0
- package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
- package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +6 -6
- package/src/hooks/auth/useLogOut.test.tsx +2 -2
- package/src/hooks/client/useClient.ts +1 -0
- package/src/hooks/comlink/useManageFavorite.test.ts +9 -4
- package/src/hooks/comlink/useManageFavorite.ts +42 -13
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +7 -3
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +39 -12
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +178 -0
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +31 -5
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.test.tsx +5 -1
- package/src/hooks/dashboard/useStudioWorkspacesByResourceId.ts +4 -3
- package/src/hooks/datasets/useDatasets.ts +6 -3
- package/src/hooks/document/useApplyDocumentActions.test.ts +25 -0
- package/src/hooks/document/{useApplyActions.ts → useApplyDocumentActions.ts} +13 -12
- package/src/hooks/document/{usePermissions.ts → useDocumentPermissions.ts} +8 -6
- package/src/hooks/document/useDocumentSyncStatus.ts +4 -1
- package/src/hooks/document/useEditDocument.test.ts +8 -8
- package/src/hooks/document/useEditDocument.ts +2 -2
- package/src/hooks/{infiniteList/useInfiniteList.test.tsx → documents/useDocuments.test.tsx} +9 -9
- package/src/hooks/{infiniteList/useInfiniteList.ts → documents/useDocuments.ts} +10 -10
- package/src/hooks/{paginatedList/usePaginatedList.test.tsx → paginatedDocuments/usePaginatedDocuments.test.tsx} +14 -14
- package/src/hooks/{paginatedList/usePaginatedList.ts → paginatedDocuments/usePaginatedDocuments.ts} +7 -7
- package/src/hooks/preview/usePreview.tsx +2 -2
- package/src/hooks/projection/useProjection.ts +2 -2
- package/src/hooks/projects/useProject.ts +4 -1
- package/src/hooks/projects/useProjects.ts +7 -3
- package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
- package/src/hooks/document/useApplyActions.test.ts +0 -25
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {type Status} from '@sanity/comlink'
|
|
2
|
+
import {type DocumentHandle} from '@sanity/sdk'
|
|
3
|
+
import {act, renderHook} from '@testing-library/react'
|
|
4
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {useNavigateToStudioDocument} from './useNavigateToStudioDocument'
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
const mockSendMessage = vi.fn()
|
|
10
|
+
const mockFetch = vi.fn()
|
|
11
|
+
let mockWorkspacesByResourceId = {}
|
|
12
|
+
let mockWorkspacesIsConnected = true
|
|
13
|
+
let mockStatusCallback: ((status: Status) => void) | null = null
|
|
14
|
+
|
|
15
|
+
vi.mock('../comlink/useWindowConnection', () => {
|
|
16
|
+
return {
|
|
17
|
+
useWindowConnection: ({onStatus}: {onStatus?: (status: Status) => void}) => {
|
|
18
|
+
mockStatusCallback = onStatus || null
|
|
19
|
+
return {
|
|
20
|
+
sendMessage: mockSendMessage,
|
|
21
|
+
fetch: mockFetch,
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
vi.mock('./useStudioWorkspacesByResourceId', () => {
|
|
28
|
+
return {
|
|
29
|
+
useStudioWorkspacesByResourceId: () => ({
|
|
30
|
+
workspacesByResourceId: mockWorkspacesByResourceId,
|
|
31
|
+
error: null,
|
|
32
|
+
isConnected: mockWorkspacesIsConnected,
|
|
33
|
+
}),
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('useNavigateToStudioDocument', () => {
|
|
38
|
+
const mockDocumentHandle: DocumentHandle = {
|
|
39
|
+
_id: 'doc123',
|
|
40
|
+
_type: 'article',
|
|
41
|
+
resourceId: 'document:project1.dataset1:doc123',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mockWorkspace = {
|
|
45
|
+
name: 'workspace1',
|
|
46
|
+
title: 'Workspace 1',
|
|
47
|
+
basePath: '/workspace1',
|
|
48
|
+
dataset: 'dataset1',
|
|
49
|
+
userApplicationId: 'user1',
|
|
50
|
+
url: 'https://test.sanity.studio',
|
|
51
|
+
_ref: 'workspace123',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.resetAllMocks()
|
|
56
|
+
mockWorkspacesByResourceId = {
|
|
57
|
+
'project1:dataset1': [mockWorkspace],
|
|
58
|
+
}
|
|
59
|
+
mockWorkspacesIsConnected = true
|
|
60
|
+
mockStatusCallback = null
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns a function and connection status', () => {
|
|
64
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
|
|
65
|
+
|
|
66
|
+
// Initially not connected
|
|
67
|
+
expect(result.current.isConnected).toBe(false)
|
|
68
|
+
|
|
69
|
+
// Simulate connection
|
|
70
|
+
act(() => {
|
|
71
|
+
mockStatusCallback?.('connected')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
expect(result.current).toEqual({
|
|
75
|
+
navigateToStudioDocument: expect.any(Function),
|
|
76
|
+
isConnected: true,
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('sends correct navigation message when called', () => {
|
|
81
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
|
|
82
|
+
|
|
83
|
+
// Simulate connection
|
|
84
|
+
act(() => {
|
|
85
|
+
mockStatusCallback?.('connected')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
result.current.navigateToStudioDocument()
|
|
89
|
+
|
|
90
|
+
expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/bridge/navigate-to-resource', {
|
|
91
|
+
resourceId: 'workspace123',
|
|
92
|
+
resourceType: 'studio',
|
|
93
|
+
path: '/intent/edit/id=doc123;type=article',
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('does not send message when not connected', () => {
|
|
98
|
+
mockWorkspacesByResourceId = {}
|
|
99
|
+
mockWorkspacesIsConnected = false
|
|
100
|
+
|
|
101
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
|
|
102
|
+
|
|
103
|
+
// Simulate connection
|
|
104
|
+
act(() => {
|
|
105
|
+
mockStatusCallback?.('connected')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
result.current.navigateToStudioDocument()
|
|
109
|
+
|
|
110
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('does not send message when no workspace is found', () => {
|
|
114
|
+
mockWorkspacesByResourceId = {}
|
|
115
|
+
mockWorkspacesIsConnected = true
|
|
116
|
+
|
|
117
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
|
|
118
|
+
|
|
119
|
+
// Simulate connection
|
|
120
|
+
act(() => {
|
|
121
|
+
mockStatusCallback?.('connected')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
result.current.navigateToStudioDocument()
|
|
125
|
+
|
|
126
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('handles invalid resourceId format', () => {
|
|
130
|
+
const invalidDocHandle: DocumentHandle = {
|
|
131
|
+
_id: 'doc123',
|
|
132
|
+
_type: 'article',
|
|
133
|
+
resourceId: 'document:project1.invalid:doc123' as `document:${string}.${string}:${string}`,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(invalidDocHandle))
|
|
137
|
+
|
|
138
|
+
// Simulate connection
|
|
139
|
+
act(() => {
|
|
140
|
+
mockStatusCallback?.('connected')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
result.current.navigateToStudioDocument()
|
|
144
|
+
|
|
145
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('warns when multiple workspaces are found', () => {
|
|
149
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
150
|
+
const mockWorkspace2 = {...mockWorkspace, _ref: 'workspace2'}
|
|
151
|
+
|
|
152
|
+
mockWorkspacesByResourceId = {
|
|
153
|
+
'project1:dataset1': [mockWorkspace, mockWorkspace2],
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
|
|
157
|
+
|
|
158
|
+
// Simulate connection
|
|
159
|
+
act(() => {
|
|
160
|
+
mockStatusCallback?.('connected')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
result.current.navigateToStudioDocument()
|
|
164
|
+
|
|
165
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
166
|
+
'Multiple workspaces found for document',
|
|
167
|
+
mockDocumentHandle.resourceId,
|
|
168
|
+
)
|
|
169
|
+
expect(mockSendMessage).toHaveBeenCalledWith(
|
|
170
|
+
'dashboard/v1/bridge/navigate-to-resource',
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
resourceId: mockWorkspace._ref,
|
|
173
|
+
}),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
consoleSpy.mockRestore()
|
|
177
|
+
})
|
|
178
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {type Status} from '@sanity/comlink'
|
|
2
|
+
import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
3
|
import {type DocumentHandle} from '@sanity/sdk'
|
|
3
4
|
import {useCallback, useState} from 'react'
|
|
4
5
|
|
|
@@ -6,7 +7,7 @@ import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
|
6
7
|
import {useStudioWorkspacesByResourceId} from './useStudioWorkspacesByResourceId'
|
|
7
8
|
|
|
8
9
|
interface NavigateToResourceMessage {
|
|
9
|
-
type: '
|
|
10
|
+
type: 'dashboard/v1/bridge/navigate-to-resource'
|
|
10
11
|
data: {
|
|
11
12
|
/**
|
|
12
13
|
* Resource ID
|
|
@@ -32,10 +33,35 @@ interface NavigateToStudioResult {
|
|
|
32
33
|
/**
|
|
33
34
|
* @public
|
|
34
35
|
* Hook that provides a function to navigate to a studio document.
|
|
36
|
+
* Currently, requires a document handle with a resourceId.
|
|
37
|
+
* That resourceId is currently formatted like: `document:projectId.dataset:documentId`
|
|
38
|
+
* If the hook you used to retrieve the document handle doesn't provide a resourceId like this,
|
|
39
|
+
* you can construct it according to the above format with the document handle's _id.
|
|
40
|
+
*
|
|
41
|
+
* This will only work if you have deployed a studio with a workspace
|
|
42
|
+
* with this projectId / dataset combination.
|
|
43
|
+
* It may be able to take a custom URL in the future.
|
|
44
|
+
*
|
|
45
|
+
* This will likely change in the future.
|
|
35
46
|
* @param documentHandle - The document handle containing document ID, type, and resource ID
|
|
36
47
|
* @returns An object containing:
|
|
37
48
|
* - navigateToStudioDocument - Function that when called will navigate to the studio document
|
|
38
|
-
* - isConnected - Boolean indicating if connection to
|
|
49
|
+
* - isConnected - Boolean indicating if connection to Dashboard is established
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import {navigateToStudioDocument, type DocumentHandle} from '@sanity/sdk'
|
|
54
|
+
*
|
|
55
|
+
* function MyComponent({documentHandle}: {documentHandle: DocumentHandle}) {
|
|
56
|
+
* const {navigateToStudioDocument, isConnected} = useNavigateToStudioDocument(documentHandle)
|
|
57
|
+
*
|
|
58
|
+
* return (
|
|
59
|
+
* <button onClick={navigateToStudioDocument} disabled={!isConnected}>
|
|
60
|
+
* Navigate to Studio Document
|
|
61
|
+
* </button>
|
|
62
|
+
* )
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
39
65
|
*/
|
|
40
66
|
export function useNavigateToStudioDocument(
|
|
41
67
|
documentHandle: DocumentHandle,
|
|
@@ -44,8 +70,8 @@ export function useNavigateToStudioDocument(
|
|
|
44
70
|
useStudioWorkspacesByResourceId()
|
|
45
71
|
const [status, setStatus] = useState<Status>('idle')
|
|
46
72
|
const {sendMessage} = useWindowConnection<NavigateToResourceMessage, never>({
|
|
47
|
-
name:
|
|
48
|
-
connectTo:
|
|
73
|
+
name: SDK_NODE_NAME,
|
|
74
|
+
connectTo: SDK_CHANNEL_NAME,
|
|
49
75
|
onStatus: setStatus,
|
|
50
76
|
})
|
|
51
77
|
|
|
@@ -79,7 +105,7 @@ export function useNavigateToStudioDocument(
|
|
|
79
105
|
const workspace = workspaces[0]
|
|
80
106
|
|
|
81
107
|
const message: NavigateToResourceMessage = {
|
|
82
|
-
type: '
|
|
108
|
+
type: 'dashboard/v1/bridge/navigate-to-resource',
|
|
83
109
|
data: {
|
|
84
110
|
resourceId: workspace._ref,
|
|
85
111
|
resourceType: 'studio',
|
|
@@ -140,7 +140,11 @@ describe('useStudioWorkspacesByResourceId', () => {
|
|
|
140
140
|
expect(result.current.isConnected).toBe(true)
|
|
141
141
|
})
|
|
142
142
|
|
|
143
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
143
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
144
|
+
'dashboard/v1/bridge/context',
|
|
145
|
+
undefined,
|
|
146
|
+
expect.any(Object),
|
|
147
|
+
)
|
|
144
148
|
})
|
|
145
149
|
|
|
146
150
|
it('should handle fetch errors', async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {type Status} from '@sanity/comlink'
|
|
2
|
+
import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
3
|
import {useEffect, useState} from 'react'
|
|
3
4
|
|
|
4
5
|
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
@@ -33,8 +34,8 @@ export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
|
|
|
33
34
|
const [error, setError] = useState<string | null>(null)
|
|
34
35
|
|
|
35
36
|
const {fetch} = useWindowConnection({
|
|
36
|
-
name:
|
|
37
|
-
connectTo:
|
|
37
|
+
name: SDK_NODE_NAME,
|
|
38
|
+
connectTo: SDK_CHANNEL_NAME,
|
|
38
39
|
onStatus: setStatus,
|
|
39
40
|
})
|
|
40
41
|
|
|
@@ -47,7 +48,7 @@ export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
|
|
|
47
48
|
try {
|
|
48
49
|
const data = await fetch<{
|
|
49
50
|
context: {availableResources: Array<{projectId: string; workspaces: Workspace[]}>}
|
|
50
|
-
}>('
|
|
51
|
+
}>('dashboard/v1/bridge/context', undefined, {signal})
|
|
51
52
|
|
|
52
53
|
const workspaceMap: WorkspacesByResourceId = {}
|
|
53
54
|
|
|
@@ -6,10 +6,10 @@ import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
|
6
6
|
type UseDatasets = {
|
|
7
7
|
/**
|
|
8
8
|
*
|
|
9
|
-
* Returns metadata for each dataset
|
|
9
|
+
* Returns metadata for each dataset the current user has access to.
|
|
10
10
|
*
|
|
11
11
|
* @category Datasets
|
|
12
|
-
* @returns The metadata for your
|
|
12
|
+
* @returns The metadata for your the datasets
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```tsx
|
|
@@ -28,7 +28,10 @@ type UseDatasets = {
|
|
|
28
28
|
(): DatasetsResponse
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* @public
|
|
33
|
+
* @function
|
|
34
|
+
*/
|
|
32
35
|
export const useDatasets: UseDatasets = createStateSourceHook({
|
|
33
36
|
// remove `undefined` since we're suspending when that is the case
|
|
34
37
|
getState: getDatasetsState as (instance: SanityInstance) => StateSource<DatasetsResponse>,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {applyDocumentActions, createDocument, type ResourceId} from '@sanity/sdk'
|
|
2
|
+
import {describe, it} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
5
|
+
|
|
6
|
+
vi.mock('../helpers/createCallbackHook', () => ({
|
|
7
|
+
createCallbackHook: vi.fn((cb) => () => cb),
|
|
8
|
+
}))
|
|
9
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
10
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
11
|
+
return {...original, applyDocumentActions: vi.fn()}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('useApplyDocumentActions', () => {
|
|
15
|
+
it('calls `createCallbackHook` with `applyDocumentActions`', async () => {
|
|
16
|
+
const {useApplyDocumentActions} = await import('./useApplyDocumentActions')
|
|
17
|
+
const resourceId: ResourceId = 'project1.dataset1'
|
|
18
|
+
expect(createCallbackHook).not.toHaveBeenCalled()
|
|
19
|
+
|
|
20
|
+
expect(applyDocumentActions).not.toHaveBeenCalled()
|
|
21
|
+
const apply = useApplyDocumentActions(resourceId)
|
|
22
|
+
apply(createDocument({_type: 'author'}))
|
|
23
|
+
expect(applyDocumentActions).toHaveBeenCalledWith(createDocument({_type: 'author'}))
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ActionsResult,
|
|
3
|
-
|
|
4
|
-
type
|
|
3
|
+
applyDocumentActions,
|
|
4
|
+
type ApplyDocumentActionsOptions,
|
|
5
5
|
type DocumentAction,
|
|
6
6
|
type ResourceId,
|
|
7
7
|
} from '@sanity/sdk'
|
|
@@ -21,9 +21,9 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
|
21
21
|
* @example Publish or unpublish a document
|
|
22
22
|
* ```
|
|
23
23
|
* import { publishDocument, unpublishDocument } from '@sanity/sdk'
|
|
24
|
-
* import {
|
|
24
|
+
* import { useApplyDocumentActions } from '@sanity/sdk-react'
|
|
25
25
|
*
|
|
26
|
-
* const apply =
|
|
26
|
+
* const apply = useApplyDocumentActions()
|
|
27
27
|
* const myDocument = { _id: 'my-document-id', _type: 'my-document-type' }
|
|
28
28
|
*
|
|
29
29
|
* return (
|
|
@@ -35,9 +35,9 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
|
35
35
|
* @example Create and publish a new document
|
|
36
36
|
* ```
|
|
37
37
|
* import { createDocument, publishDocument } from '@sanity/sdk'
|
|
38
|
-
* import {
|
|
38
|
+
* import { useApplyDocumentActions } from '@sanity/sdk-react'
|
|
39
39
|
*
|
|
40
|
-
* const apply =
|
|
40
|
+
* const apply = useApplyDocumentActions()
|
|
41
41
|
*
|
|
42
42
|
* const handleCreateAndPublish = () => {
|
|
43
43
|
* const handle = { _id: window.crypto.randomUUID(), _type: 'my-document-type' }
|
|
@@ -54,21 +54,22 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
|
54
54
|
* )
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
|
-
export function
|
|
57
|
+
export function useApplyDocumentActions(
|
|
58
58
|
resourceId?: ResourceId,
|
|
59
59
|
): <TDocument extends SanityDocument>(
|
|
60
60
|
action: DocumentAction<TDocument> | DocumentAction<TDocument>[],
|
|
61
|
-
options?:
|
|
61
|
+
options?: ApplyDocumentActionsOptions,
|
|
62
62
|
) => Promise<ActionsResult<TDocument>>
|
|
63
63
|
|
|
64
64
|
/** @beta */
|
|
65
|
-
export function
|
|
65
|
+
export function useApplyDocumentActions(
|
|
66
66
|
resourceId?: ResourceId,
|
|
67
67
|
): (
|
|
68
68
|
action: DocumentAction | DocumentAction[],
|
|
69
|
-
options?:
|
|
69
|
+
options?: ApplyDocumentActionsOptions,
|
|
70
70
|
) => Promise<ActionsResult> {
|
|
71
|
-
return
|
|
71
|
+
return _useApplyDocumentActions(resourceId)()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const
|
|
74
|
+
const _useApplyDocumentActions = (resourceId?: ResourceId) =>
|
|
75
|
+
createCallbackHook(applyDocumentActions, resourceId)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type DocumentAction,
|
|
3
|
+
type DocumentPermissionsResult,
|
|
3
4
|
getPermissionsState,
|
|
4
5
|
getResourceId,
|
|
5
|
-
type PermissionsResult,
|
|
6
6
|
} from '@sanity/sdk'
|
|
7
7
|
import {useCallback, useMemo, useSyncExternalStore} from 'react'
|
|
8
8
|
import {filter, firstValueFrom} from 'rxjs'
|
|
@@ -21,12 +21,12 @@ import {useSanityInstance} from '../context/useSanityInstance'
|
|
|
21
21
|
*
|
|
22
22
|
* @example Checking for permission to publish a document
|
|
23
23
|
* ```ts
|
|
24
|
-
* import {
|
|
24
|
+
* import {useDocumentPermissions, useApplyDocumentActions} from '@sanity/sdk-react'
|
|
25
25
|
* import {publishDocument} from '@sanity/sdk'
|
|
26
26
|
*
|
|
27
27
|
* export function PublishButton({doc}: {doc: DocumentHandle}) {
|
|
28
|
-
* const publishPermissions =
|
|
29
|
-
* const applyAction =
|
|
28
|
+
* const publishPermissions = useDocumentPermissions(publishDocument(doc))
|
|
29
|
+
* const applyAction = useApplyDocumentActions()
|
|
30
30
|
*
|
|
31
31
|
* return (
|
|
32
32
|
* <>
|
|
@@ -47,7 +47,9 @@ import {useSanityInstance} from '../context/useSanityInstance'
|
|
|
47
47
|
* }
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
export function
|
|
50
|
+
export function useDocumentPermissions(
|
|
51
|
+
actions: DocumentAction | DocumentAction[],
|
|
52
|
+
): DocumentPermissionsResult {
|
|
51
53
|
// if actions is an array, we need to check each action to see if the resourceId is the same
|
|
52
54
|
if (Array.isArray(actions)) {
|
|
53
55
|
const resourceIds = actions.map((action) => action.resourceId)
|
|
@@ -78,5 +80,5 @@ export function usePermissions(actions: DocumentAction | DocumentAction[]): Perm
|
|
|
78
80
|
[actions, instance],
|
|
79
81
|
)
|
|
80
82
|
|
|
81
|
-
return useSyncExternalStore(subscribe, getCurrent) as
|
|
83
|
+
return useSyncExternalStore(subscribe, getCurrent) as DocumentPermissionsResult
|
|
82
84
|
}
|
|
@@ -12,7 +12,7 @@ import {renderHook} from '@testing-library/react'
|
|
|
12
12
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
13
13
|
|
|
14
14
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
15
|
-
import {
|
|
15
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
16
16
|
import {useEditDocument} from './useEditDocument'
|
|
17
17
|
|
|
18
18
|
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
@@ -29,8 +29,8 @@ vi.mock('../context/useSanityInstance', () => ({
|
|
|
29
29
|
useSanityInstance: vi.fn(),
|
|
30
30
|
}))
|
|
31
31
|
|
|
32
|
-
vi.mock('./
|
|
33
|
-
|
|
32
|
+
vi.mock('./useApplyDocumentActions', () => ({
|
|
33
|
+
useApplyDocumentActions: vi.fn(),
|
|
34
34
|
}))
|
|
35
35
|
|
|
36
36
|
// Create a fake instance to be returned by useSanityInstance.
|
|
@@ -66,7 +66,7 @@ describe('useEditDocument hook', () => {
|
|
|
66
66
|
} as unknown as StateSource<SanityDocument>)
|
|
67
67
|
|
|
68
68
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
|
|
69
|
-
vi.mocked(
|
|
69
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
70
70
|
|
|
71
71
|
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
72
72
|
const promise = result.current('newValue')
|
|
@@ -87,7 +87,7 @@ describe('useEditDocument hook', () => {
|
|
|
87
87
|
} as unknown as StateSource<SanityDocument>)
|
|
88
88
|
|
|
89
89
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
|
|
90
|
-
vi.mocked(
|
|
90
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
91
91
|
|
|
92
92
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
93
93
|
const promise = result.current({...doc, foo: 'baz', extra: 'old', _id: 'doc1'})
|
|
@@ -105,7 +105,7 @@ describe('useEditDocument hook', () => {
|
|
|
105
105
|
} as unknown as StateSource<SanityDocument>)
|
|
106
106
|
|
|
107
107
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
|
|
108
|
-
vi.mocked(
|
|
108
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
109
109
|
|
|
110
110
|
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
111
111
|
const promise = result.current((prev: unknown) => `${prev}Updated`) // 'bar' becomes 'barUpdated'
|
|
@@ -125,7 +125,7 @@ describe('useEditDocument hook', () => {
|
|
|
125
125
|
} as unknown as StateSource<SanityDocument>)
|
|
126
126
|
|
|
127
127
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
|
|
128
|
-
vi.mocked(
|
|
128
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
129
129
|
|
|
130
130
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
131
131
|
const promise = result.current((prevDoc) => ({...prevDoc, foo: 'baz'}))
|
|
@@ -143,7 +143,7 @@ describe('useEditDocument hook', () => {
|
|
|
143
143
|
} as unknown as StateSource<SanityDocument>)
|
|
144
144
|
|
|
145
145
|
const fakeApply = vi.fn()
|
|
146
|
-
vi.mocked(
|
|
146
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(fakeApply)
|
|
147
147
|
|
|
148
148
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
149
149
|
expect(() => result.current('notAnObject' as unknown as SanityDocument)).toThrowError(
|
|
@@ -12,7 +12,7 @@ import {type SanityDocument} from '@sanity/types'
|
|
|
12
12
|
import {useCallback} from 'react'
|
|
13
13
|
|
|
14
14
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
15
|
-
import {
|
|
15
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
16
16
|
|
|
17
17
|
const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
|
|
18
18
|
|
|
@@ -154,7 +154,7 @@ export function useEditDocument(
|
|
|
154
154
|
const resourceId = getResourceId(doc.resourceId)!
|
|
155
155
|
const documentId = doc._id
|
|
156
156
|
const instance = useSanityInstance(resourceId)
|
|
157
|
-
const apply =
|
|
157
|
+
const apply = useApplyDocumentActions(resourceId)
|
|
158
158
|
const isDocumentReady = useCallback(
|
|
159
159
|
() => getDocumentState(instance, documentId).getCurrent() !== undefined,
|
|
160
160
|
[instance, documentId],
|
|
@@ -3,11 +3,11 @@ import {describe, vi} from 'vitest'
|
|
|
3
3
|
|
|
4
4
|
import {evaluateSync, parse} from '../_synchronous-groq-js.mjs'
|
|
5
5
|
import {useQuery} from '../query/useQuery'
|
|
6
|
-
import {
|
|
6
|
+
import {useDocuments} from './useDocuments'
|
|
7
7
|
|
|
8
8
|
vi.mock('../query/useQuery')
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('useDocuments', () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
const dataset = [
|
|
13
13
|
{
|
|
@@ -76,13 +76,13 @@ describe('useInfiniteList', () => {
|
|
|
76
76
|
|
|
77
77
|
it('should respect custom page size', () => {
|
|
78
78
|
const customBatchSize = 2
|
|
79
|
-
const {result} = renderHook(() =>
|
|
79
|
+
const {result} = renderHook(() => useDocuments({batchSize: customBatchSize}))
|
|
80
80
|
|
|
81
81
|
expect(result.current.data.length).toBe(customBatchSize)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
it('should filter by document type', () => {
|
|
85
|
-
const {result} = renderHook(() =>
|
|
85
|
+
const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'}))
|
|
86
86
|
|
|
87
87
|
expect(result.current.data.every((doc) => doc._type === 'movie')).toBe(true)
|
|
88
88
|
expect(result.current.count).toBe(5) // 5 movies in the dataset
|
|
@@ -90,7 +90,7 @@ describe('useInfiniteList', () => {
|
|
|
90
90
|
|
|
91
91
|
// groq-js doesn't support search filters yet
|
|
92
92
|
it.skip('should apply search filter', () => {
|
|
93
|
-
const {result} = renderHook(() =>
|
|
93
|
+
const {result} = renderHook(() => useDocuments({search: 'inter'}))
|
|
94
94
|
|
|
95
95
|
// Should match "Interstellar"
|
|
96
96
|
expect(result.current.data.some((doc) => doc._id === 'movie3')).toBe(true)
|
|
@@ -98,7 +98,7 @@ describe('useInfiniteList', () => {
|
|
|
98
98
|
|
|
99
99
|
it('should apply ordering', () => {
|
|
100
100
|
const {result} = renderHook(() =>
|
|
101
|
-
|
|
101
|
+
useDocuments({
|
|
102
102
|
filter: '_type == "movie"',
|
|
103
103
|
orderings: [{field: 'releaseYear', direction: 'desc'}],
|
|
104
104
|
}),
|
|
@@ -110,7 +110,7 @@ describe('useInfiniteList', () => {
|
|
|
110
110
|
|
|
111
111
|
it('should load more data when loadMore is called', () => {
|
|
112
112
|
const batchSize = 2
|
|
113
|
-
const {result} = renderHook(() =>
|
|
113
|
+
const {result} = renderHook(() => useDocuments({batchSize: batchSize}))
|
|
114
114
|
|
|
115
115
|
expect(result.current.data.length).toBe(batchSize)
|
|
116
116
|
|
|
@@ -122,7 +122,7 @@ describe('useInfiniteList', () => {
|
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
it('should indicate when there is more data to load', () => {
|
|
125
|
-
const {result} = renderHook(() =>
|
|
125
|
+
const {result} = renderHook(() => useDocuments({batchSize: 3}))
|
|
126
126
|
expect(result.current.hasMore).toBe(true)
|
|
127
127
|
// Load all remaining data
|
|
128
128
|
act(() => {
|
|
@@ -133,7 +133,7 @@ describe('useInfiniteList', () => {
|
|
|
133
133
|
|
|
134
134
|
// New test case for resetting limit when filter changes
|
|
135
135
|
it('should reset limit when filter changes', () => {
|
|
136
|
-
const {result, rerender} = renderHook((props) =>
|
|
136
|
+
const {result, rerender} = renderHook((props) => useDocuments(props), {
|
|
137
137
|
initialProps: {batchSize: 2, filter: ''},
|
|
138
138
|
})
|
|
139
139
|
// Initially, data length equals pageSize (2)
|