@sanity/sdk-react 3.0.0-rc.2 → 3.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.
- package/dist/_exports/dashboard.d.ts +209 -0
- package/dist/_exports/dashboard.d.ts.map +1 -0
- package/dist/_exports/dashboard.js +278 -0
- package/dist/_exports/dashboard.js.map +1 -0
- package/dist/index.d.ts +86 -261
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -532
- package/dist/index.js.map +1 -1
- package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js +177 -0
- package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map +1 -0
- package/package.json +9 -8
- package/src/_exports/dashboard.ts +11 -0
- package/src/_exports/sdk-react.ts +2 -12
- package/src/components/SDKProvider.test.tsx +5 -5
- package/src/components/auth/AuthBoundary.tsx +5 -5
- package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
- package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
- package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
- package/src/context/OrganizationResourcesProvider.tsx +2 -2
- package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
- package/src/hooks/comlink/useWindowConnection.ts +1 -1
- package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +1 -1
- package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
- package/src/hooks/dashboard/useFavorite.ts +34 -0
- package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
- package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
- package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
- package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
- package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
- package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
- package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
- package/src/hooks/dashboard/useWindowTitle.ts +1 -1
- package/src/hooks/datasets/useDatasets.test.tsx +29 -22
- package/src/hooks/datasets/useDatasets.ts +31 -53
- package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
- package/src/hooks/dashboard/useManageFavorite.ts +0 -173
- /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import {type DatasetsResponse} from '@sanity/client'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
getDatasetsState,
|
|
5
|
-
resolveDatasets,
|
|
6
|
-
type StateSource,
|
|
7
|
-
} from '@sanity/sdk'
|
|
2
|
+
import {createSanityInstance, datasets, type StateSource} from '@sanity/sdk'
|
|
3
|
+
import {type FetcherSnapshot} from '@sanity/sdk/_internal'
|
|
8
4
|
import {type ReactNode} from 'react'
|
|
9
5
|
import {type Observable} from 'rxjs'
|
|
10
6
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
@@ -16,32 +12,43 @@ import {useDatasets} from './useDatasets'
|
|
|
16
12
|
|
|
17
13
|
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
18
14
|
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
19
|
-
return {...original,
|
|
15
|
+
return {...original, datasets: {getState: vi.fn(), resolveState: vi.fn()}}
|
|
20
16
|
})
|
|
21
17
|
|
|
22
18
|
const stateSource = (
|
|
23
19
|
current: DatasetsResponse | undefined,
|
|
24
|
-
): StateSource<DatasetsResponse
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
): StateSource<FetcherSnapshot<DatasetsResponse>> => {
|
|
21
|
+
// Cache the snapshot: useSyncExternalStore requires a referentially stable current value.
|
|
22
|
+
const snapshot = current
|
|
23
|
+
? {status: 'success', data: current, error: undefined, isFetching: false, dataUpdatedAt: 1}
|
|
24
|
+
: {
|
|
25
|
+
status: 'pending',
|
|
26
|
+
data: undefined,
|
|
27
|
+
error: undefined,
|
|
28
|
+
isFetching: true,
|
|
29
|
+
dataUpdatedAt: undefined,
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
getCurrent: vi.fn(() => snapshot),
|
|
33
|
+
subscribe: vi.fn(() => () => {}),
|
|
28
34
|
get observable(): Observable<unknown> {
|
|
29
35
|
throw new Error('Not implemented')
|
|
30
36
|
},
|
|
31
|
-
}
|
|
37
|
+
} as unknown as StateSource<FetcherSnapshot<DatasetsResponse>>
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
const sanityInstance = expect.objectContaining({config: expect.any(Object)})
|
|
34
41
|
|
|
35
42
|
describe('useDatasets', () => {
|
|
36
43
|
beforeEach(() => {
|
|
37
44
|
vi.clearAllMocks()
|
|
38
|
-
vi.mocked(
|
|
45
|
+
vi.mocked(datasets.getState).mockReturnValue(stateSource([] as unknown as DatasetsResponse))
|
|
39
46
|
})
|
|
40
47
|
|
|
41
48
|
it('resolves the projectId from the instance config resource', () => {
|
|
42
49
|
// test-utils wraps with ResourceProvider projectId="test" dataset="test".
|
|
43
50
|
renderHook(() => useDatasets())
|
|
44
|
-
expect(
|
|
51
|
+
expect(datasets.getState).toHaveBeenCalledWith(
|
|
45
52
|
sanityInstance,
|
|
46
53
|
expect.objectContaining({projectId: 'test'}),
|
|
47
54
|
)
|
|
@@ -49,7 +56,7 @@ describe('useDatasets', () => {
|
|
|
49
56
|
|
|
50
57
|
it('lets an explicit projectId override the ambient resource', () => {
|
|
51
58
|
renderHook(() => useDatasets({projectId: 'explicit-project'}))
|
|
52
|
-
expect(
|
|
59
|
+
expect(datasets.getState).toHaveBeenCalledWith(
|
|
53
60
|
sanityInstance,
|
|
54
61
|
expect.objectContaining({projectId: 'explicit-project'}),
|
|
55
62
|
)
|
|
@@ -66,7 +73,7 @@ describe('useDatasets', () => {
|
|
|
66
73
|
</ResourceProvider>
|
|
67
74
|
),
|
|
68
75
|
})
|
|
69
|
-
expect(
|
|
76
|
+
expect(datasets.getState).toHaveBeenCalledWith(
|
|
70
77
|
sanityInstance,
|
|
71
78
|
expect.objectContaining({projectId: 'resource-project'}),
|
|
72
79
|
)
|
|
@@ -82,7 +89,7 @@ describe('useDatasets', () => {
|
|
|
82
89
|
})
|
|
83
90
|
// A dataset-less config can't form a DatasetResource; the projectId is carried
|
|
84
91
|
// via ProjectContext and injected so project-scoped reads still resolve it.
|
|
85
|
-
expect(
|
|
92
|
+
expect(datasets.getState).toHaveBeenCalledWith(
|
|
86
93
|
sanityInstance,
|
|
87
94
|
expect.objectContaining({projectId: 'config-project'}),
|
|
88
95
|
)
|
|
@@ -101,16 +108,16 @@ describe('useDatasets', () => {
|
|
|
101
108
|
</SanityInstanceContext.Provider>
|
|
102
109
|
),
|
|
103
110
|
})
|
|
104
|
-
expect(
|
|
111
|
+
expect(datasets.getState).toHaveBeenCalledWith(
|
|
105
112
|
emptyInstance,
|
|
106
113
|
expect.objectContaining({projectId: 'bare-project'}),
|
|
107
114
|
)
|
|
108
115
|
})
|
|
109
116
|
|
|
110
|
-
it('suspends via
|
|
111
|
-
vi.mocked(
|
|
112
|
-
vi.mocked(
|
|
117
|
+
it('suspends via the datasets fetcher until dataset data is available', () => {
|
|
118
|
+
vi.mocked(datasets.getState).mockReturnValue(stateSource(undefined))
|
|
119
|
+
vi.mocked(datasets.resolveState).mockReturnValue(new Promise(() => {}))
|
|
113
120
|
renderHook(() => useDatasets())
|
|
114
|
-
expect(
|
|
121
|
+
expect(datasets.resolveState).toHaveBeenCalled()
|
|
115
122
|
})
|
|
116
123
|
})
|
|
@@ -1,64 +1,42 @@
|
|
|
1
1
|
import {type DatasetsResponse} from '@sanity/client'
|
|
2
|
-
import {
|
|
3
|
-
getDatasetsState,
|
|
4
|
-
type ProjectHandle,
|
|
5
|
-
resolveDatasets,
|
|
6
|
-
type SanityInstance,
|
|
7
|
-
type StateSource,
|
|
8
|
-
} from '@sanity/sdk'
|
|
2
|
+
import {datasets, type ProjectHandle} from '@sanity/sdk'
|
|
9
3
|
|
|
10
|
-
import {
|
|
4
|
+
import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
11
5
|
import {useResolvedProjectId} from '../helpers/useResolvedProjectId'
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
* Returns metadata for each dataset the current user has access to.
|
|
17
|
-
*
|
|
18
|
-
* @category Datasets
|
|
19
|
-
* @param options - Optional project/resource to read datasets for. Defaults to
|
|
20
|
-
* the resource named in `ResourceProvider`/`SDKProvider`.
|
|
21
|
-
* @returns The metadata for your the datasets
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```tsx
|
|
25
|
-
* const datasets = useDatasets()
|
|
26
|
-
*
|
|
27
|
-
* return (
|
|
28
|
-
* <select>
|
|
29
|
-
* {datasets.map((dataset) => (
|
|
30
|
-
* <option key={dataset.name}>{dataset.name}</option>
|
|
31
|
-
* ))}
|
|
32
|
-
* </select>
|
|
33
|
-
* )
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @remarks
|
|
37
|
-
* The `projectId` is resolved in order from:
|
|
38
|
-
* 1. an explicit `projectId` option
|
|
39
|
-
* 2. A legacy ProjectContext (e.g. a `<ResourceProvider projectId="…">` with no dataset), then
|
|
40
|
-
* 3. The active resource (`ResourceProvider`/`SDKProvider`)
|
|
41
|
-
* 4. `instance.config`.
|
|
42
|
-
*/
|
|
43
|
-
(options?: ProjectHandle): DatasetsResponse
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const useDatasetsBase = createStateSourceHook({
|
|
47
|
-
getState: getDatasetsState as (
|
|
48
|
-
instance: SanityInstance,
|
|
49
|
-
projectHandle?: ProjectHandle,
|
|
50
|
-
) => StateSource<DatasetsResponse>,
|
|
51
|
-
shouldSuspend: (instance, projectHandle?: ProjectHandle) =>
|
|
52
|
-
// remove `undefined` since we're suspending when that is the case
|
|
53
|
-
getDatasetsState(instance, projectHandle).getCurrent() === undefined,
|
|
54
|
-
suspender: resolveDatasets,
|
|
55
|
-
})
|
|
7
|
+
const useDatasetsBase = createFetcherHook(datasets)
|
|
56
8
|
|
|
57
9
|
/**
|
|
10
|
+
* Returns metadata for each dataset the current user has access to.
|
|
11
|
+
*
|
|
12
|
+
* @category Datasets
|
|
13
|
+
* @param options - Optional project/resource to read datasets for. Defaults to
|
|
14
|
+
* the resource named in `ResourceProvider`/`SDKProvider`.
|
|
15
|
+
* @returns A {@link FetcherHookResult} whose `data` is the metadata for the
|
|
16
|
+
* datasets.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const {data: datasets} = useDatasets()
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <select>
|
|
24
|
+
* {datasets.map((dataset) => (
|
|
25
|
+
* <option key={dataset.name}>{dataset.name}</option>
|
|
26
|
+
* ))}
|
|
27
|
+
* </select>
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The `projectId` is resolved in order from:
|
|
33
|
+
* 1. an explicit `projectId` option
|
|
34
|
+
* 2. A legacy ProjectContext (e.g. a `<ResourceProvider projectId="…">` with no dataset), then
|
|
35
|
+
* 3. The active resource (`ResourceProvider`/`SDKProvider`)
|
|
36
|
+
* 4. `instance.config`.
|
|
58
37
|
* @public
|
|
59
|
-
* @function
|
|
60
38
|
*/
|
|
61
|
-
export
|
|
39
|
+
export function useDatasets(options?: ProjectHandle): FetcherHookResult<DatasetsResponse> {
|
|
62
40
|
const projectId = useResolvedProjectId(options)
|
|
63
41
|
return useDatasetsBase(projectId ? {...options, projectId} : options)
|
|
64
42
|
}
|
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import {type Message} from '@sanity/comlink'
|
|
2
|
-
import {type FavoriteStatusResponse, getFavoritesState, resolveFavoritesState} from '@sanity/sdk'
|
|
3
|
-
import {act, renderHook} from '@testing-library/react'
|
|
4
|
-
import {BehaviorSubject} from 'rxjs'
|
|
5
|
-
import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
|
|
6
|
-
|
|
7
|
-
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
8
|
-
import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection'
|
|
9
|
-
import {useManageFavorite} from './useManageFavorite'
|
|
10
|
-
|
|
11
|
-
type MockFetch = (type: string, data?: unknown, options?: unknown) => Promise<{success: boolean}>
|
|
12
|
-
type MockSendMessage = (type: string, data?: unknown) => void
|
|
13
|
-
|
|
14
|
-
vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
15
|
-
const actual = await importOriginal()
|
|
16
|
-
return {
|
|
17
|
-
...actual,
|
|
18
|
-
getFavoritesState: vi.fn(),
|
|
19
|
-
resolveFavoritesState: vi.fn(),
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
vi.mock('../comlink/useWindowConnection', () => ({
|
|
24
|
-
useWindowConnection: vi.fn(),
|
|
25
|
-
}))
|
|
26
|
-
|
|
27
|
-
describe('useManageFavorite', () => {
|
|
28
|
-
let favoriteStatusSubject: BehaviorSubject<FavoriteStatusResponse>
|
|
29
|
-
let mockFetch: Mock<MockFetch>
|
|
30
|
-
let mockSendMessage: Mock<MockSendMessage>
|
|
31
|
-
|
|
32
|
-
const mockDocumentHandle = {
|
|
33
|
-
documentId: 'mock-id',
|
|
34
|
-
documentType: 'mock-type',
|
|
35
|
-
resourceType: 'studio' as const,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
favoriteStatusSubject = new BehaviorSubject<FavoriteStatusResponse>({isFavorited: false})
|
|
40
|
-
|
|
41
|
-
// Mock getFavoritesState
|
|
42
|
-
vi.mocked(getFavoritesState).mockImplementation(() => ({
|
|
43
|
-
subscribe: (callback?: () => void) => {
|
|
44
|
-
if (!callback) return () => {}
|
|
45
|
-
|
|
46
|
-
const subscription = favoriteStatusSubject.subscribe(() => callback())
|
|
47
|
-
callback() // Initial call
|
|
48
|
-
return () => subscription.unsubscribe()
|
|
49
|
-
},
|
|
50
|
-
getCurrent: () => favoriteStatusSubject.getValue(),
|
|
51
|
-
observable: favoriteStatusSubject.asObservable(),
|
|
52
|
-
}))
|
|
53
|
-
|
|
54
|
-
// Mock resolveFavoritesState
|
|
55
|
-
vi.mocked(resolveFavoritesState).mockImplementation(async () => {
|
|
56
|
-
const newValue = {isFavorited: !favoriteStatusSubject.getValue().isFavorited}
|
|
57
|
-
favoriteStatusSubject.next(newValue)
|
|
58
|
-
return newValue
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Mock useWindowConnection
|
|
62
|
-
mockFetch = vi.fn<MockFetch>().mockResolvedValue({success: true})
|
|
63
|
-
mockSendMessage = vi.fn<MockSendMessage>()
|
|
64
|
-
vi.mocked(useWindowConnection).mockImplementation(
|
|
65
|
-
() =>
|
|
66
|
-
({
|
|
67
|
-
fetch: (type: string, data?: unknown, options: unknown = {}) =>
|
|
68
|
-
mockFetch(type, data, options),
|
|
69
|
-
sendMessage: mockSendMessage,
|
|
70
|
-
}) as unknown as WindowConnection<Message>,
|
|
71
|
-
)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
afterEach(() => {
|
|
75
|
-
favoriteStatusSubject.complete()
|
|
76
|
-
vi.clearAllMocks()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should initialize with default states', () => {
|
|
80
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
81
|
-
wrapper: ({children}) => (
|
|
82
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
83
|
-
{children}
|
|
84
|
-
</ResourceProvider>
|
|
85
|
-
),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
expect(result.current.isFavorited).toBe(false)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should handle favorite action and update state', async () => {
|
|
92
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
93
|
-
wrapper: ({children}) => (
|
|
94
|
-
<ResourceProvider projectId="test" dataset="test" fallback={null}>
|
|
95
|
-
{children}
|
|
96
|
-
</ResourceProvider>
|
|
97
|
-
),
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
expect(result.current.isFavorited).toBe(false)
|
|
101
|
-
|
|
102
|
-
await act(async () => {
|
|
103
|
-
await result.current.favorite()
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
107
|
-
'dashboard/v1/events/favorite/mutate',
|
|
108
|
-
{
|
|
109
|
-
document: {
|
|
110
|
-
id: 'mock-id',
|
|
111
|
-
type: 'mock-type',
|
|
112
|
-
resource: {
|
|
113
|
-
id: 'test.test',
|
|
114
|
-
type: 'studio',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
eventType: 'added',
|
|
118
|
-
},
|
|
119
|
-
// empty options object (from useWindowConnection)
|
|
120
|
-
{},
|
|
121
|
-
)
|
|
122
|
-
expect(resolveFavoritesState).toHaveBeenCalled()
|
|
123
|
-
expect(result.current.isFavorited).toBe(true)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('should handle unfavorite action and update state', async () => {
|
|
127
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
128
|
-
wrapper: ({children}) => (
|
|
129
|
-
<ResourceProvider projectId="test" dataset="test" fallback={null}>
|
|
130
|
-
{children}
|
|
131
|
-
</ResourceProvider>
|
|
132
|
-
),
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
// Set initial state to favorited
|
|
136
|
-
await act(async () => {
|
|
137
|
-
favoriteStatusSubject.next({isFavorited: true})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
expect(result.current.isFavorited).toBe(true)
|
|
141
|
-
|
|
142
|
-
await act(async () => {
|
|
143
|
-
await result.current.unfavorite()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
147
|
-
'dashboard/v1/events/favorite/mutate',
|
|
148
|
-
{
|
|
149
|
-
document: {
|
|
150
|
-
id: 'mock-id',
|
|
151
|
-
type: 'mock-type',
|
|
152
|
-
resource: {
|
|
153
|
-
id: 'test.test',
|
|
154
|
-
type: 'studio',
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
eventType: 'removed',
|
|
158
|
-
},
|
|
159
|
-
{},
|
|
160
|
-
)
|
|
161
|
-
expect(resolveFavoritesState).toHaveBeenCalled()
|
|
162
|
-
expect(result.current.isFavorited).toBe(false)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('should not update state if favorite action fails', async () => {
|
|
166
|
-
mockFetch.mockResolvedValueOnce({success: false})
|
|
167
|
-
|
|
168
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
169
|
-
wrapper: ({children}) => (
|
|
170
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
171
|
-
{children}
|
|
172
|
-
</ResourceProvider>
|
|
173
|
-
),
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
expect(result.current.isFavorited).toBe(false)
|
|
177
|
-
|
|
178
|
-
await act(async () => {
|
|
179
|
-
await result.current.favorite()
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
expect(resolveFavoritesState).not.toHaveBeenCalled()
|
|
183
|
-
expect(result.current.isFavorited).toBe(false)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('should throw error during favorite/unfavorite actions', async () => {
|
|
187
|
-
const errorMessage = 'Failed to update favorite status'
|
|
188
|
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
189
|
-
|
|
190
|
-
mockFetch.mockImplementation(() => {
|
|
191
|
-
throw new Error(errorMessage)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
195
|
-
wrapper: ({children}) => (
|
|
196
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
197
|
-
{children}
|
|
198
|
-
</ResourceProvider>
|
|
199
|
-
),
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
await act(async () => {
|
|
203
|
-
await expect(result.current.favorite()).rejects.toThrow(errorMessage)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
expect(resolveFavoritesState).not.toHaveBeenCalled()
|
|
207
|
-
expect(result.current.isFavorited).toBe(false)
|
|
208
|
-
|
|
209
|
-
await act(async () => {
|
|
210
|
-
await expect(result.current.unfavorite()).rejects.toThrow(errorMessage)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
expect(resolveFavoritesState).not.toHaveBeenCalled()
|
|
214
|
-
consoleErrorSpy.mockRestore()
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('should throw error when studio resource is missing projectId or dataset', () => {
|
|
218
|
-
const mockDocumentHandleWithoutProjectId = {
|
|
219
|
-
documentId: 'mock-id',
|
|
220
|
-
documentType: 'mock-type',
|
|
221
|
-
resourceType: 'studio' as const,
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
expect(() =>
|
|
225
|
-
renderHook(() => useManageFavorite(mockDocumentHandleWithoutProjectId), {
|
|
226
|
-
wrapper: ({children}) => (
|
|
227
|
-
<ResourceProvider projectId={undefined} dataset={undefined} fallback={null}>
|
|
228
|
-
{children}
|
|
229
|
-
</ResourceProvider>
|
|
230
|
-
),
|
|
231
|
-
}),
|
|
232
|
-
).toThrow('projectId and dataset are required for studio resources')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('should throw error when resourceId is missing for non-studio resources', () => {
|
|
236
|
-
const mockMediaDocumentHandle = {
|
|
237
|
-
documentId: 'mock-id',
|
|
238
|
-
documentType: 'mock-type',
|
|
239
|
-
resourceType: 'media-library' as const,
|
|
240
|
-
resourceId: undefined,
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
expect(() =>
|
|
244
|
-
renderHook(() => useManageFavorite(mockMediaDocumentHandle), {
|
|
245
|
-
wrapper: ({children}) => (
|
|
246
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
247
|
-
{children}
|
|
248
|
-
</ResourceProvider>
|
|
249
|
-
),
|
|
250
|
-
}),
|
|
251
|
-
).toThrow('resourceId is required for media-library and canvas resources')
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('should include schemaName in payload when provided', async () => {
|
|
255
|
-
const mockDocumentHandleWithSchema = {
|
|
256
|
-
...mockDocumentHandle,
|
|
257
|
-
schemaName: 'testSchema',
|
|
258
|
-
}
|
|
259
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema), {
|
|
260
|
-
wrapper: ({children}) => (
|
|
261
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
262
|
-
{children}
|
|
263
|
-
</ResourceProvider>
|
|
264
|
-
),
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
await act(async () => {
|
|
268
|
-
await result.current.favorite()
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
272
|
-
'dashboard/v1/events/favorite/mutate',
|
|
273
|
-
{
|
|
274
|
-
document: {
|
|
275
|
-
id: 'mock-id',
|
|
276
|
-
type: 'mock-type',
|
|
277
|
-
resource: {
|
|
278
|
-
id: 'test-project.test-dataset',
|
|
279
|
-
type: 'studio',
|
|
280
|
-
schemaName: 'testSchema',
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
eventType: 'added',
|
|
284
|
-
},
|
|
285
|
-
{},
|
|
286
|
-
)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('should default isFavorited to false if state is undefined', () => {
|
|
290
|
-
// Mock getFavoritesState to return undefined for getCurrent
|
|
291
|
-
vi.mocked(getFavoritesState).mockImplementation(() => ({
|
|
292
|
-
subscribe: (callback?: () => void) => {
|
|
293
|
-
if (!callback) return () => {}
|
|
294
|
-
callback()
|
|
295
|
-
return () => {}
|
|
296
|
-
},
|
|
297
|
-
getCurrent: () => undefined,
|
|
298
|
-
observable: favoriteStatusSubject.asObservable(),
|
|
299
|
-
}))
|
|
300
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
301
|
-
wrapper: ({children}) => (
|
|
302
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
303
|
-
{children}
|
|
304
|
-
</ResourceProvider>
|
|
305
|
-
),
|
|
306
|
-
})
|
|
307
|
-
expect(result.current.isFavorited).toBe(false)
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
it('should do nothing if fetch is missing', async () => {
|
|
311
|
-
vi.mocked(useWindowConnection).mockReturnValue({
|
|
312
|
-
fetch: undefined,
|
|
313
|
-
sendMessage: mockSendMessage,
|
|
314
|
-
} as unknown as WindowConnection<Message>)
|
|
315
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), {
|
|
316
|
-
wrapper: ({children}) => (
|
|
317
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
318
|
-
{children}
|
|
319
|
-
</ResourceProvider>
|
|
320
|
-
),
|
|
321
|
-
})
|
|
322
|
-
await act(async () => {
|
|
323
|
-
await result.current.favorite()
|
|
324
|
-
await result.current.unfavorite()
|
|
325
|
-
})
|
|
326
|
-
expect(mockFetch).not.toHaveBeenCalled()
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
it('should do nothing if documentId is missing', async () => {
|
|
330
|
-
const handle = {...mockDocumentHandle, documentId: undefined}
|
|
331
|
-
// @ts-expect-error -- no access to ManageFavorite props type
|
|
332
|
-
const {result} = renderHook(() => useManageFavorite(handle), {
|
|
333
|
-
wrapper: ({children}) => (
|
|
334
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
335
|
-
{children}
|
|
336
|
-
</ResourceProvider>
|
|
337
|
-
),
|
|
338
|
-
})
|
|
339
|
-
await act(async () => {
|
|
340
|
-
await result.current.favorite()
|
|
341
|
-
await result.current.unfavorite()
|
|
342
|
-
})
|
|
343
|
-
expect(mockFetch).not.toHaveBeenCalled()
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
it('should do nothing if documentType is missing', async () => {
|
|
347
|
-
const handle = {...mockDocumentHandle, documentType: undefined}
|
|
348
|
-
// @ts-expect-error -- no access to ManageFavorite props type
|
|
349
|
-
const {result} = renderHook(() => useManageFavorite(handle), {
|
|
350
|
-
wrapper: ({children}) => (
|
|
351
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
352
|
-
{children}
|
|
353
|
-
</ResourceProvider>
|
|
354
|
-
),
|
|
355
|
-
})
|
|
356
|
-
await act(async () => {
|
|
357
|
-
await result.current.favorite()
|
|
358
|
-
await result.current.unfavorite()
|
|
359
|
-
})
|
|
360
|
-
expect(mockFetch).not.toHaveBeenCalled()
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
it('should do nothing if resourceType is missing', async () => {
|
|
364
|
-
const handle = {...mockDocumentHandle, resourceType: undefined, resourceId: 'studio'}
|
|
365
|
-
// @ts-expect-error -- no access to ManageFavorite props type
|
|
366
|
-
const {result} = renderHook(() => useManageFavorite(handle), {
|
|
367
|
-
wrapper: ({children}) => (
|
|
368
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
369
|
-
{children}
|
|
370
|
-
</ResourceProvider>
|
|
371
|
-
),
|
|
372
|
-
})
|
|
373
|
-
await act(async () => {
|
|
374
|
-
await result.current.favorite()
|
|
375
|
-
await result.current.unfavorite()
|
|
376
|
-
})
|
|
377
|
-
expect(mockFetch).not.toHaveBeenCalled()
|
|
378
|
-
})
|
|
379
|
-
})
|