@sanity/sdk-react 0.0.0-rc.5 → 0.0.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 (44) hide show
  1. package/README.md +5 -57
  2. package/dist/index.d.ts +1000 -438
  3. package/dist/index.js +325 -259
  4. package/dist/index.js.map +1 -1
  5. package/package.json +15 -14
  6. package/src/_exports/sdk-react.ts +4 -1
  7. package/src/components/SDKProvider.tsx +6 -1
  8. package/src/components/SanityApp.test.tsx +29 -47
  9. package/src/components/SanityApp.tsx +12 -11
  10. package/src/components/auth/AuthBoundary.test.tsx +177 -7
  11. package/src/components/auth/AuthBoundary.tsx +32 -2
  12. package/src/components/auth/ConfigurationError.ts +22 -0
  13. package/src/components/auth/LoginError.tsx +9 -3
  14. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +136 -0
  15. package/src/hooks/auth/useVerifyOrgProjects.tsx +48 -0
  16. package/src/hooks/client/useClient.ts +3 -3
  17. package/src/hooks/comlink/useManageFavorite.test.ts +276 -27
  18. package/src/hooks/comlink/useManageFavorite.ts +102 -51
  19. package/src/hooks/comlink/useWindowConnection.ts +3 -2
  20. package/src/hooks/datasets/useDatasets.test.ts +80 -0
  21. package/src/hooks/datasets/useDatasets.ts +2 -1
  22. package/src/hooks/document/useApplyDocumentActions.ts +105 -31
  23. package/src/hooks/document/useDocument.test.ts +41 -4
  24. package/src/hooks/document/useDocument.ts +198 -114
  25. package/src/hooks/document/useDocumentEvent.test.ts +5 -5
  26. package/src/hooks/document/useDocumentEvent.ts +67 -23
  27. package/src/hooks/document/useDocumentPermissions.ts +47 -8
  28. package/src/hooks/document/useDocumentSyncStatus.test.ts +12 -5
  29. package/src/hooks/document/useDocumentSyncStatus.ts +41 -14
  30. package/src/hooks/document/useEditDocument.test.ts +24 -6
  31. package/src/hooks/document/useEditDocument.ts +238 -133
  32. package/src/hooks/documents/useDocuments.test.tsx +1 -1
  33. package/src/hooks/documents/useDocuments.ts +153 -44
  34. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +1 -1
  35. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +120 -47
  36. package/src/hooks/projection/useProjection.ts +134 -46
  37. package/src/hooks/projects/useProject.test.ts +80 -0
  38. package/src/hooks/projects/useProjects.test.ts +77 -0
  39. package/src/hooks/query/useQuery.test.tsx +4 -4
  40. package/src/hooks/query/useQuery.ts +115 -43
  41. package/src/hooks/releases/useActiveReleases.test.tsx +84 -0
  42. package/src/hooks/releases/useActiveReleases.ts +39 -0
  43. package/src/hooks/releases/usePerspective.test.tsx +120 -0
  44. package/src/hooks/releases/usePerspective.ts +50 -0
