@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.2
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 +103 -155
- package/dist/index.d.ts +2488 -2019
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4435 -1529
- package/dist/index.js.map +1 -1
- package/package.json +55 -59
- package/src/_exports/index.ts +2 -4
- package/src/_exports/sdk-react.ts +30 -18
- package/src/components/SDKProvider.test.tsx +173 -32
- package/src/components/SDKProvider.tsx +87 -28
- package/src/components/SanityApp.test.tsx +68 -12
- package/src/components/SanityApp.tsx +93 -87
- package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
- package/src/components/auth/AuthBoundary.test.tsx +7 -7
- package/src/components/auth/AuthBoundary.tsx +29 -6
- package/src/components/auth/DashboardAccessRequest.tsx +37 -0
- package/src/components/auth/LoginError.test.tsx +191 -5
- package/src/components/auth/LoginError.tsx +104 -61
- package/src/components/errors/ChunkLoadError.test.tsx +59 -0
- package/src/components/errors/ChunkLoadError.tsx +56 -0
- package/src/components/errors/CorsErrorComponent.tsx +2 -2
- package/src/components/errors/chunkReloadStorage.ts +57 -0
- package/src/config/handles.ts +29 -27
- package/src/constants.ts +5 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
- package/src/context/ComlinkTokenRefresh.tsx +3 -5
- package/src/context/DefaultResourceContext.ts +3 -3
- package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
- package/src/context/OrganizationResourcesProvider.tsx +111 -0
- package/src/context/PerspectiveContext.ts +3 -3
- package/src/context/ProjectContext.ts +15 -0
- package/src/context/ResourceProvider.test.tsx +93 -90
- package/src/context/ResourceProvider.tsx +62 -93
- package/src/context/SDKStudioContext.test.tsx +28 -33
- package/src/context/SanityInstanceProvider.test.tsx +100 -0
- package/src/context/SanityInstanceProvider.tsx +71 -0
- package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
- package/src/context/WorkbenchTokenRefresh.tsx +61 -0
- package/src/context/renderSanityApp.test.tsx +151 -49
- package/src/context/renderSanityApp.tsx +12 -8
- package/src/context/workbenchToken.ts +63 -0
- package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
- package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
- package/src/hooks/access/useCheckPermissions.ts +24 -0
- package/src/hooks/agent/agentActions.test.tsx +2 -2
- package/src/hooks/agent/agentActions.ts +52 -72
- package/src/hooks/agent/useAgentResourceContext.ts +1 -1
- package/src/hooks/applications/useApplication.test-d.ts +30 -0
- package/src/hooks/applications/useApplication.ts +22 -0
- package/src/hooks/applications/useApplications.test-d.ts +31 -0
- package/src/hooks/applications/useApplications.ts +25 -0
- package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
- package/src/hooks/applications/useDeleteApplication.ts +11 -0
- package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
- package/src/hooks/applications/useUpdateApplication.ts +11 -0
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
- package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
- package/src/hooks/client/useClient.test.tsx +1 -4
- package/src/hooks/client/useClient.ts +1 -0
- package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
- package/src/hooks/comlink/useFrameConnection.ts +1 -1
- package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
- package/src/hooks/comlink/useWindowConnection.ts +2 -3
- package/src/hooks/comments/useCommentActions.test.tsx +150 -0
- package/src/hooks/comments/useCommentActions.ts +109 -0
- package/src/hooks/comments/useCommentList.ts +79 -0
- package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
- package/src/hooks/comments/useCommentThreads.ts +73 -0
- package/src/hooks/comments/useComments.test.tsx +242 -0
- package/src/hooks/comments/useComments.ts +59 -0
- package/src/hooks/context/useResource.test.tsx +32 -0
- package/src/hooks/context/useResource.ts +24 -0
- package/src/hooks/context/useSanityInstance.test.tsx +18 -1
- package/src/hooks/context/useSanityInstance.ts +6 -6
- package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
- package/src/hooks/dashboard/useManageFavorite.ts +11 -17
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -2
- package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
- package/src/hooks/dashboard/useWindowTitle.ts +114 -0
- package/src/hooks/datasets/useDatasets.test.tsx +116 -0
- package/src/hooks/datasets/useDatasets.ts +33 -13
- package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
- package/src/hooks/document/useApplyDocumentActions.ts +66 -48
- package/src/hooks/document/useCreateDocument.test.tsx +83 -0
- package/src/hooks/document/useCreateDocument.ts +117 -0
- package/src/hooks/document/useDocument.test.tsx +37 -8
- package/src/hooks/document/useDocument.ts +131 -66
- package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
- package/src/hooks/document/useDocumentEvent.ts +16 -10
- package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
- package/src/hooks/document/useDocumentPermissions.ts +71 -39
- package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
- package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
- package/src/hooks/document/useEditDocument.test.tsx +21 -14
- package/src/hooks/document/useEditDocument.ts +163 -17
- package/src/hooks/documents/useDocuments.test.tsx +64 -44
- package/src/hooks/documents/useDocuments.ts +31 -20
- package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
- package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
- package/src/hooks/helpers/createFetcherHook.ts +69 -0
- package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
- package/src/hooks/helpers/createMutationHook.tsx +93 -0
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
- package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
- package/src/hooks/helpers/useApplyActions.ts +68 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +226 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +79 -48
- package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
- package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
- package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
- package/src/hooks/installations/useInstallation.test-d.ts +19 -0
- package/src/hooks/installations/useInstallation.ts +22 -0
- package/src/hooks/installations/useInstallations.test-d.ts +26 -0
- package/src/hooks/installations/useInstallations.ts +25 -0
- package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
- package/src/hooks/organizations/useOrganization.test.ts +63 -0
- package/src/hooks/organizations/useOrganization.ts +34 -0
- package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
- package/src/hooks/organizations/useOrganizations.test.ts +63 -0
- package/src/hooks/organizations/useOrganizations.ts +39 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +35 -22
- package/src/hooks/presence/usePresence.test.tsx +0 -15
- package/src/hooks/presence/usePresence.ts +22 -20
- package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
- package/src/hooks/presence/usePresenceForDocument.ts +104 -0
- package/src/hooks/presence/useReportPresence.test.tsx +202 -0
- package/src/hooks/presence/useReportPresence.ts +170 -0
- package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
- package/src/hooks/preview/useDocumentPreview.tsx +12 -14
- package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
- package/src/hooks/projection/useDocumentProjection.ts +81 -2
- package/src/hooks/projects/useProject.test-d.ts +56 -0
- package/src/hooks/projects/useProject.test.tsx +120 -0
- package/src/hooks/projects/useProject.ts +42 -43
- package/src/hooks/projects/useProjects.test-d.ts +60 -0
- package/src/hooks/projects/useProjects.test.ts +45 -98
- package/src/hooks/projects/useProjects.ts +17 -32
- package/src/hooks/query/useQuery.test.tsx +6 -5
- package/src/hooks/query/useQuery.ts +94 -17
- package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
- package/src/hooks/releases/useActiveReleases.ts +28 -24
- package/src/hooks/releases/useAllReleases.test.tsx +93 -0
- package/src/hooks/releases/useAllReleases.ts +62 -0
- package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
- package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
- package/src/hooks/releases/usePerspective.test.tsx +18 -10
- package/src/hooks/releases/usePerspective.ts +13 -17
- package/src/hooks/users/useUser.test.tsx +3 -9
- package/src/hooks/users/useUser.ts +4 -8
- package/src/hooks/users/useUsers.test.tsx +103 -7
- package/src/hooks/users/useUsers.ts +38 -5
- package/src/utils/resolveOrgResources.test.ts +111 -0
- package/src/utils/resolveOrgResources.ts +69 -0
- package/src/hooks/context/useDefaultResource.test.tsx +0 -25
- package/src/hooks/context/useDefaultResource.ts +0 -30
- package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
- package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
- package/src/hooks/datasets/useDatasets.test.ts +0 -66
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
- package/src/hooks/projects/useProject.test.ts +0 -79
|
@@ -1,112 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type Project, projects, type StateSource} from '@sanity/sdk'
|
|
2
|
+
import {type FetcherSnapshot} from '@sanity/sdk/_internal'
|
|
3
|
+
import {type Observable} from 'rxjs'
|
|
2
4
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
5
|
|
|
4
|
-
import {
|
|
6
|
+
import {renderHook} from '../../../test/test-utils'
|
|
7
|
+
import {useProjects} from './useProjects'
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
10
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
11
|
+
return {...original, projects: {getState: vi.fn(), resolveState: vi.fn(), refetch: vi.fn()}}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const stateSource = (current: Project[] | undefined): StateSource<FetcherSnapshot<Project[]>> => {
|
|
15
|
+
const snapshot = current
|
|
16
|
+
? {status: 'success', data: current, error: undefined, isFetching: false, dataUpdatedAt: 1}
|
|
17
|
+
: {
|
|
18
|
+
status: 'pending',
|
|
19
|
+
data: undefined,
|
|
20
|
+
error: undefined,
|
|
21
|
+
isFetching: true,
|
|
22
|
+
dataUpdatedAt: undefined,
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
getCurrent: vi.fn(() => snapshot),
|
|
26
|
+
subscribe: vi.fn(() => () => {}),
|
|
27
|
+
get observable(): Observable<unknown> {
|
|
28
|
+
throw new Error('Not implemented')
|
|
29
|
+
},
|
|
30
|
+
} as unknown as StateSource<FetcherSnapshot<Project[]>>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sanityInstance = expect.objectContaining({config: expect.any(Object)})
|
|
16
34
|
|
|
17
35
|
describe('useProjects', () => {
|
|
18
|
-
// Use beforeEach to reset modules and ensure mocks are fresh for each test
|
|
19
36
|
beforeEach(() => {
|
|
20
|
-
vi.
|
|
21
|
-
|
|
22
|
-
vi.mock('@sanity/sdk', () => ({
|
|
23
|
-
getProjectsState: vi.fn(() => ({
|
|
24
|
-
getCurrent: vi.fn(() => undefined),
|
|
25
|
-
})),
|
|
26
|
-
resolveProjects: vi.fn(),
|
|
27
|
-
}))
|
|
28
|
-
vi.mock('../helpers/createStateSourceHook', () => ({
|
|
29
|
-
createStateSourceHook: vi.fn(),
|
|
30
|
-
}))
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should call createStateSourceHook with correct arguments on import', async () => {
|
|
34
|
-
// Dynamically import the hook *after* mocks are set up and modules reset
|
|
35
|
-
await import('./useProjects')
|
|
36
|
-
|
|
37
|
-
// Check if createStateSourceHook was called during the module evaluation (import)
|
|
38
|
-
expect(createStateSourceHook).toHaveBeenCalled()
|
|
39
|
-
expect(createStateSourceHook).toHaveBeenCalledWith(
|
|
40
|
-
expect.objectContaining({
|
|
41
|
-
getState: expect.any(Function),
|
|
42
|
-
shouldSuspend: expect.any(Function),
|
|
43
|
-
suspender: expect.any(Function), // Actual function reference doesn't matter here as it's mocked
|
|
44
|
-
// Note: getConfig is not used in useProjects
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
37
|
+
vi.clearAllMocks()
|
|
38
|
+
vi.mocked(projects.getState).mockReturnValue(stateSource([{id: 'a'}] as unknown as Project[]))
|
|
47
39
|
})
|
|
48
40
|
|
|
49
|
-
it('
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Get the arguments passed to createStateSourceHook
|
|
54
|
-
const mockCreateStateSourceHook = createStateSourceHook as ReturnType<typeof vi.fn>
|
|
55
|
-
expect(mockCreateStateSourceHook.mock.calls.length).toBeGreaterThan(0)
|
|
56
|
-
const createStateSourceHookArgs = mockCreateStateSourceHook.mock.calls[0][0]
|
|
57
|
-
const shouldSuspend = createStateSourceHookArgs.shouldSuspend
|
|
58
|
-
|
|
59
|
-
// Mock instance for the test call
|
|
60
|
-
const mockInstance = {} as SanityInstance // Use specific type
|
|
61
|
-
|
|
62
|
-
// Call the shouldSuspend function with both required parameters
|
|
63
|
-
const result = shouldSuspend(mockInstance, undefined) // Pass undefined for options
|
|
64
|
-
|
|
65
|
-
// Assert that getProjectsState was called with the correct arguments
|
|
66
|
-
const mockGetProjectsState = getProjectsState as ReturnType<typeof vi.fn>
|
|
67
|
-
expect(mockGetProjectsState).toHaveBeenCalledWith(mockInstance, undefined)
|
|
68
|
-
|
|
69
|
-
// Assert that getCurrent was called on the result of getProjectsState
|
|
70
|
-
expect(mockGetProjectsState.mock.results.length).toBeGreaterThan(0)
|
|
71
|
-
const getProjectsStateMockResult = mockGetProjectsState.mock.results[0].value
|
|
72
|
-
expect(getProjectsStateMockResult.getCurrent).toHaveBeenCalled()
|
|
73
|
-
|
|
74
|
-
// Assert the result of shouldSuspend based on the mocked getCurrent value
|
|
75
|
-
expect(result).toBe(true) // Since getCurrent is mocked to return undefined
|
|
41
|
+
it('reads the projects fetcher with the passed options', () => {
|
|
42
|
+
renderHook(() => useProjects({organizationId: 'org123'}))
|
|
43
|
+
expect(projects.getState).toHaveBeenCalledWith(sanityInstance, {organizationId: 'org123'})
|
|
76
44
|
})
|
|
77
45
|
|
|
78
|
-
it('
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(
|
|
83
|
-
|
|
84
|
-
const createStateSourceHookArgs = mockCreateStateSourceHook.mock.calls[0][0]
|
|
85
|
-
const getState = createStateSourceHookArgs.getState
|
|
86
|
-
|
|
87
|
-
// Test that getState can handle the new options parameter
|
|
88
|
-
const mockInstance = {} as SanityInstance
|
|
89
|
-
const mockOptions = {organizationId: 'org123', includeMembers: false}
|
|
90
|
-
|
|
91
|
-
// This should not throw
|
|
92
|
-
expect(() => getState(mockInstance, mockOptions)).not.toThrow()
|
|
46
|
+
it('returns the fetcher data in the result envelope', () => {
|
|
47
|
+
const {result} = renderHook(() => useProjects())
|
|
48
|
+
expect(result.current.data).toEqual([{id: 'a'}])
|
|
49
|
+
expect(result.current.isFetching).toBe(false)
|
|
50
|
+
expect(typeof result.current.refetch).toBe('function')
|
|
93
51
|
})
|
|
94
52
|
|
|
95
|
-
it('
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const shouldSuspend = createStateSourceHookArgs.shouldSuspend
|
|
101
|
-
|
|
102
|
-
const mockInstance = {} as SanityInstance
|
|
103
|
-
|
|
104
|
-
// Test with different options
|
|
105
|
-
expect(() => shouldSuspend(mockInstance, undefined)).not.toThrow()
|
|
106
|
-
expect(() => shouldSuspend(mockInstance, {organizationId: 'org123'})).not.toThrow()
|
|
107
|
-
expect(() => shouldSuspend(mockInstance, {includeMembers: false})).not.toThrow()
|
|
108
|
-
expect(() =>
|
|
109
|
-
shouldSuspend(mockInstance, {organizationId: 'org123', includeMembers: false}),
|
|
110
|
-
).not.toThrow()
|
|
53
|
+
it('suspends via the projects fetcher until data is available', () => {
|
|
54
|
+
vi.mocked(projects.getState).mockReturnValue(stateSource(undefined))
|
|
55
|
+
vi.mocked(projects.resolveState).mockReturnValue(new Promise(() => {}))
|
|
56
|
+
renderHook(() => useProjects())
|
|
57
|
+
expect(projects.resolveState).toHaveBeenCalled()
|
|
111
58
|
})
|
|
112
59
|
})
|
|
@@ -1,33 +1,18 @@
|
|
|
1
|
-
import {type
|
|
2
|
-
import {getProjectsState, resolveProjects, type SanityInstance, type StateSource} from '@sanity/sdk'
|
|
1
|
+
import {type Project, projects, type ProjectsOptions} from '@sanity/sdk'
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @public
|
|
8
|
-
* @category Types
|
|
9
|
-
* @interface
|
|
10
|
-
*/
|
|
11
|
-
export type ProjectWithoutMembers = Omit<SanityProject, 'members'>
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @public
|
|
15
|
-
* @category Types
|
|
16
|
-
*/
|
|
17
|
-
type UseProjects = <TIncludeMembers extends boolean = false>(options?: {
|
|
18
|
-
organizationId?: string
|
|
19
|
-
includeMembers?: TIncludeMembers
|
|
20
|
-
}) => TIncludeMembers extends true ? SanityProject[] : ProjectWithoutMembers[]
|
|
3
|
+
import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
21
4
|
|
|
22
5
|
/**
|
|
23
6
|
* Returns metadata for each project you have access to.
|
|
24
7
|
*
|
|
25
8
|
* @category Projects
|
|
26
9
|
* @param options - Configuration options
|
|
27
|
-
* @returns
|
|
10
|
+
* @returns A {@link FetcherHookResult} whose `data` is an array of project
|
|
11
|
+
* metadata. `members` is included only when `includeMembers: true`; `features`
|
|
12
|
+
* is included unless `includeFeatures: false`.
|
|
28
13
|
* @example
|
|
29
14
|
* ```tsx
|
|
30
|
-
* const projects = useProjects()
|
|
15
|
+
* const {data: projects} = useProjects()
|
|
31
16
|
*
|
|
32
17
|
* return (
|
|
33
18
|
* <select>
|
|
@@ -39,18 +24,18 @@ type UseProjects = <TIncludeMembers extends boolean = false>(options?: {
|
|
|
39
24
|
* ```
|
|
40
25
|
* @example
|
|
41
26
|
* ```tsx
|
|
42
|
-
* const
|
|
43
|
-
* const
|
|
27
|
+
* const {data: projects} = useProjects()
|
|
28
|
+
* const {data: projectsWithFeatures} = useProjects()
|
|
29
|
+
* const {data: projectsWithMembers} = useProjects({includeMembers: true})
|
|
30
|
+
* const {data: projectsWithoutMembers} = useProjects({includeMembers: false})
|
|
31
|
+
* const {data: projectsWithoutFeatures} = useProjects({includeFeatures: false})
|
|
44
32
|
* ```
|
|
45
33
|
* @public
|
|
46
34
|
* @function
|
|
47
35
|
*/
|
|
48
|
-
export const useProjects
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
getProjectsState(instance, options).getCurrent() === undefined,
|
|
55
|
-
suspender: resolveProjects,
|
|
56
|
-
}) as UseProjects
|
|
36
|
+
export const useProjects = createFetcherHook(projects) as <
|
|
37
|
+
IncludeMembers extends boolean = false,
|
|
38
|
+
IncludeFeatures extends boolean = true,
|
|
39
|
+
>(
|
|
40
|
+
options?: ProjectsOptions<IncludeMembers, IncludeFeatures>,
|
|
41
|
+
) => FetcherHookResult<Project<IncludeMembers, IncludeFeatures>[]>
|
|
@@ -33,7 +33,7 @@ describe('useQuery', () => {
|
|
|
33
33
|
} as StateSource<unknown>)
|
|
34
34
|
|
|
35
35
|
function TestComponent() {
|
|
36
|
-
const {data, isPending} = useQuery
|
|
36
|
+
const {data, isPending} = useQuery({query: 'test query'})
|
|
37
37
|
return (
|
|
38
38
|
<div data-testid="output">
|
|
39
39
|
{data} - {isPending ? 'pending' : 'not pending'}
|
|
@@ -42,7 +42,7 @@ describe('useQuery', () => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
render(
|
|
45
|
-
<ResourceProvider
|
|
45
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
46
46
|
<TestComponent />
|
|
47
47
|
</ResourceProvider>,
|
|
48
48
|
)
|
|
@@ -82,13 +82,14 @@ describe('useQuery', () => {
|
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
function TestComponent() {
|
|
85
|
-
const {data} = useQuery
|
|
85
|
+
const {data} = useQuery({query: 'test query'})
|
|
86
86
|
return <div data-testid="output">{data}</div>
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
render(
|
|
90
90
|
<ResourceProvider
|
|
91
|
-
|
|
91
|
+
projectId="p"
|
|
92
|
+
dataset="d"
|
|
92
93
|
fallback={<div data-testid="fallback">Loading...</div>}
|
|
93
94
|
>
|
|
94
95
|
<TestComponent />
|
|
@@ -163,7 +164,7 @@ describe('useQuery', () => {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
render(
|
|
166
|
-
<ResourceProvider
|
|
167
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
167
168
|
<WrapperComponent />
|
|
168
169
|
</ResourceProvider>,
|
|
169
170
|
)
|
|
@@ -1,25 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
parseQueryKey,
|
|
5
|
-
type QueryOptions,
|
|
6
|
-
resolveQuery,
|
|
7
|
-
} from '@sanity/sdk'
|
|
1
|
+
import {getQueryState, type QueryOptions, resolveQuery} from '@sanity/sdk'
|
|
2
|
+
import {getQueryKey, parseQueryKey} from '@sanity/sdk/_internal'
|
|
3
|
+
import {type SanityQueryResult} from 'groq'
|
|
8
4
|
import {useEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition} from 'react'
|
|
9
5
|
|
|
10
|
-
import {type ResourceHandle} from '../../config/handles'
|
|
11
6
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
useNormalizedResourceOptions,
|
|
9
|
+
type WithResourceNameSupport,
|
|
10
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
11
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
12
|
+
/**
|
|
13
|
+
* Hook options for useQuery, supporting both direct resource and resourceName.
|
|
14
|
+
* @beta
|
|
15
|
+
*/
|
|
16
|
+
type UseQueryOptions<
|
|
17
|
+
TQuery extends string = string,
|
|
18
|
+
TDataset extends string = string,
|
|
19
|
+
TProjectId extends string = string,
|
|
20
|
+
> = WithResourceNameSupport<QueryOptions<TQuery, TDataset, TProjectId>>
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
// Overload 1: Inferred Type (using Typegen)
|
|
23
|
+
/**
|
|
24
|
+
* @public
|
|
25
|
+
* Executes a GROQ query, inferring the result type from the query string and options.
|
|
26
|
+
* Leverages Sanity Typegen if configured for enhanced type safety.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
|
|
29
|
+
* @returns An object containing `data` (typed based on the query) and `isPending` (for transitions).
|
|
30
|
+
*
|
|
31
|
+
* @example Basic usage (Inferred Type)
|
|
32
|
+
* ```tsx
|
|
33
|
+
* import {useQuery} from '@sanity/sdk-react'
|
|
34
|
+
* import {defineQuery} from 'groq'
|
|
35
|
+
*
|
|
36
|
+
* const myQuery = defineQuery(`*[_type == "movie"]{_id, title}`)
|
|
37
|
+
*
|
|
38
|
+
* function MovieList() {
|
|
39
|
+
* // Typegen infers the return type for data
|
|
40
|
+
* const {data} = useQuery({ query: myQuery })
|
|
41
|
+
*
|
|
42
|
+
* return (
|
|
43
|
+
* <div>
|
|
44
|
+
* <h2>Movies</h2>
|
|
45
|
+
* <ul>
|
|
46
|
+
* {data.map(movie => <li key={movie._id}>{movie.title}</li>)}
|
|
47
|
+
* </ul>
|
|
48
|
+
* </div>
|
|
49
|
+
* )
|
|
50
|
+
* }
|
|
51
|
+
* // Suspense boundary should wrap <MovieList /> for initial load
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example Using parameters (Inferred Type)
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import {useQuery} from '@sanity/sdk-react'
|
|
57
|
+
* import {defineQuery} from 'groq'
|
|
58
|
+
*
|
|
59
|
+
* const myQuery = defineQuery(`*[_type == "movie" && _id == $id][0]`)
|
|
60
|
+
*
|
|
61
|
+
* function MovieDetails({movieId}: {movieId: string}) {
|
|
62
|
+
* // Typegen infers the return type based on query and params
|
|
63
|
+
* const {data, isPending} = useQuery({
|
|
64
|
+
* query: myQuery,
|
|
65
|
+
* params: { id: movieId }
|
|
66
|
+
* })
|
|
67
|
+
*
|
|
68
|
+
* return (
|
|
69
|
+
* // utilize `isPending` to signal to users that new data is coming in
|
|
70
|
+
* // (e.g. the `movieId` changed and we're loading in the new one)
|
|
71
|
+
* <div style={{ opacity: isPending ? 0.5 : 1 }}>
|
|
72
|
+
* {data ? <h1>{data.title}</h1> : <p>Movie not found</p>}
|
|
73
|
+
* </div>
|
|
74
|
+
* )
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function useQuery<
|
|
79
|
+
TQuery extends string = string,
|
|
80
|
+
TDataset extends string = string,
|
|
81
|
+
TProjectId extends string = string,
|
|
82
|
+
>(
|
|
83
|
+
options: UseQueryOptions<TQuery, TDataset, TProjectId>,
|
|
84
|
+
): {
|
|
85
|
+
/** The query result, typed based on the GROQ query string */
|
|
86
|
+
data: SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>
|
|
87
|
+
/** True if a query transition is in progress */
|
|
88
|
+
isPending: boolean
|
|
89
|
+
}
|
|
16
90
|
|
|
17
|
-
// Overload
|
|
91
|
+
// Overload 2: Explicit Type Provided
|
|
18
92
|
/**
|
|
19
93
|
* @public
|
|
20
94
|
* Executes a GROQ query with an explicitly provided result type `TData`.
|
|
21
95
|
*
|
|
22
|
-
* @param options - Configuration for the query, including `query`, optional `params`, `
|
|
96
|
+
* @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
|
|
23
97
|
* @returns An object containing `data` (cast to `TData`) and `isPending` (indicates whether a query resolution is pending; note that Suspense handles initial loading states). *
|
|
24
98
|
* @example Manually typed query result
|
|
25
99
|
* ```tsx
|
|
@@ -43,7 +117,7 @@ type ReactQueryOptions = Omit<QueryOptions, 'resource' | 'resourceName'> & Resou
|
|
|
43
117
|
* }
|
|
44
118
|
* ```
|
|
45
119
|
*/
|
|
46
|
-
export function useQuery<TData>(options:
|
|
120
|
+
export function useQuery<TData>(options: WithResourceNameSupport<QueryOptions>): {
|
|
47
121
|
/** The query result, cast to the provided type TData */
|
|
48
122
|
data: TData
|
|
49
123
|
/** True if another query is resolving in the background (suspense handles the initial loading state) */
|
|
@@ -63,16 +137,18 @@ export function useQuery<TData>(options: ReactQueryOptions): {
|
|
|
63
137
|
* - Subscribes to changes, providing real-time updates.
|
|
64
138
|
* - Integrates with React Suspense for handling initial loading states.
|
|
65
139
|
* - Uses React Transitions for managing loading states during query/parameter changes (indicated by `isPending`).
|
|
140
|
+
* - Supports type inference based on the GROQ query when using Sanity Typegen.
|
|
66
141
|
* - Allows specifying an explicit return type `TData` for the query result.
|
|
67
142
|
*
|
|
68
143
|
* @category GROQ
|
|
69
144
|
*/
|
|
70
|
-
export function useQuery(options:
|
|
145
|
+
export function useQuery(options: WithResourceNameSupport<QueryOptions>): {
|
|
71
146
|
data: unknown
|
|
72
147
|
isPending: boolean
|
|
73
148
|
} {
|
|
74
149
|
// Implementation returns unknown, overloads define specifics
|
|
75
150
|
const instance = useSanityInstance()
|
|
151
|
+
trackHookUsage(instance, 'useQuery')
|
|
76
152
|
|
|
77
153
|
// Normalize options: resolve resourceName to resource and strip resourceName
|
|
78
154
|
const normalized = useNormalizedResourceOptions(options)
|
|
@@ -81,7 +157,7 @@ export function useQuery(options: ReactQueryOptions): {
|
|
|
81
157
|
const [isPending, startTransition] = useTransition()
|
|
82
158
|
|
|
83
159
|
// Get the unique key for this query and its options (using normalized options)
|
|
84
|
-
const queryKey = getQueryKey(normalized)
|
|
160
|
+
const queryKey = getQueryKey(instance, normalized)
|
|
85
161
|
// Use a deferred state to avoid immediate re-renders when the query changes
|
|
86
162
|
const [deferredQueryKey, setDeferredQueryKey] = useState(queryKey)
|
|
87
163
|
|
|
@@ -123,11 +199,12 @@ export function useQuery(options: ReactQueryOptions): {
|
|
|
123
199
|
const currentSignal = ref.current.signal
|
|
124
200
|
const deferred = parseQueryKey(deferredQueryKey)
|
|
125
201
|
|
|
202
|
+
// eslint-disable-next-line react-hooks/refs -- intentional during suspended render; see comment above.
|
|
126
203
|
throw resolveQuery(instance, {...deferred, signal: currentSignal})
|
|
127
204
|
}
|
|
128
205
|
|
|
129
206
|
// Subscribe to updates and get the current data
|
|
130
207
|
// useSyncExternalStore ensures the component re-renders when the data changes
|
|
131
|
-
const data = useSyncExternalStore(subscribe, getCurrent) as
|
|
208
|
+
const data = useSyncExternalStore(subscribe, getCurrent) as SanityQueryResult
|
|
132
209
|
return useMemo(() => ({data, isPending}), [data, isPending])
|
|
133
210
|
}
|
|
@@ -20,14 +20,14 @@ describe('useActiveReleases', () => {
|
|
|
20
20
|
vi.clearAllMocks()
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
it('
|
|
23
|
+
it('suspends until the releases state source emits, then resolves with the data', async () => {
|
|
24
24
|
const mockSubject = new BehaviorSubject<ReleaseDocument[] | undefined>(undefined)
|
|
25
25
|
const mockStateSource = {
|
|
26
26
|
subscribe: vi.fn((callback) => {
|
|
27
27
|
const subscription = mockSubject.subscribe(callback)
|
|
28
28
|
return () => subscription.unsubscribe()
|
|
29
29
|
}),
|
|
30
|
-
getCurrent: vi.fn(() =>
|
|
30
|
+
getCurrent: vi.fn(() => mockSubject.getValue()),
|
|
31
31
|
observable: mockSubject,
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -43,22 +43,28 @@ describe('useActiveReleases', () => {
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
wrapper: ({children}) => (
|
|
46
|
-
<ResourceProvider
|
|
46
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
47
47
|
{children}
|
|
48
48
|
</ResourceProvider>
|
|
49
49
|
),
|
|
50
50
|
},
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
// Verify that the hook threw a promise (suspended)
|
|
54
53
|
expect(result.current).toBeInstanceOf(Promise)
|
|
55
54
|
expect(mockStateSource.getCurrent).toHaveBeenCalled()
|
|
55
|
+
|
|
56
|
+
const resolved: ReleaseDocument[] = [
|
|
57
|
+
{_id: 'release1', _type: 'release'} as unknown as ReleaseDocument,
|
|
58
|
+
]
|
|
59
|
+
mockSubject.next(resolved)
|
|
60
|
+
|
|
61
|
+
await expect(result.current).resolves.toEqual(resolved)
|
|
56
62
|
})
|
|
57
63
|
|
|
58
64
|
it('should resolve with releases when data is available', () => {
|
|
59
65
|
const mockReleases: ReleaseDocument[] = [
|
|
60
|
-
{_id: 'release1', _type: 'release'} as ReleaseDocument,
|
|
61
|
-
{_id: 'release2', _type: 'release'} as ReleaseDocument,
|
|
66
|
+
{_id: 'release1', _type: 'release'} as unknown as ReleaseDocument,
|
|
67
|
+
{_id: 'release2', _type: 'release'} as unknown as ReleaseDocument,
|
|
62
68
|
]
|
|
63
69
|
|
|
64
70
|
const mockSubject = new BehaviorSubject<ReleaseDocument[]>(mockReleases)
|
|
@@ -75,7 +81,7 @@ describe('useActiveReleases', () => {
|
|
|
75
81
|
|
|
76
82
|
const {result} = renderHook(() => useActiveReleases(), {
|
|
77
83
|
wrapper: ({children}) => (
|
|
78
|
-
<ResourceProvider
|
|
84
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
79
85
|
{children}
|
|
80
86
|
</ResourceProvider>
|
|
81
87
|
),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type DatasetHandle,
|
|
2
3
|
type DocumentResource,
|
|
3
4
|
getActiveReleasesState,
|
|
4
5
|
type ReleaseDocument,
|
|
@@ -7,18 +8,38 @@ import {
|
|
|
7
8
|
} from '@sanity/sdk'
|
|
8
9
|
import {filter, firstValueFrom} from 'rxjs'
|
|
9
10
|
|
|
10
|
-
import {type ResourceHandle} from '../../config/handles'
|
|
11
11
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
useNormalizedResourceOptions,
|
|
14
|
+
type WithResourceNameSupport,
|
|
15
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
16
|
+
|
|
17
|
+
type UseActiveReleasesValue = {
|
|
18
|
+
(options?: {resource?: DocumentResource}): ReleaseDocument[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const useActiveReleasesValue: UseActiveReleasesValue = createStateSourceHook({
|
|
22
|
+
getState: getActiveReleasesState as (
|
|
23
|
+
instance: SanityInstance,
|
|
24
|
+
options?: {resource?: DocumentResource},
|
|
25
|
+
) => StateSource<ReleaseDocument[]>,
|
|
26
|
+
shouldSuspend: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
|
|
27
|
+
getActiveReleasesState(instance, options ?? {}).getCurrent() === undefined,
|
|
28
|
+
suspender: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
|
|
29
|
+
firstValueFrom(
|
|
30
|
+
getActiveReleasesState(instance, options ?? {}).observable.pipe(filter(Boolean)),
|
|
31
|
+
),
|
|
32
|
+
})
|
|
13
33
|
|
|
14
34
|
/**
|
|
15
35
|
* @public
|
|
16
|
-
|
|
36
|
+
* @function
|
|
37
|
+
*
|
|
17
38
|
* Returns the active releases for the current project,
|
|
18
39
|
* represented as a list of release documents.
|
|
19
40
|
*
|
|
20
41
|
* @returns The active releases for the current project.
|
|
21
|
-
* @category
|
|
42
|
+
* @category Releases
|
|
22
43
|
* @example
|
|
23
44
|
* ```tsx
|
|
24
45
|
* import {useActiveReleases} from '@sanity/sdk-react'
|
|
@@ -26,26 +47,9 @@ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOpti
|
|
|
26
47
|
* const activeReleases = useActiveReleases()
|
|
27
48
|
* ```
|
|
28
49
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const useActiveReleasesValue = createStateSourceHook({
|
|
34
|
-
getState: getActiveReleasesState as (
|
|
35
|
-
instance: SanityInstance,
|
|
36
|
-
options: {resource: DocumentResource},
|
|
37
|
-
) => StateSource<ReleaseDocument[]>,
|
|
38
|
-
shouldSuspend: (instance: SanityInstance, options: {resource: DocumentResource}) =>
|
|
39
|
-
getActiveReleasesState(instance, options).getCurrent() === undefined,
|
|
40
|
-
suspender: (instance: SanityInstance, options: {resource: DocumentResource}) =>
|
|
41
|
-
firstValueFrom(getActiveReleasesState(instance, options).observable.pipe(filter(Boolean))),
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @public
|
|
46
|
-
* @function
|
|
47
|
-
*/
|
|
48
|
-
export const useActiveReleases: UseActiveReleases = (options: ResourceHandle | undefined) => {
|
|
50
|
+
export function useActiveReleases(
|
|
51
|
+
options?: WithResourceNameSupport<DatasetHandle> | undefined,
|
|
52
|
+
): ReleaseDocument[] {
|
|
49
53
|
const normalizedOptions = useNormalizedResourceOptions(options ?? {})
|
|
50
54
|
return useActiveReleasesValue(normalizedOptions)
|
|
51
55
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {getAllReleasesState, type ReleaseDocument} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {BehaviorSubject} from 'rxjs'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
7
|
+
import {useAllReleases} from './useAllReleases'
|
|
8
|
+
|
|
9
|
+
vi.mock('@sanity/sdk', async () => {
|
|
10
|
+
const actual = await vi.importActual('@sanity/sdk')
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
getAllReleasesState: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('useAllReleases', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('suspends until the releases state source emits, then resolves with the data', async () => {
|
|
23
|
+
const mockSubject = new BehaviorSubject<ReleaseDocument[] | undefined>(undefined)
|
|
24
|
+
const mockStateSource = {
|
|
25
|
+
subscribe: vi.fn((callback) => {
|
|
26
|
+
const subscription = mockSubject.subscribe(callback)
|
|
27
|
+
return () => subscription.unsubscribe()
|
|
28
|
+
}),
|
|
29
|
+
getCurrent: vi.fn(() => mockSubject.getValue()),
|
|
30
|
+
observable: mockSubject,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
vi.mocked(getAllReleasesState).mockReturnValue(mockStateSource)
|
|
34
|
+
|
|
35
|
+
const {result} = renderHook(
|
|
36
|
+
() => {
|
|
37
|
+
try {
|
|
38
|
+
return useAllReleases()
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return e
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
wrapper: ({children}) => (
|
|
45
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
46
|
+
{children}
|
|
47
|
+
</ResourceProvider>
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
expect(result.current).toBeInstanceOf(Promise)
|
|
53
|
+
expect(mockStateSource.getCurrent).toHaveBeenCalled()
|
|
54
|
+
|
|
55
|
+
const resolved: ReleaseDocument[] = [
|
|
56
|
+
{_id: 'r-active', _type: 'system.release', state: 'active'} as ReleaseDocument,
|
|
57
|
+
]
|
|
58
|
+
mockSubject.next(resolved)
|
|
59
|
+
|
|
60
|
+
await expect(result.current).resolves.toEqual(resolved)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns every release including archived and published once loaded', () => {
|
|
64
|
+
const mockReleases: ReleaseDocument[] = [
|
|
65
|
+
{_id: 'r-active', _type: 'system.release', state: 'active'} as ReleaseDocument,
|
|
66
|
+
{_id: 'r-archived', _type: 'system.release', state: 'archived'} as ReleaseDocument,
|
|
67
|
+
{_id: 'r-published', _type: 'system.release', state: 'published'} as ReleaseDocument,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const mockSubject = new BehaviorSubject<ReleaseDocument[]>(mockReleases)
|
|
71
|
+
const mockStateSource = {
|
|
72
|
+
subscribe: vi.fn((callback) => {
|
|
73
|
+
const subscription = mockSubject.subscribe(callback)
|
|
74
|
+
return () => subscription.unsubscribe()
|
|
75
|
+
}),
|
|
76
|
+
getCurrent: vi.fn(() => mockReleases),
|
|
77
|
+
observable: mockSubject,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
vi.mocked(getAllReleasesState).mockReturnValue(mockStateSource)
|
|
81
|
+
|
|
82
|
+
const {result} = renderHook(() => useAllReleases(), {
|
|
83
|
+
wrapper: ({children}) => (
|
|
84
|
+
<ResourceProvider projectId="p" dataset="d" fallback={<p>Loading...</p>}>
|
|
85
|
+
{children}
|
|
86
|
+
</ResourceProvider>
|
|
87
|
+
),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(result.current).toEqual(mockReleases)
|
|
91
|
+
expect(mockStateSource.getCurrent).toHaveBeenCalled()
|
|
92
|
+
})
|
|
93
|
+
})
|