@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,18 +1,23 @@
|
|
|
1
1
|
import {type DocumentResource, type PerspectiveHandle} from '@sanity/sdk'
|
|
2
|
-
import {useContext} from 'react'
|
|
2
|
+
import {useContext, useMemo} from 'react'
|
|
3
3
|
|
|
4
4
|
import {ResourceContext} from '../../context/DefaultResourceContext'
|
|
5
5
|
import {PerspectiveContext} from '../../context/PerspectiveContext'
|
|
6
6
|
import {ResourcesContext} from '../../context/ResourcesContext'
|
|
7
|
+
import {SanityInstanceContext} from '../../context/SanityInstanceContext'
|
|
8
|
+
|
|
9
|
+
type NormalizedResourceFields = 'resourceName' | 'projectId' | 'dataset'
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
|
-
* You should generally prefer to use the React-layer handle types (ResourceHandle, DocumentHandle) from '\@sanity/sdk-react' instead.
|
|
10
|
-
* This type is useful for non-handles (like document actions) that we still want to resolve resources for.
|
|
11
12
|
* Adds React hook support (resourceName resolution) to core types.
|
|
12
|
-
*
|
|
13
|
+
* Prefer using the React-layer handle types (ResourceHandle, DocumentHandle)
|
|
14
|
+
* from `@sanity/sdk-react` — this wrapper is kept for cases where overloads
|
|
15
|
+
* don't fit (e.g. non-handle options objects).
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - The core type to extend (must have optional `resource` field)
|
|
18
|
+
* @beta
|
|
13
19
|
*/
|
|
14
|
-
export type WithResourceNameSupport<T> =
|
|
15
|
-
resource?: DocumentResource
|
|
20
|
+
export type WithResourceNameSupport<T extends {resource?: DocumentResource}> = T & {
|
|
16
21
|
/**
|
|
17
22
|
* Optional name of a resource to resolve from context.
|
|
18
23
|
* If provided, will be resolved to a `DocumentResource` via `ResourcesContext`.
|
|
@@ -23,22 +28,23 @@ export type WithResourceNameSupport<T> = Omit<T, 'resource'> & {
|
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
30
|
* Pure function that normalizes options by resolving `resourceName` to a `DocumentResource`
|
|
26
|
-
* using the provided resources map
|
|
27
|
-
*
|
|
28
|
-
* and you cannot call the {@link useNormalizedResourceOptions} hook.
|
|
31
|
+
* using the provided resources map. Use this when options are only available at call time
|
|
32
|
+
* (e.g. inside a callback) and you cannot call the {@link useNormalizedResourceOptions} hook.
|
|
29
33
|
*
|
|
30
34
|
* @typeParam T - The options type (must include optional resource field)
|
|
31
35
|
* @param options - Options that may include `resourceName` and/or `resource`
|
|
32
36
|
* @param resources - Map of resource names to DocumentResource (e.g. from ResourcesContext)
|
|
33
|
-
* @param contextResource - Resource from context (
|
|
34
|
-
* @param contextPerspective - Perspective from context (
|
|
35
|
-
* @returns Normalized options with `resourceName` removed and
|
|
37
|
+
* @param contextResource - Resource from context (from ResourceContext)
|
|
38
|
+
* @param contextPerspective - Perspective from context (from PerspectiveContext)
|
|
39
|
+
* @returns Normalized options with `resourceName` removed and `resource` resolved
|
|
36
40
|
* @internal
|
|
37
41
|
*/
|
|
38
42
|
export function normalizeResourceOptions<
|
|
39
43
|
T extends {
|
|
40
44
|
resource?: DocumentResource
|
|
41
45
|
resourceName?: string
|
|
46
|
+
projectId?: string
|
|
47
|
+
dataset?: string
|
|
42
48
|
perspective?: unknown
|
|
43
49
|
},
|
|
44
50
|
>(
|
|
@@ -46,70 +52,87 @@ export function normalizeResourceOptions<
|
|
|
46
52
|
resources: Record<string, DocumentResource>,
|
|
47
53
|
contextResource?: DocumentResource,
|
|
48
54
|
contextPerspective?: PerspectiveHandle['perspective'],
|
|
49
|
-
): Omit<T,
|
|
50
|
-
const {resourceName, ...rest} = options
|
|
55
|
+
): Omit<T, NormalizedResourceFields> {
|
|
56
|
+
const {resourceName, projectId, dataset, ...rest} = options
|
|
57
|
+
const resource = options.resource
|
|
51
58
|
|
|
52
|
-
if (resourceName &&
|
|
59
|
+
if (resourceName && resource) {
|
|
53
60
|
throw new Error(
|
|
54
|
-
`Resource name ${JSON.stringify(resourceName)} and resource ${JSON.stringify(
|
|
61
|
+
`Resource name ${JSON.stringify(resourceName)} and resource ${JSON.stringify(resource)} cannot be used together.`,
|
|
55
62
|
)
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
let resolvedResource: DocumentResource | undefined
|
|
65
|
+
let resolvedResource: DocumentResource | undefined
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
// Tier (a): explicit resource object or resourceName lookup
|
|
68
|
+
if (resource) {
|
|
69
|
+
resolvedResource = resource
|
|
70
|
+
} else if (resourceName) {
|
|
61
71
|
if (!Object.hasOwn(resources, resourceName)) {
|
|
62
72
|
throw new Error(
|
|
63
|
-
`There's no resource named ${JSON.stringify(resourceName)} in context.
|
|
64
|
-
'Register it via the resources prop on <SanityApp>.',
|
|
73
|
+
`There's no resource named ${JSON.stringify(resourceName)} in context. Please use <ResourceProvider>.`,
|
|
65
74
|
)
|
|
66
75
|
}
|
|
67
76
|
resolvedResource = resources[resourceName]
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
// Tier (b): projectId or dataset in options → synthesize a resource
|
|
80
|
+
if (!resolvedResource && projectId && dataset) {
|
|
81
|
+
resolvedResource = {
|
|
82
|
+
projectId,
|
|
83
|
+
dataset,
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
87
|
+
// Tier (c): fall back to whatever ResourceContext provides
|
|
88
|
+
if (!resolvedResource) {
|
|
89
|
+
resolvedResource = contextResource
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
// Inject perspective from context when not explicitly provided in options
|
|
80
93
|
const resolvedPerspective = Object.hasOwn(options, 'perspective')
|
|
81
94
|
? options.perspective
|
|
82
95
|
: contextPerspective
|
|
83
96
|
|
|
84
97
|
return {
|
|
85
98
|
...rest,
|
|
86
|
-
resource: resolvedResource,
|
|
99
|
+
...(resolvedResource !== undefined && {resource: resolvedResource}),
|
|
87
100
|
...(resolvedPerspective !== undefined && {perspective: resolvedPerspective}),
|
|
88
|
-
}
|
|
101
|
+
}
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* This hook ensures that options passed to core layer functions contain
|
|
96
|
-
* the correct `resource` and `perspective` values, maintaining clean
|
|
97
|
-
* separation between React and core layers.
|
|
105
|
+
* Returns the effective context resource: the `ResourceContext` value if set,
|
|
106
|
+
* otherwise a resource synthesized from the current `SanityInstance` config
|
|
107
|
+
* (tier-d fallback — returns `undefined` for studio-style configs with no project).
|
|
98
108
|
*
|
|
99
|
-
* @
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export function useEffectiveContextResource(): DocumentResource | undefined {
|
|
112
|
+
const contextResource = useContext(ResourceContext)
|
|
113
|
+
const instance = useContext(SanityInstanceContext)
|
|
114
|
+
const {projectId, dataset, resource: configResource} = instance?.config ?? {}
|
|
115
|
+
|
|
116
|
+
return useMemo(() => {
|
|
117
|
+
if (contextResource) return contextResource
|
|
118
|
+
if (configResource) return configResource
|
|
119
|
+
if (projectId && dataset) return {projectId, dataset}
|
|
120
|
+
return undefined
|
|
121
|
+
}, [contextResource, configResource, projectId, dataset])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Normalizes hook options by resolving `resourceName` to a `DocumentResource`.
|
|
102
126
|
*
|
|
103
|
-
* @remarks
|
|
104
127
|
* Resolution priority for resource:
|
|
105
|
-
* 1.
|
|
106
|
-
* 2.
|
|
107
|
-
* 3.
|
|
108
|
-
* 4.
|
|
128
|
+
* 1. Explicit `resource` or `resourceName` in options
|
|
129
|
+
* 2. Bare `projectId`/`dataset` pair in options → synthesized into a resource
|
|
130
|
+
* 3. `ResourceContext` value (set by `ResourceProvider` / `SDKProvider`)
|
|
131
|
+
* 4. Current `SanityInstance` config — falls back to `undefined` for studio configs
|
|
109
132
|
*
|
|
110
133
|
* Resolution priority for perspective:
|
|
111
|
-
* 1.
|
|
112
|
-
* 2.
|
|
134
|
+
* 1. Explicit `perspective` in options
|
|
135
|
+
* 2. `PerspectiveContext` value (set by `ResourceProvider`)
|
|
113
136
|
*
|
|
114
137
|
* @internal
|
|
115
138
|
*/
|
|
@@ -117,11 +140,19 @@ export function useNormalizedResourceOptions<
|
|
|
117
140
|
T extends {
|
|
118
141
|
resource?: DocumentResource
|
|
119
142
|
resourceName?: string
|
|
120
|
-
|
|
143
|
+
projectId?: string
|
|
144
|
+
dataset?: string
|
|
145
|
+
perspective?: PerspectiveHandle['perspective']
|
|
121
146
|
},
|
|
122
|
-
>(
|
|
147
|
+
>(
|
|
148
|
+
options: T,
|
|
149
|
+
): Omit<T, NormalizedResourceFields> & {
|
|
150
|
+
resource?: DocumentResource
|
|
151
|
+
perspective?: PerspectiveHandle['perspective']
|
|
152
|
+
} {
|
|
123
153
|
const resources = useContext(ResourcesContext)
|
|
124
|
-
const
|
|
154
|
+
const effectiveContextResource = useEffectiveContextResource()
|
|
125
155
|
const contextPerspective = useContext(PerspectiveContext)
|
|
126
|
-
|
|
156
|
+
|
|
157
|
+
return normalizeResourceOptions(options, resources, effectiveContextResource, contextPerspective)
|
|
127
158
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {type DocumentResource} from '@sanity/sdk'
|
|
2
|
+
import {renderHook} from '@testing-library/react'
|
|
3
|
+
import {type ReactNode} from 'react'
|
|
4
|
+
import {describe, expect, it} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {ResourceContext} from '../../context/DefaultResourceContext'
|
|
7
|
+
import {ProjectContext} from '../../context/ProjectContext'
|
|
8
|
+
import {useResolvedProjectId} from './useResolvedProjectId'
|
|
9
|
+
|
|
10
|
+
const datasetResource: DocumentResource = {projectId: 'resource-project', dataset: 'production'}
|
|
11
|
+
|
|
12
|
+
describe('useResolvedProjectId', () => {
|
|
13
|
+
it('prefers an explicit projectId on the options', () => {
|
|
14
|
+
const {result} = renderHook(() => useResolvedProjectId({projectId: 'option-project'}), {
|
|
15
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
16
|
+
<ResourceContext.Provider value={datasetResource}>
|
|
17
|
+
<ProjectContext.Provider value="context-project">{children}</ProjectContext.Provider>
|
|
18
|
+
</ResourceContext.Provider>
|
|
19
|
+
),
|
|
20
|
+
})
|
|
21
|
+
expect(result.current).toBe('option-project')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('falls back to the ambient project scope (ProjectContext) over the resource', () => {
|
|
25
|
+
const {result} = renderHook(() => useResolvedProjectId(), {
|
|
26
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
27
|
+
<ResourceContext.Provider value={datasetResource}>
|
|
28
|
+
<ProjectContext.Provider value="context-project">{children}</ProjectContext.Provider>
|
|
29
|
+
</ResourceContext.Provider>
|
|
30
|
+
),
|
|
31
|
+
})
|
|
32
|
+
expect(result.current).toBe('context-project')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('falls back to the resolved resource projectId', () => {
|
|
36
|
+
const {result} = renderHook(() => useResolvedProjectId(), {
|
|
37
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
38
|
+
<ResourceContext.Provider value={datasetResource}>{children}</ResourceContext.Provider>
|
|
39
|
+
),
|
|
40
|
+
})
|
|
41
|
+
expect(result.current).toBe('resource-project')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('ignores a non-dataset resource (e.g. media library)', () => {
|
|
45
|
+
const {result} = renderHook(() => useResolvedProjectId(), {
|
|
46
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
47
|
+
<ResourceContext.Provider value={{mediaLibraryId: 'ml-id'}}>
|
|
48
|
+
{children}
|
|
49
|
+
</ResourceContext.Provider>
|
|
50
|
+
),
|
|
51
|
+
})
|
|
52
|
+
expect(result.current).toBeUndefined()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('returns undefined when nothing resolves', () => {
|
|
56
|
+
const {result} = renderHook(() => useResolvedProjectId())
|
|
57
|
+
expect(result.current).toBeUndefined()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {type DocumentResource, isDatasetResource} from '@sanity/sdk'
|
|
2
|
+
import {useContext} from 'react'
|
|
3
|
+
|
|
4
|
+
import {ProjectContext} from '../../context/ProjectContext'
|
|
5
|
+
import {useNormalizedResourceOptions} from './useNormalizedResourceOptions'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the effective `projectId` for project-scoped hooks (`useProject`,
|
|
9
|
+
* `useDatasets`, `useUsers`).
|
|
10
|
+
*
|
|
11
|
+
* Precedence:
|
|
12
|
+
* 1. an explicit `projectId` on the options
|
|
13
|
+
* 2. the ambient project scope (`ProjectContext`, e.g. a dataset-less
|
|
14
|
+
* `<ResourceProvider projectId="…">`)
|
|
15
|
+
* 3. the resolved resource's projectId (`ResourceProvider`/`SDKProvider`)
|
|
16
|
+
*
|
|
17
|
+
* Returns `undefined` when none apply, letting callers fall back to core's
|
|
18
|
+
* `instance.config.projectId`.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export function useResolvedProjectId(options?: {
|
|
23
|
+
projectId?: string
|
|
24
|
+
dataset?: string
|
|
25
|
+
resource?: DocumentResource
|
|
26
|
+
resourceName?: string
|
|
27
|
+
}): string | undefined {
|
|
28
|
+
const {resource} = useNormalizedResourceOptions(options ?? {})
|
|
29
|
+
const contextProjectId = useContext(ProjectContext)
|
|
30
|
+
return (
|
|
31
|
+
options?.projectId ??
|
|
32
|
+
contextProjectId ??
|
|
33
|
+
(resource && isDatasetResource(resource) ? resource.projectId : undefined)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {type SanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {trackHookMounted} from '@sanity/sdk/_internal'
|
|
3
|
+
import {useRef} from 'react'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tracks the first usage of a named hook per SDK session.
|
|
9
|
+
* If the telemetry manager hasn't initialized yet, the hook
|
|
10
|
+
* name is buffered and flushed when it becomes available.
|
|
11
|
+
*
|
|
12
|
+
* Uses a ref to ensure the tracking call only happens once per
|
|
13
|
+
* component mount, avoiding repeated WeakMap lookups on re-renders.
|
|
14
|
+
*
|
|
15
|
+
* Call at the top of any public hook whose adoption we want to measure.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export function useTrackHookUsage(hookName: string): void {
|
|
20
|
+
const instance = useSanityInstance()
|
|
21
|
+
const tracked = useRef<true | null>(null)
|
|
22
|
+
if (tracked.current === null) {
|
|
23
|
+
tracked.current = true
|
|
24
|
+
trackHookMounted(instance, hookName)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Non-hook variant for tracking hook usage when an instance is already
|
|
30
|
+
* available (avoids an extra `useSanityInstance` call in hooks that
|
|
31
|
+
* already have the instance).
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export function trackHookUsage(instance: SanityInstance, hookName: string): void {
|
|
36
|
+
trackHookMounted(instance, hookName)
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {type Installation, type InstallationActiveConfig} from '@sanity/sdk'
|
|
2
|
+
import {expectTypeOf, test} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
5
|
+
import {useInstallation} from './useInstallation'
|
|
6
|
+
|
|
7
|
+
test('useInstallation — no include: the base installation', () => {
|
|
8
|
+
const result = useInstallation('inst_1')
|
|
9
|
+
expectTypeOf(result).toEqualTypeOf<FetcherHookResult<Installation<never>>>()
|
|
10
|
+
expectTypeOf<
|
|
11
|
+
Extract<keyof typeof result.data, 'activeConfig' | 'access' | 'interfaces'>
|
|
12
|
+
>().toEqualTypeOf<never>()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('useInstallation — activeConfig include adds the config field', () => {
|
|
16
|
+
const result = useInstallation('inst_1', {include: ['activeConfig']})
|
|
17
|
+
expectTypeOf(result.data).toEqualTypeOf<Installation<'activeConfig'>>()
|
|
18
|
+
expectTypeOf(result.data.activeConfig).toEqualTypeOf<InstallationActiveConfig | null>()
|
|
19
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {type Installation, installation, type InstallationInclude} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a single installation by id.
|
|
7
|
+
*
|
|
8
|
+
* The hook suspends until the first fetch succeeds, so `data` is always present.
|
|
9
|
+
* The `include` tokens you pass shape `data`: each requested token adds its
|
|
10
|
+
* field, and omitted ones are absent from the type.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
* @param installationId - The installation id.
|
|
14
|
+
* @param options - Optional `include` list to expand related resources.
|
|
15
|
+
* @returns The result envelope `{data, isFetching, error, refetch}`.
|
|
16
|
+
*/
|
|
17
|
+
export const useInstallation = createFetcherHook(installation) as <
|
|
18
|
+
Include extends InstallationInclude = never,
|
|
19
|
+
>(
|
|
20
|
+
installationId: string,
|
|
21
|
+
options?: {include?: Include[]},
|
|
22
|
+
) => FetcherHookResult<Installation<Include>>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {type Installation, type InstallationAccess, type InstallationsResponse} from '@sanity/sdk'
|
|
2
|
+
import {expectTypeOf, test} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
5
|
+
import {useInstallations} from './useInstallations'
|
|
6
|
+
|
|
7
|
+
test('useInstallations — no include: base items, no gated fields', () => {
|
|
8
|
+
const result = useInstallations({organizationId: 'org_1'})
|
|
9
|
+
expectTypeOf(result).toEqualTypeOf<FetcherHookResult<InstallationsResponse<never>>>()
|
|
10
|
+
type Item = (typeof result.data.data)[number]
|
|
11
|
+
expectTypeOf<
|
|
12
|
+
Extract<keyof Item, 'activeConfig' | 'access' | 'interfaces'>
|
|
13
|
+
>().toEqualTypeOf<never>()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('useInstallations — include shapes each item', () => {
|
|
17
|
+
const result = useInstallations({organizationId: 'org_1', include: ['access']})
|
|
18
|
+
expectTypeOf(result.data.data).toEqualTypeOf<Installation<'access'>[]>()
|
|
19
|
+
type Item = (typeof result.data.data)[number]
|
|
20
|
+
expectTypeOf<Item['access']>().toEqualTypeOf<InstallationAccess[]>()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('useInstallations — requires an organizationId', () => {
|
|
24
|
+
// @ts-expect-error — organizationId is required
|
|
25
|
+
void useInstallations({})
|
|
26
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type InstallationInclude,
|
|
3
|
+
installations,
|
|
4
|
+
type InstallationsOptions,
|
|
5
|
+
type InstallationsResponse,
|
|
6
|
+
} from '@sanity/sdk'
|
|
7
|
+
|
|
8
|
+
import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the installations matching the given options.
|
|
12
|
+
*
|
|
13
|
+
* The hook suspends until the first fetch succeeds, so `data` is always present.
|
|
14
|
+
* The `include` tokens you pass shape `data.data`: each requested token adds its
|
|
15
|
+
* field, and omitted ones are absent from the type.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
* @param options - Filter and include options for the installations list.
|
|
19
|
+
* @returns The result envelope `{data, isFetching, error, refetch}`.
|
|
20
|
+
*/
|
|
21
|
+
export const useInstallations = createFetcherHook(installations) as <
|
|
22
|
+
Include extends InstallationInclude = never,
|
|
23
|
+
>(
|
|
24
|
+
options: InstallationsOptions<Include>,
|
|
25
|
+
) => FetcherHookResult<InstallationsResponse<Include>>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {type Organization, type OrganizationMember} from '@sanity/sdk'
|
|
2
|
+
import {expectTypeOf, test} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {useOrganization} from './useOrganization'
|
|
5
|
+
|
|
6
|
+
test('useOrganization — no flags: members and features both omitted', () => {
|
|
7
|
+
expectTypeOf(useOrganization({organizationId: 'org_1'}).data).toEqualTypeOf<
|
|
8
|
+
Organization<false, false>
|
|
9
|
+
>()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('useOrganization — returns the FetcherHookResult envelope', () => {
|
|
13
|
+
const result = useOrganization({organizationId: 'org_1'})
|
|
14
|
+
expectTypeOf(result.isFetching).toEqualTypeOf<boolean>()
|
|
15
|
+
expectTypeOf(result.error).toEqualTypeOf<unknown>()
|
|
16
|
+
expectTypeOf(result.refetch).toEqualTypeOf<() => Promise<Organization<false, false>>>()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('useOrganization — includeMembers: true adds members to the type', () => {
|
|
20
|
+
expectTypeOf(useOrganization({organizationId: 'org_1', includeMembers: true}).data).toEqualTypeOf<
|
|
21
|
+
Organization<true, false>
|
|
22
|
+
>()
|
|
23
|
+
type Result = ReturnType<typeof useOrganization<true, false>>
|
|
24
|
+
expectTypeOf<Result['data']['members']>().toEqualTypeOf<OrganizationMember[]>()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('useOrganization — includeFeatures: true adds features to the type', () => {
|
|
28
|
+
expectTypeOf(
|
|
29
|
+
useOrganization({organizationId: 'org_1', includeFeatures: true}).data,
|
|
30
|
+
).toEqualTypeOf<Organization<false, true>>()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('useOrganization — both flags true → both arrays present', () => {
|
|
34
|
+
expectTypeOf(
|
|
35
|
+
useOrganization({organizationId: 'org_1', includeMembers: true, includeFeatures: true}).data,
|
|
36
|
+
).toEqualTypeOf<Organization<true, true>>()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('useOrganization — both flags false → bare base shape', () => {
|
|
40
|
+
expectTypeOf(
|
|
41
|
+
useOrganization({organizationId: 'org_1', includeMembers: false, includeFeatures: false}).data,
|
|
42
|
+
).toEqualTypeOf<Organization<false, false>>()
|
|
43
|
+
type Result = ReturnType<typeof useOrganization<false, false>>
|
|
44
|
+
expectTypeOf<Result['data']['id']>().toEqualTypeOf<string>()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('useOrganization — rejects non-boolean flag values', () => {
|
|
48
|
+
// @ts-expect-error — includeMembers must be a boolean
|
|
49
|
+
void useOrganization({organizationId: 'org_1', includeMembers: 'yes'})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('useOrganization — non-literal boolean flag makes members optional', () => {
|
|
53
|
+
const includeMembers = false as boolean
|
|
54
|
+
expectTypeOf(useOrganization({organizationId: 'org_1', includeMembers}).data).toEqualTypeOf<
|
|
55
|
+
Organization<boolean, false>
|
|
56
|
+
>()
|
|
57
|
+
type Result = ReturnType<typeof useOrganization<boolean, false>>
|
|
58
|
+
expectTypeOf<Result['data']['members']>().toEqualTypeOf<OrganizationMember[] | undefined>()
|
|
59
|
+
expectTypeOf<Pick<Result['data'], 'members'>>().toEqualTypeOf<{members?: OrganizationMember[]}>()
|
|
60
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {type Organization, organization, type StateSource} from '@sanity/sdk'
|
|
2
|
+
import {type FetcherSnapshot} from '@sanity/sdk/_internal'
|
|
3
|
+
import {type Observable} from 'rxjs'
|
|
4
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {renderHook} from '../../../test/test-utils'
|
|
7
|
+
import {useOrganization} from './useOrganization'
|
|
8
|
+
|
|
9
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
10
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
11
|
+
return {...original, organization: {getState: vi.fn(), resolveState: vi.fn(), refetch: vi.fn()}}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const stateSource = (
|
|
15
|
+
current: Organization | undefined,
|
|
16
|
+
): StateSource<FetcherSnapshot<Organization>> => {
|
|
17
|
+
const snapshot = current
|
|
18
|
+
? {status: 'success', data: current, error: undefined, isFetching: false, dataUpdatedAt: 1}
|
|
19
|
+
: {
|
|
20
|
+
status: 'pending',
|
|
21
|
+
data: undefined,
|
|
22
|
+
error: undefined,
|
|
23
|
+
isFetching: true,
|
|
24
|
+
dataUpdatedAt: undefined,
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
getCurrent: vi.fn(() => snapshot),
|
|
28
|
+
subscribe: vi.fn(() => () => {}),
|
|
29
|
+
get observable(): Observable<unknown> {
|
|
30
|
+
throw new Error('Not implemented')
|
|
31
|
+
},
|
|
32
|
+
} as unknown as StateSource<FetcherSnapshot<Organization>>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sanityInstance = expect.objectContaining({config: expect.any(Object)})
|
|
36
|
+
|
|
37
|
+
describe('useOrganization', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks()
|
|
40
|
+
vi.mocked(organization.getState).mockReturnValue(
|
|
41
|
+
stateSource({id: 'org_1'} as unknown as Organization),
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('reads the organization fetcher with the passed options', () => {
|
|
46
|
+
renderHook(() => useOrganization({organizationId: 'org_1'}))
|
|
47
|
+
expect(organization.getState).toHaveBeenCalledWith(sanityInstance, {organizationId: 'org_1'})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('returns the fetcher data in the result envelope', () => {
|
|
51
|
+
const {result} = renderHook(() => useOrganization({organizationId: 'org_1'}))
|
|
52
|
+
expect(result.current.data).toEqual({id: 'org_1'})
|
|
53
|
+
expect(result.current.isFetching).toBe(false)
|
|
54
|
+
expect(typeof result.current.refetch).toBe('function')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('suspends via the organization fetcher until data is available', () => {
|
|
58
|
+
vi.mocked(organization.getState).mockReturnValue(stateSource(undefined))
|
|
59
|
+
vi.mocked(organization.resolveState).mockReturnValue(new Promise(() => {}))
|
|
60
|
+
renderHook(() => useOrganization({organizationId: 'org_1'}))
|
|
61
|
+
expect(organization.resolveState).toHaveBeenCalled()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {type Organization, organization, type OrganizationOptions} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns metadata for a given organisation.
|
|
7
|
+
*
|
|
8
|
+
* @category Organizations
|
|
9
|
+
* @param options - Configuration options
|
|
10
|
+
* @returns A {@link FetcherHookResult} whose `data` is the metadata for the
|
|
11
|
+
* organisation. `members` is included only when `includeMembers: true`;
|
|
12
|
+
* `features` is included only when `includeFeatures: true`.
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function OrganizationName({organizationId}: {organizationId: string}) {
|
|
16
|
+
* const {data: organization} = useOrganization({organizationId})
|
|
17
|
+
*
|
|
18
|
+
* return <h1>{organization.name}</h1>
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const {data: organizationWithMembers} = useOrganization({organizationId, includeMembers: true})
|
|
24
|
+
* const {data: organizationWithFeatures} = useOrganization({organizationId, includeFeatures: true})
|
|
25
|
+
* ```
|
|
26
|
+
* @public
|
|
27
|
+
* @function
|
|
28
|
+
*/
|
|
29
|
+
export const useOrganization = createFetcherHook(organization) as <
|
|
30
|
+
IncludeMembers extends boolean = false,
|
|
31
|
+
IncludeFeatures extends boolean = false,
|
|
32
|
+
>(
|
|
33
|
+
options: OrganizationOptions<IncludeMembers, IncludeFeatures>,
|
|
34
|
+
) => FetcherHookResult<Organization<IncludeMembers, IncludeFeatures>>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {type OrganizationMember, type Organizations} from '@sanity/sdk'
|
|
2
|
+
import {expectTypeOf, test} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {useOrganizations} from './useOrganizations'
|
|
5
|
+
|
|
6
|
+
test('useOrganizations — no args: members and features both omitted', () => {
|
|
7
|
+
expectTypeOf(useOrganizations().data).toEqualTypeOf<Organizations<false, false>>()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('useOrganizations — returns the FetcherHookResult envelope', () => {
|
|
11
|
+
const result = useOrganizations()
|
|
12
|
+
expectTypeOf(result.isFetching).toEqualTypeOf<boolean>()
|
|
13
|
+
expectTypeOf(result.error).toEqualTypeOf<unknown>()
|
|
14
|
+
expectTypeOf(result.refetch).toEqualTypeOf<() => Promise<Organizations<false, false>>>()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('useOrganizations — includeMembers: true adds members to the type', () => {
|
|
18
|
+
expectTypeOf(useOrganizations({includeMembers: true}).data).toEqualTypeOf<
|
|
19
|
+
Organizations<true, false>
|
|
20
|
+
>()
|
|
21
|
+
type Result = ReturnType<typeof useOrganizations<true, false>>
|
|
22
|
+
expectTypeOf<Result['data'][number]['members']>().toEqualTypeOf<OrganizationMember[]>()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('useOrganizations — includeFeatures: true adds features to the type', () => {
|
|
26
|
+
expectTypeOf(useOrganizations({includeFeatures: true}).data).toEqualTypeOf<
|
|
27
|
+
Organizations<false, true>
|
|
28
|
+
>()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('useOrganizations — both flags true → both arrays present', () => {
|
|
32
|
+
expectTypeOf(useOrganizations({includeMembers: true, includeFeatures: true}).data).toEqualTypeOf<
|
|
33
|
+
Organizations<true, true>
|
|
34
|
+
>()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('useOrganizations — both flags false → bare base shape', () => {
|
|
38
|
+
expectTypeOf(
|
|
39
|
+
useOrganizations({includeMembers: false, includeFeatures: false}).data,
|
|
40
|
+
).toEqualTypeOf<Organizations<false, false>>()
|
|
41
|
+
type Result = ReturnType<typeof useOrganizations<false, false>>
|
|
42
|
+
expectTypeOf<Result['data'][number]['id']>().toEqualTypeOf<string>()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('useOrganizations — rejects non-boolean flag values', () => {
|
|
46
|
+
// @ts-expect-error — includeMembers must be a boolean
|
|
47
|
+
void useOrganizations({includeMembers: 'yes'})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('useOrganizations — includeImplicitMemberships does not change the data shape', () => {
|
|
51
|
+
expectTypeOf(useOrganizations({includeImplicitMemberships: true}).data).toEqualTypeOf<
|
|
52
|
+
Organizations<false, false>
|
|
53
|
+
>()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('useOrganizations — non-literal boolean flag makes members optional', () => {
|
|
57
|
+
const includeMembers = false as boolean
|
|
58
|
+
expectTypeOf(useOrganizations({includeMembers}).data).toEqualTypeOf<
|
|
59
|
+
Organizations<boolean, false>
|
|
60
|
+
>()
|
|
61
|
+
type Result = ReturnType<typeof useOrganizations<boolean, false>>
|
|
62
|
+
expectTypeOf<Result['data'][number]['members']>().toEqualTypeOf<
|
|
63
|
+
OrganizationMember[] | undefined
|
|
64
|
+
>()
|
|
65
|
+
expectTypeOf<Pick<Result['data'][number], 'members'>>().toEqualTypeOf<{
|
|
66
|
+
members?: OrganizationMember[]
|
|
67
|
+
}>()
|
|
68
|
+
})
|