@@ -0,0 +1,136 @@
1
+ import {observeOrganizationVerificationState, type OrgVerificationResult} from '@sanity/sdk'
2
+ import {act, renderHook, waitFor} from '@testing-library/react'
3
+ import {Subject} from 'rxjs'
4
+ import {describe, expect, it, vi} from 'vitest'
5
+
6
+ import {useSanityInstance} from '../context/useSanityInstance'
7
+ import {useVerifyOrgProjects} from './useVerifyOrgProjects'
8
+
9
+ // Mock dependencies
10
+ vi.mock('@sanity/sdk', async (importOriginal) => {
11
+ const original = await importOriginal<typeof import('@sanity/sdk')>()
12
+ return {
13
+ ...original,
14
+ observeOrganizationVerificationState: vi.fn(),
15
+ }
16
+ })
17
+ vi.mock('../context/useSanityInstance')
18
+
19
+ describe('useVerifyOrgProjects', () => {
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ const mockInstance = {config: {}} as any // Dummy instance
22
+ const mockObserve = vi.mocked(observeOrganizationVerificationState)
23
+ const mockUseInstance = vi.mocked(useSanityInstance)
24
+ const testProjectIds = ['proj-1']
25
+
26
+ beforeEach(() => {
27
+ vi.clearAllMocks()
28
+ mockUseInstance.mockReturnValue(mockInstance)
29
+ })
30
+
31
+ it('should return null and not observe state if disabled', () => {
32
+ const {result} = renderHook(() => useVerifyOrgProjects(true, testProjectIds))
33
+
34
+ expect(result.current).toBeNull()
35
+ expect(mockObserve).not.toHaveBeenCalled()
36
+ })
37
+
38
+ it('should return null and not observe state if projectIds is missing or empty', () => {
39
+ const {result: resultUndefined} = renderHook(() => useVerifyOrgProjects(false, undefined))
40
+ expect(resultUndefined.current).toBeNull()
41
+ expect(mockObserve).not.toHaveBeenCalled()
42
+
43
+ const {result: resultEmpty} = renderHook(() => useVerifyOrgProjects(false, []))
44
+ expect(resultEmpty.current).toBeNull()
45
+ expect(mockObserve).not.toHaveBeenCalled()
46
+ })
47
+
48
+ it('should return null initially when not disabled and projectIds provided', () => {
49
+ const subject = new Subject<OrgVerificationResult>()
50
+ mockObserve.mockReturnValue(subject.asObservable())
51
+
52
+ const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds))
53
+
54
+ expect(result.current).toBeNull()
55
+ expect(mockObserve).toHaveBeenCalledWith(mockInstance, testProjectIds)
56
+ })
57
+
58
+ it('should return null if observable emits { error: null }', async () => {
59
+ const subject = new Subject<OrgVerificationResult>()
60
+ mockObserve.mockReturnValue(subject.asObservable())
61
+
62
+ const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds))
63
+
64
+ act(() => {
65
+ subject.next({error: null})
66
+ })
67
+
68
+ await waitFor(() => {
69
+ expect(result.current).toBeNull()
70
+ })
71
+ })
72
+
73
+ it('should return error string if observable emits { error: string }', async () => {
74
+ const subject = new Subject<OrgVerificationResult>()
75
+ const errorMessage = 'Org mismatch'
76
+ mockObserve.mockReturnValue(subject.asObservable())
77
+
78
+ const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds))
79
+
80
+ act(() => {
81
+ subject.next({error: errorMessage})
82
+ })
83
+
84
+ await waitFor(() => {
85
+ expect(result.current).toBe(errorMessage)
86
+ })
87
+ })
88
+
89
+ it('should unsubscribe on unmount', () => {
90
+ const subject = new Subject<OrgVerificationResult>()
91
+ const unsubscribeSpy = vi.spyOn(subject, 'unsubscribe')
92
+ mockObserve.mockReturnValue(subject)
93
+
94
+ const {unmount} = renderHook(() => useVerifyOrgProjects(false, testProjectIds))
95
+
96
+ expect(unsubscribeSpy).not.toHaveBeenCalled()
97
+ unmount()
98
+ // Note: RxJS handles the inner subscription cleanup when the source (Subject) completes or errors,
99
+ // but testing library unmount should trigger the useEffect cleanup which calls unsubscribe.
100
+ // However, the spy might be on the Subject itself, not the final Subscription object.
101
+ // Let's adjust to spy on the returned subscription directly if possible, or accept this limitation.
102
+ // For now, we assume the useEffect cleanup calls unsubscribe correctly.
103
+ // We can validate subscription logic more deeply if needed.
104
+ // For this test, let's check if the observable reference still has observers.
105
+ expect(subject.observed).toBe(false) // Check if observers are gone after unmount
106
+ })
107
+
108
+ it('should clear the error if disabled becomes true', async () => {
109
+ const subject = new Subject<OrgVerificationResult>()
110
+ const errorMessage = 'Org mismatch'
111
+ mockObserve.mockReturnValue(subject.asObservable())
112
+
113
+ const {result, rerender} = renderHook(
114
+ ({disabled, pIds}) => useVerifyOrgProjects(disabled, pIds),
115
+ {
116
+ initialProps: {disabled: false, pIds: testProjectIds},
117
+ },
118
+ )
119
+
120
+ // Set initial error
121
+ act(() => {
122
+ subject.next({error: errorMessage})
123
+ })
124
+ await waitFor(() => {
125
+ expect(result.current).toBe(errorMessage)
126
+ })
127
+
128
+ // Disable the hook
129
+ rerender({disabled: true, pIds: testProjectIds})
130
+
131
+ // Error should be cleared
132
+ await waitFor(() => {
133
+ expect(result.current).toBeNull()
134
+ })
135
+ })
136
+ })
@@ -0,0 +1,48 @@
1
+ import {observeOrganizationVerificationState, type OrgVerificationResult} from '@sanity/sdk'
2
+ import {useEffect, useState} from 'react'
3
+
4
+ import {useSanityInstance} from '../context/useSanityInstance'
5
+
6
+ /**
7
+ * Hook that verifies the current projects belongs to the organization ID specified in the dashboard context.
8
+ *
9
+ * @public
10
+ * @param disabled - When true, disables verification and skips project verification API calls
11
+ * @returns Error message if the project doesn't match the organization ID, or null if all match or verification isn't needed
12
+ * @category Projects
13
+ * @example
14
+ * ```tsx
15
+ * function OrgVerifier() {
16
+ * const error = useVerifyOrgProjects()
17
+ *
18
+ * if (error) {
19
+ * return <div className="error">{error}</div>
20
+ * }
21
+ *
22
+ * return <div>Organization projects verified!</div>
23
+ * }
24
+ * ```
25
+ */
26
+ export function useVerifyOrgProjects(disabled = false, projectIds?: string[]): string | null {
27
+ const instance = useSanityInstance()
28
+ const [error, setError] = useState<string | null>(null)
29
+
30
+ useEffect(() => {
31
+ if (disabled || !projectIds || projectIds.length === 0) {
32
+ if (error !== null) setError(null)
33
+ return
34
+ }
35
+
36
+ const verificationObservable$ = observeOrganizationVerificationState(instance, projectIds)
37
+
38
+ const subscription = verificationObservable$.subscribe((result: OrgVerificationResult) => {
39
+ setError(result.error)
40
+ })
41
+
42
+ return () => {
43
+ subscription.unsubscribe()
44
+ }
45
+ }, [instance, disabled, error, projectIds])
46
+
47
+ return error
48
+ }
@@ -5,11 +5,11 @@ import {createStateSourceHook} from '../helpers/createStateSourceHook'
5
5
 
6
6
  /**
7
7
  * A React hook that provides a client that subscribes to changes in your application,
8
- * such as user authentication changes.
9
8
  *
10
9
  * @remarks
11
- * The hook uses `useSyncExternalStore` to safely subscribe to changes
12
- * and ensure consistency between server and client rendering.
10
+ * This hook is intended for advanced use cases and special API calls that the React SDK
11
+ * does not yet provide hooks for. We welcome you to get in touch with us to let us know
12
+ * your use cases for this!
13
13
  *
14
14
  * @category Platform
15
15
  * @returns A Sanity client
@@ -1,8 +1,16 @@
1
1
  import {type Message, type Node, type Status} from '@sanity/comlink'
2
- import {getOrCreateNode} from '@sanity/sdk'
2
+ import {
3
+ type FavoriteStatusResponse,
4
+ getFavoritesState,
5
+ getOrCreateNode,
6
+ resolveFavoritesState,
7
+ type SanityInstance,
8
+ } from '@sanity/sdk'
9
+ import {BehaviorSubject} from 'rxjs'
3
10
  import {beforeEach, describe, expect, it, vi} from 'vitest'
4
11
 
5
12
  import {act, renderHook} from '../../../test/test-utils'
13
+ import {useSanityInstance} from '../context/useSanityInstance'
6
14
  import {useManageFavorite} from './useManageFavorite'
7
15
 
8
16
  vi.mock(import('@sanity/sdk'), async (importOriginal) => {
@@ -11,12 +19,17 @@ vi.mock(import('@sanity/sdk'), async (importOriginal) => {
11
19
  ...actual,
12
20
  getOrCreateNode: vi.fn(),
13
21
  releaseNode: vi.fn(),
22
+ getFavoritesState: vi.fn(),
23
+ resolveFavoritesState: vi.fn(),
14
24
  }
15
25
  })
16
26
 
27
+ vi.mock('../context/useSanityInstance')
28
+
17
29
  describe('useManageFavorite', () => {
18
30
  let node: Node<Message, Message>
19
31
  let statusCallback: ((status: Status) => void) | null = null
32
+ let favoriteStatusSubject: BehaviorSubject<FavoriteStatusResponse>
20
33
 
21
34
  const mockDocumentHandle = {
22
35
  documentId: 'mock-id',
@@ -27,7 +40,7 @@ describe('useManageFavorite', () => {
27
40
  function createMockNode() {
28
41
  return {
29
42
  on: vi.fn(() => () => {}),
30
- post: vi.fn(),
43
+ fetch: vi.fn().mockImplementation(() => Promise.resolve({success: true})),
31
44
  stop: vi.fn(),
32
45
  onStatus: vi.fn((callback) => {
33
46
  statusCallback = callback
@@ -38,8 +51,42 @@ describe('useManageFavorite', () => {
38
51
 
39
52
  beforeEach(() => {
40
53
  statusCallback = null
54
+ favoriteStatusSubject = new BehaviorSubject<FavoriteStatusResponse>({isFavorited: false})
41
55
  node = createMockNode()
42
56
  vi.mocked(getOrCreateNode).mockReturnValue(node)
57
+
58
+ // Mock getFavoritesState
59
+ vi.mocked(getFavoritesState).mockImplementation(() => ({
60
+ subscribe: (callback?: () => void) => {
61
+ if (!callback) return () => {}
62
+
63
+ const subscription = favoriteStatusSubject.subscribe(() => callback())
64
+ callback() // Initial call
65
+ return () => subscription.unsubscribe()
66
+ },
67
+ getCurrent: () => favoriteStatusSubject.getValue(),
68
+ observable: favoriteStatusSubject.asObservable(),
69
+ }))
70
+
71
+ // Mock resolveFavoritesState
72
+ vi.mocked(resolveFavoritesState).mockImplementation(async () => {
73
+ const newValue = {isFavorited: !favoriteStatusSubject.getValue().isFavorited}
74
+ favoriteStatusSubject.next(newValue)
75
+ return newValue
76
+ })
77
+
78
+ // Default mock for useSanityInstance
79
+ vi.mocked(useSanityInstance).mockReturnValue({
80
+ config: {
81
+ projectId: 'test',
82
+ dataset: 'test',
83
+ },
84
+ } as unknown as SanityInstance)
85
+ })
86
+
87
+ afterEach(() => {
88
+ favoriteStatusSubject.complete()
89
+ vi.clearAllMocks()
43
90
  })
44
91
 
45
92
  it('should initialize with default states', () => {
@@ -49,60 +96,118 @@ describe('useManageFavorite', () => {
49
96
  expect(result.current.isConnected).toBe(false)
50
97
  })
51
98
 
52
- it('should handle favorite action', () => {
99
+ it('should handle favorite action and update state', async () => {
53
100
  const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
54
101
 
102
+ expect(result.current.isFavorited).toBe(false)
103
+
104
+ // Simulate connection first
55
105
  act(() => {
56
- result.current.favorite()
106
+ statusCallback?.('connected')
107
+ })
108
+
109
+ await act(async () => {
110
+ await result.current.favorite()
57
111
  })
58
112
 
59
- expect(node.post).toHaveBeenCalledWith('dashboard/v1/events/favorite/mutate', {
60
- document: {
61
- id: 'mock-id',
62
- type: 'mock-type',
63
- resource: {
64
- id: 'test.test',
65
- type: 'studio',
113
+ expect(node.fetch).toHaveBeenCalledWith(
114
+ 'dashboard/v1/events/favorite/mutate',
115
+ {
116
+ document: {
117
+ id: 'mock-id',
118
+ type: 'mock-type',
119
+ resource: {
120
+ id: 'test.test',
121
+ type: 'studio',
122
+ },
66
123
  },
124
+ eventType: 'added',
67
125
  },
68
- eventType: 'added',
69
- })
126
+ // empty options object (from useWindowConnection)
127
+ {},
128
+ )
129
+ expect(resolveFavoritesState).toHaveBeenCalled()
70
130
  expect(result.current.isFavorited).toBe(true)
71
131
  })
72
132
 
73
- it('should handle unfavorite action', () => {
133
+ it('should handle unfavorite action and update state', async () => {
74
134
  const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
75
135
 
136
+ // Set initial state to favorited
137
+ await act(async () => {
138
+ favoriteStatusSubject.next({isFavorited: true})
139
+ })
140
+
141
+ expect(result.current.isFavorited).toBe(true)
142
+
143
+ // Simulate connection first
76
144
  act(() => {
77
- result.current.unfavorite()
145
+ statusCallback?.('connected')
146
+ })
147
+
148
+ await act(async () => {
149
+ await result.current.unfavorite()
78
150
  })
79
151
 
80
- expect(node.post).toHaveBeenCalledWith('dashboard/v1/events/favorite/mutate', {
81
- document: {
82
- id: 'mock-id',
83
- type: 'mock-type',
84
- resource: {
85
- id: 'test.test',
86
- type: 'studio',
152
+ expect(node.fetch).toHaveBeenCalledWith(
153
+ 'dashboard/v1/events/favorite/mutate',
154
+ {
155
+ document: {
156
+ id: 'mock-id',
157
+ type: 'mock-type',
158
+ resource: {
159
+ id: 'test.test',
160
+ type: 'studio',
161
+ },
87
162
  },
163
+ eventType: 'removed',
88
164
  },
89
- eventType: 'removed',
165
+ {},
166
+ )
167
+ expect(resolveFavoritesState).toHaveBeenCalled()
168
+ expect(result.current.isFavorited).toBe(false)
169
+ })
170
+
171
+ it('should not update state if favorite action fails', async () => {
172
+ vi.mocked(node.fetch).mockImplementationOnce(() => Promise.resolve({success: false}))
173
+
174
+ const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
175
+
176
+ expect(result.current.isFavorited).toBe(false)
177
+
178
+ await act(async () => {
179
+ await result.current.favorite()
90
180
  })
181
+
182
+ expect(resolveFavoritesState).not.toHaveBeenCalled()
91
183
  expect(result.current.isFavorited).toBe(false)
92
184
  })
93
185
 
94
- it('should throw error during favorite/unfavorite actions', () => {
186
+ it('should throw error during favorite/unfavorite actions', async () => {
95
187
  const errorMessage = 'Failed to update favorite status'
96
188
 
97
- vi.mocked(node.post).mockImplementationOnce(() => {
189
+ vi.mocked(node.fetch).mockImplementation(() => {
98
190
  throw new Error(errorMessage)
99
191
  })
100
192
 
101
193
  const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
102
194
 
103
- act(() => {
104
- expect(() => result.current.favorite()).toThrow(errorMessage)
195
+ await act(async () => {
196
+ statusCallback?.('connected')
105
197
  })
198
+
199
+ await act(async () => {
200
+ await expect(result.current.favorite()).rejects.toThrow(errorMessage)
201
+ })
202
+
203
+ expect(resolveFavoritesState).not.toHaveBeenCalled()
204
+ expect(result.current.isFavorited).toBe(false)
205
+
206
+ await act(async () => {
207
+ await expect(result.current.unfavorite()).rejects.toThrow(errorMessage)
208
+ })
209
+
210
+ expect(resolveFavoritesState).not.toHaveBeenCalled()
106
211
  })
107
212
 
108
213
  it('should update connection status', () => {
@@ -116,4 +221,148 @@ describe('useManageFavorite', () => {
116
221
 
117
222
  expect(result.current.isConnected).toBe(true)
118
223
  })
224
+
225
+ it('should throw error when studio resource is missing projectId or dataset', () => {
226
+ // Mock the Sanity instance to not have projectId or dataset
227
+ vi.mocked(useSanityInstance).mockReturnValue({
228
+ config: {
229
+ projectId: undefined,
230
+ dataset: undefined,
231
+ },
232
+ } as unknown as SanityInstance)
233
+
234
+ const mockDocumentHandleWithoutProjectId = {
235
+ documentId: 'mock-id',
236
+ documentType: 'mock-type',
237
+ resourceType: 'studio' as const,
238
+ }
239
+
240
+ expect(() => renderHook(() => useManageFavorite(mockDocumentHandleWithoutProjectId))).toThrow(
241
+ 'projectId and dataset are required for studio resources',
242
+ )
243
+ })
244
+
245
+ it('should throw error when resourceId is missing for non-studio resources', () => {
246
+ const mockMediaDocumentHandle = {
247
+ documentId: 'mock-id',
248
+ documentType: 'mock-type',
249
+ resourceType: 'media-library' as const,
250
+ resourceId: undefined,
251
+ }
252
+
253
+ expect(() => renderHook(() => useManageFavorite(mockMediaDocumentHandle))).toThrow(
254
+ 'resourceId is required for media-library and canvas resources',
255
+ )
256
+ })
257
+
258
+ it('should handle favorites service timeout gracefully', async () => {
259
+ // Mock both state functions for timeout scenario
260
+ vi.mocked(getFavoritesState).mockImplementationOnce(() => ({
261
+ subscribe: () => () => {},
262
+ getCurrent: () => undefined, // This will trigger the resolveFavoritesState call
263
+ observable: favoriteStatusSubject.asObservable(),
264
+ }))
265
+
266
+ vi.mocked(resolveFavoritesState).mockImplementationOnce(() => {
267
+ throw new Error('Favorites service connection timeout')
268
+ })
269
+
270
+ const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
271
+
272
+ // Should return fallback state instead of suspending
273
+ expect(result.current).toEqual({
274
+ favorite: expect.any(Function),
275
+ unfavorite: expect.any(Function),
276
+ isFavorited: false,
277
+ isConnected: false,
278
+ })
279
+
280
+ // Favorite and unfavorite actions should be a no-op
281
+ await act(async () => {
282
+ await result.current.favorite()
283
+ })
284
+
285
+ expect(node.fetch).not.toHaveBeenCalled()
286
+
287
+ await act(async () => {
288
+ await result.current.unfavorite()
289
+ })
290
+
291
+ expect(node.fetch).not.toHaveBeenCalled()
292
+ })
293
+
294
+ it('should still throw non-timeout errors for suspension', async () => {
295
+ vi.mocked(getFavoritesState).mockImplementation(() => ({
296
+ subscribe: () => () => {},
297
+ getCurrent: () => undefined, // This will trigger the resolveFavoritesState call
298
+ observable: favoriteStatusSubject.asObservable(),
299
+ }))
300
+
301
+ // Mock resolveFavoritesState to throw
302
+ const error = new Error('Some other error')
303
+ vi.mocked(resolveFavoritesState).mockImplementation(() => {
304
+ throw error
305
+ })
306
+
307
+ expect(() => {
308
+ renderHook(() => useManageFavorite(mockDocumentHandle))
309
+ }).toThrow(error)
310
+ })
311
+
312
+ it('should not call fetch if connection is not established', async () => {
313
+ const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
314
+
315
+ // Ensure connection is not established
316
+ expect(result.current.isConnected).toBe(false)
317
+
318
+ // Try to favorite
319
+ await act(async () => {
320
+ await result.current.favorite()
321
+ })
322
+
323
+ // Fetch should not have been called due to the new status check
324
+ expect(node.fetch).not.toHaveBeenCalled()
325
+
326
+ // Try to unfavorite
327
+ await act(async () => {
328
+ await result.current.unfavorite()
329
+ })
330
+
331
+ // Fetch should still not have been called
332
+ expect(node.fetch).not.toHaveBeenCalled()
333
+ })
334
+
335
+ it('should include schemaName in payload when provided', async () => {
336
+ const mockDocumentHandleWithSchema = {
337
+ ...mockDocumentHandle,
338
+ schemaName: 'testSchema',
339
+ }
340
+ const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema))
341
+
342
+ // Simulate connection first
343
+ act(() => {
344
+ statusCallback?.('connected')
345
+ })
346
+
347
+ await act(async () => {
348
+ await result.current.favorite()
349
+ })
350
+
351
+ expect(node.fetch).toHaveBeenCalledWith(
352
+ 'dashboard/v1/events/favorite/mutate',
353
+ {
354
+ document: {
355
+ id: 'mock-id',
356
+ type: 'mock-type',
357
+ resource: {
358
+ id: 'test.test',
359
+ type: 'studio',
360
+ schemaName: 'testSchema', // <-- Expect schemaName here
361
+ },
362
+ },
363
+ eventType: 'added',
364
+ },
365
+ {},
366
+ )
367
+ })
119
368
  })