@sanity/sdk-react 0.0.0-alpha.3 → 0.0.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +6 -100
  2. package/dist/index.d.ts +2390 -2
  3. package/dist/index.js +1119 -2
  4. package/dist/index.js.map +1 -1
  5. package/package.json +35 -49
  6. package/src/_exports/index.ts +2 -10
  7. package/src/_exports/sdk-react.ts +73 -0
  8. package/src/components/SDKProvider.test.tsx +103 -0
  9. package/src/components/SDKProvider.tsx +52 -0
  10. package/src/components/SanityApp.test.tsx +244 -0
  11. package/src/components/SanityApp.tsx +106 -0
  12. package/src/components/auth/AuthBoundary.test.tsx +204 -29
  13. package/src/components/auth/AuthBoundary.tsx +96 -19
  14. package/src/components/auth/ConfigurationError.ts +22 -0
  15. package/src/components/auth/LoginCallback.test.tsx +22 -24
  16. package/src/components/auth/LoginCallback.tsx +6 -16
  17. package/src/components/auth/LoginError.test.tsx +11 -18
  18. package/src/components/auth/LoginError.tsx +43 -25
  19. package/src/components/utils.ts +22 -0
  20. package/src/context/ResourceProvider.test.tsx +157 -0
  21. package/src/context/ResourceProvider.tsx +111 -0
  22. package/src/context/SanityInstanceContext.ts +4 -0
  23. package/src/hooks/_synchronous-groq-js.mjs +4 -0
  24. package/src/hooks/auth/useAuthState.tsx +4 -5
  25. package/src/hooks/auth/useAuthToken.tsx +1 -1
  26. package/src/hooks/auth/useCurrentUser.tsx +28 -4
  27. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +42 -0
  28. package/src/hooks/auth/useDashboardOrganizationId.tsx +30 -0
  29. package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
  30. package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +7 -6
  31. package/src/hooks/auth/useLogOut.test.tsx +2 -2
  32. package/src/hooks/auth/useLogOut.tsx +1 -1
  33. package/src/hooks/auth/useLoginUrl.tsx +14 -0
  34. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +136 -0
  35. package/src/hooks/auth/useVerifyOrgProjects.tsx +48 -0
  36. package/src/hooks/client/useClient.ts +13 -33
  37. package/src/hooks/comlink/useFrameConnection.test.tsx +167 -0
  38. package/src/hooks/comlink/useFrameConnection.ts +107 -0
  39. package/src/hooks/comlink/useManageFavorite.test.ts +368 -0
  40. package/src/hooks/comlink/useManageFavorite.ts +210 -0
  41. package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +85 -0
  42. package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +115 -0
  43. package/src/hooks/comlink/useWindowConnection.test.ts +135 -0
  44. package/src/hooks/comlink/useWindowConnection.ts +123 -0
  45. package/src/hooks/context/useSanityInstance.test.tsx +157 -15
  46. package/src/hooks/context/useSanityInstance.ts +68 -11
  47. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +276 -0
  48. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +139 -0
  49. package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.test.tsx +291 -0
  50. package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts +101 -0
  51. package/src/hooks/datasets/useDatasets.test.ts +80 -0
  52. package/src/hooks/datasets/useDatasets.ts +52 -0
  53. package/src/hooks/document/useApplyDocumentActions.test.ts +20 -0
  54. package/src/hooks/document/useApplyDocumentActions.ts +124 -0
  55. package/src/hooks/document/useDocument.test.ts +118 -0
  56. package/src/hooks/document/useDocument.ts +212 -0
  57. package/src/hooks/document/useDocumentEvent.test.ts +62 -0
  58. package/src/hooks/document/useDocumentEvent.ts +94 -0
  59. package/src/hooks/document/useDocumentPermissions.test.ts +204 -0
  60. package/src/hooks/document/useDocumentPermissions.ts +131 -0
  61. package/src/hooks/document/useDocumentSyncStatus.test.ts +23 -0
  62. package/src/hooks/document/useDocumentSyncStatus.ts +61 -0
  63. package/src/hooks/document/useEditDocument.test.ts +196 -0
  64. package/src/hooks/document/useEditDocument.ts +314 -0
  65. package/src/hooks/documents/useDocuments.test.tsx +179 -0
  66. package/src/hooks/documents/useDocuments.ts +300 -0
  67. package/src/hooks/helpers/createCallbackHook.test.tsx +2 -2
  68. package/src/hooks/helpers/createCallbackHook.tsx +1 -1
  69. package/src/hooks/helpers/createStateSourceHook.test.tsx +67 -1
  70. package/src/hooks/helpers/createStateSourceHook.tsx +27 -11
  71. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +284 -0
  72. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +353 -0
  73. package/src/hooks/preview/usePreview.test.tsx +85 -17
  74. package/src/hooks/preview/usePreview.tsx +81 -22
  75. package/src/hooks/projection/useProjection.test.tsx +283 -0
  76. package/src/hooks/projection/useProjection.ts +232 -0
  77. package/src/hooks/projects/useProject.test.ts +80 -0
  78. package/src/hooks/projects/useProject.ts +51 -0
  79. package/src/hooks/projects/useProjects.test.ts +77 -0
  80. package/src/hooks/projects/useProjects.ts +45 -0
  81. package/src/hooks/query/useQuery.test.tsx +188 -0
  82. package/src/hooks/query/useQuery.ts +193 -0
  83. package/src/hooks/releases/useActiveReleases.test.tsx +84 -0
  84. package/src/hooks/releases/useActiveReleases.ts +39 -0
  85. package/src/hooks/releases/usePerspective.test.tsx +120 -0
  86. package/src/hooks/releases/usePerspective.ts +49 -0
  87. package/src/hooks/users/useUsers.test.tsx +330 -0
  88. package/src/hooks/users/useUsers.ts +120 -0
  89. package/src/utils/getEnv.ts +21 -0
  90. package/src/version.ts +8 -0
  91. package/src/vite-env.d.ts +10 -0
  92. package/dist/_chunks-es/useLogOut.js +0 -44
  93. package/dist/_chunks-es/useLogOut.js.map +0 -1
  94. package/dist/assets/bundle-CcAyERuZ.css +0 -11
  95. package/dist/components.d.ts +0 -259
  96. package/dist/components.js +0 -301
  97. package/dist/components.js.map +0 -1
  98. package/dist/hooks.d.ts +0 -186
  99. package/dist/hooks.js +0 -81
  100. package/dist/hooks.js.map +0 -1
  101. package/src/_exports/components.ts +0 -13
  102. package/src/_exports/hooks.ts +0 -9
  103. package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +0 -113
  104. package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +0 -42
  105. package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +0 -21
  106. package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +0 -105
  107. package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +0 -42
  108. package/src/components/DocumentListLayout/DocumentListLayout.tsx +0 -12
  109. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +0 -49
  110. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +0 -39
  111. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +0 -30
  112. package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +0 -171
  113. package/src/components/Login/LoginLinks.test.tsx +0 -100
  114. package/src/components/Login/LoginLinks.tsx +0 -73
  115. package/src/components/auth/Login.test.tsx +0 -41
  116. package/src/components/auth/Login.tsx +0 -45
  117. package/src/components/auth/LoginFooter.test.tsx +0 -29
  118. package/src/components/auth/LoginFooter.tsx +0 -65
  119. package/src/components/auth/LoginLayout.test.tsx +0 -33
  120. package/src/components/auth/LoginLayout.tsx +0 -81
  121. package/src/components/context/SanityProvider.test.tsx +0 -25
  122. package/src/components/context/SanityProvider.tsx +0 -42
  123. package/src/css/css.config.js +0 -220
  124. package/src/css/paramour.css +0 -2347
  125. package/src/css/styles.css +0 -11
  126. package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
  127. package/src/hooks/auth/useLoginUrls.test.tsx +0 -68
  128. package/src/hooks/auth/useLoginUrls.tsx +0 -51
  129. package/src/hooks/client/useClient.test.tsx +0 -130
  130. package/src/hooks/documentCollection/useDocuments.test.ts +0 -130
  131. package/src/hooks/documentCollection/useDocuments.ts +0 -87
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/sdk-react",
3
- "version": "0.0.0-alpha.3",
3
+ "version": "0.0.0-alpha.30",
4
4
  "private": false,
5
5
  "description": "Sanity SDK React toolkit for Content OS",
6
6
  "keywords": [
@@ -30,31 +30,11 @@
30
30
  "import": "./dist/index.js",
31
31
  "default": "./dist/index.js"
32
32
  },
33
- "./components": {
34
- "source": "./src/_exports/components.ts",
35
- "import": "./dist/components.js",
36
- "default": "./dist/components.js"
37
- },
38
- "./hooks": {
39
- "source": "./src/_exports/hooks.ts",
40
- "import": "./dist/hooks.js",
41
- "default": "./dist/hooks.js"
42
- },
43
33
  "./package.json": "./package.json"
44
34
  },
45
35
  "main": "./dist/index.js",
46
36
  "module": "./dist/index.js",
47
37
  "types": "./dist/index.d.ts",
48
- "typesVersions": {
49
- "*": {
50
- "components": [
51
- "./dist/components.d.ts"
52
- ],
53
- "hooks": [
54
- "./dist/hooks.d.ts"
55
- ]
56
- }
57
- },
58
38
  "files": [
59
39
  "dist",
60
40
  "src"
@@ -62,37 +42,43 @@
62
42
  "browserslist": "extends @sanity/browserslist-config",
63
43
  "prettier": "@sanity/prettier-config",
64
44
  "dependencies": {
65
- "@paramour/css": "1.0.0-rc.2",
66
- "@sanity/icons": "^3.5.2",
67
- "@sanity/logos": "^2.1.13",
68
- "@sanity/ui": "^2.8.19",
69
- "inter-ui": "^4.1.0",
70
- "react-error-boundary": "^4.1.2",
71
- "rollup-plugin-import-css": "^3.5.7",
72
- "rxjs": "^7.8.1",
73
- "@sanity/sdk": "0.0.0-alpha.3"
45
+ "@sanity/client": "^7.0.0",
46
+ "@sanity/message-protocol": "^0.8.0",
47
+ "@sanity/types": "^3.83.0",
48
+ "@types/lodash-es": "^4.17.12",
49
+ "groq": "3.86.2-experimental.0",
50
+ "lodash-es": "^4.17.21",
51
+ "react-compiler-runtime": "19.0.0-beta-ebf51a3-20250411",
52
+ "react-error-boundary": "^5.0.0",
53
+ "rxjs": "^7.8.2",
54
+ "@sanity/sdk": "0.0.0-alpha.30"
74
55
  },
75
56
  "devDependencies": {
76
- "@sanity/client": "^6.24.1",
77
- "@sanity/pkg-utils": "^6.12.2",
57
+ "@sanity/browserslist-config": "^1.0.5",
58
+ "@sanity/comlink": "^3.0.2",
59
+ "@sanity/pkg-utils": "^7.2.2",
78
60
  "@sanity/prettier-config": "^1.0.3",
79
- "@storybook/react": "^8.4.7",
80
61
  "@testing-library/jest-dom": "^6.6.3",
81
- "@testing-library/react": "^16.1.0",
82
- "@types/react": "^19.0.3",
83
- "@types/react-dom": "^19.0.2",
84
- "@vitejs/plugin-react": "^4.3.4",
85
- "@vitest/coverage-v8": "2.1.8",
86
- "eslint": "^9.17.0",
62
+ "@testing-library/react": "^16.3.0",
63
+ "@types/react": "^19.1.2",
64
+ "@types/react-dom": "^19.1.3",
65
+ "@vitejs/plugin-react": "^4.4.1",
66
+ "@vitest/coverage-v8": "3.1.2",
67
+ "babel-plugin-react-compiler": "19.1.0-rc.1",
68
+ "eslint": "^9.22.0",
87
69
  "jsdom": "^25.0.1",
88
- "prettier": "^3.4.2",
89
- "react": "^19.0.0",
90
- "react-dom": "^19.0.0",
91
- "typescript": "^5.7.2",
92
- "vite": "^5.4.11",
93
- "vitest": "^2.1.8",
70
+ "prettier": "^3.5.3",
71
+ "react": "^19.1.0",
72
+ "react-dom": "^19.1.0",
73
+ "rollup-plugin-visualizer": "^5.14.0",
74
+ "typescript": "^5.8.3",
75
+ "vite": "^6.3.4",
76
+ "vitest": "^3.1.2",
94
77
  "@repo/config-eslint": "0.0.0",
95
- "@repo/package.config": "0.0.1"
78
+ "@repo/config-test": "0.0.1",
79
+ "@repo/package.bundle": "3.82.0",
80
+ "@repo/package.config": "0.0.1",
81
+ "@repo/tsconfig": "0.0.1"
96
82
  },
97
83
  "peerDependencies": {
98
84
  "react": "^18.0.0 || ^19.0.0",
@@ -102,16 +88,16 @@
102
88
  "node": ">=20.0.0"
103
89
  },
104
90
  "publishConfig": {
105
- "access": "restricted"
91
+ "access": "public"
106
92
  },
107
93
  "scripts": {
108
94
  "build": "pkg build --strict --clean --check",
95
+ "build:bundle": "vite build --configLoader runner --config package.bundle.ts",
109
96
  "clean": "rimraf dist",
110
97
  "dev": "pkg watch",
111
- "docs": "typedoc --out docs --tsconfig ../../tsconfig.tsdoc.json",
98
+ "docs": "typedoc --json docs/typedoc.json --tsconfig ./tsconfig.dist.json",
112
99
  "format": "prettier --write --cache --ignore-unknown .",
113
100
  "lint": "eslint .",
114
- "paramour": "npx paramour --config=./src/css/css.config.js --output=./src/css/paramour.css",
115
101
  "test": "vitest run",
116
102
  "test:coverage": "vitest run --coverage",
117
103
  "test:watch": "vitest",
@@ -1,10 +1,2 @@
1
- /**
2
- * An example function that will be removed.
3
- * @public
4
- */
5
- export function main(): void {
6
- //
7
- }
8
-
9
- // export * from './components'
10
- // export * from './hooks'
1
+ export * from './sdk-react.ts'
2
+ export * from '@sanity/sdk'
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @module exports
3
+ */
4
+ export {AuthBoundary, type AuthBoundaryProps} from '../components/auth/AuthBoundary'
5
+ export {SanityApp, type SanityAppProps} from '../components/SanityApp'
6
+ export {SDKProvider, type SDKProviderProps} from '../components/SDKProvider'
7
+ export {ResourceProvider, type ResourceProviderProps} from '../context/ResourceProvider'
8
+ export {useAuthState} from '../hooks/auth/useAuthState'
9
+ export {useAuthToken} from '../hooks/auth/useAuthToken'
10
+ export {useCurrentUser} from '../hooks/auth/useCurrentUser'
11
+ export {useDashboardOrganizationId} from '../hooks/auth/useDashboardOrganizationId'
12
+ export {useHandleAuthCallback} from '../hooks/auth/useHandleAuthCallback'
13
+ export {useLoginUrl} from '../hooks/auth/useLoginUrl'
14
+ export {useLogOut} from '../hooks/auth/useLogOut'
15
+ export {useVerifyOrgProjects} from '../hooks/auth/useVerifyOrgProjects'
16
+ export {useClient} from '../hooks/client/useClient'
17
+ export {
18
+ type FrameConnection,
19
+ type FrameMessageHandler as MessageHandler,
20
+ useFrameConnection,
21
+ type UseFrameConnectionOptions,
22
+ } from '../hooks/comlink/useFrameConnection'
23
+ export {useManageFavorite} from '../hooks/comlink/useManageFavorite'
24
+ export {useRecordDocumentHistoryEvent} from '../hooks/comlink/useRecordDocumentHistoryEvent'
25
+ export {
26
+ useWindowConnection,
27
+ type UseWindowConnectionOptions,
28
+ type WindowConnection,
29
+ type WindowMessageHandler,
30
+ } from '../hooks/comlink/useWindowConnection'
31
+ export {useSanityInstance} from '../hooks/context/useSanityInstance'
32
+ export {
33
+ type NavigateToStudioResult,
34
+ useNavigateToStudioDocument,
35
+ } from '../hooks/dashboard/useNavigateToStudioDocument'
36
+ export {useStudioWorkspacesByProjectIdDataset} from '../hooks/dashboard/useStudioWorkspacesByProjectIdDataset'
37
+ export {useDatasets} from '../hooks/datasets/useDatasets'
38
+ export {useApplyDocumentActions} from '../hooks/document/useApplyDocumentActions'
39
+ export {useDocument} from '../hooks/document/useDocument'
40
+ export {useDocumentEvent} from '../hooks/document/useDocumentEvent'
41
+ export {useDocumentPermissions} from '../hooks/document/useDocumentPermissions'
42
+ export {useDocumentSyncStatus} from '../hooks/document/useDocumentSyncStatus'
43
+ export {useEditDocument} from '../hooks/document/useEditDocument'
44
+ export {
45
+ type DocumentsOptions,
46
+ type DocumentsResponse,
47
+ useDocuments,
48
+ } from '../hooks/documents/useDocuments'
49
+ export {
50
+ type PaginatedDocumentsOptions,
51
+ type PaginatedDocumentsResponse,
52
+ usePaginatedDocuments,
53
+ } from '../hooks/paginatedDocuments/usePaginatedDocuments'
54
+ export {
55
+ usePreview,
56
+ type UsePreviewOptions,
57
+ type UsePreviewResults,
58
+ } from '../hooks/preview/usePreview'
59
+ export {
60
+ useProjection,
61
+ type UseProjectionOptions,
62
+ type UseProjectionResults,
63
+ } from '../hooks/projection/useProjection'
64
+ export {useProject} from '../hooks/projects/useProject'
65
+ export {type ProjectWithoutMembers, useProjects} from '../hooks/projects/useProjects'
66
+ export {useQuery} from '../hooks/query/useQuery'
67
+ export {useActiveReleases} from '../hooks/releases/useActiveReleases'
68
+ export {usePerspective} from '../hooks/releases/usePerspective'
69
+ export {type UsersResult, useUsers} from '../hooks/users/useUsers'
70
+ export {REACT_SDK_VERSION} from '../version'
71
+ export {type DatasetsResponse, type SanityProjectMember} from '@sanity/client'
72
+ export type {Status as ComlinkStatus} from '@sanity/comlink'
73
+ export {type SanityDocument, type SortOrderingItem} from '@sanity/types'
@@ -0,0 +1,103 @@
1
+ import {render} from '@testing-library/react'
2
+ import React from 'react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
+
5
+ import {SDKProvider} from './SDKProvider'
6
+
7
+ // Mock ResourceProvider to test nesting behavior
8
+ vi.mock('../context/ResourceProvider', () => ({
9
+ ResourceProvider: ({
10
+ children,
11
+ ...props
12
+ }: {
13
+ children: React.ReactNode
14
+ projectId?: string
15
+ dataset?: string
16
+ }) => {
17
+ return (
18
+ <div
19
+ data-testid="resource-provider"
20
+ data-config={JSON.stringify({
21
+ projectId: props.projectId,
22
+ dataset: props.dataset,
23
+ })}
24
+ >
25
+ {children}
26
+ </div>
27
+ )
28
+ },
29
+ }))
30
+
31
+ // Mock AuthBoundary
32
+ vi.mock('./auth/AuthBoundary', () => ({
33
+ AuthBoundary: ({children}: {children: React.ReactNode}) => {
34
+ return <div data-testid="auth-boundary">{children}</div>
35
+ },
36
+ }))
37
+
38
+ describe('SDKProvider', () => {
39
+ it('renders single ResourceProvider with AuthBoundary for a single config', () => {
40
+ const config = {
41
+ projectId: 'test-project',
42
+ dataset: 'production',
43
+ }
44
+
45
+ const {getAllByTestId, getByTestId} = render(
46
+ <SDKProvider config={[config]} fallback={<div>Loading...</div>}>
47
+ <div>Child Content</div>
48
+ </SDKProvider>,
49
+ )
50
+
51
+ // Should create a single ResourceProvider
52
+ const providers = getAllByTestId('resource-provider')
53
+ expect(providers.length).toBe(1)
54
+
55
+ // Should create an AuthBoundary inside
56
+ expect(getByTestId('auth-boundary')).toBeInTheDocument()
57
+
58
+ // Verify provider has the correct config
59
+ expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual({
60
+ projectId: 'test-project',
61
+ dataset: 'production',
62
+ })
63
+ })
64
+
65
+ it('renders nested ResourceProviders with AuthBoundary for multiple configs', () => {
66
+ const configs = [
67
+ {
68
+ projectId: 'project-1',
69
+ dataset: 'production',
70
+ },
71
+ {
72
+ projectId: 'project-2',
73
+ dataset: 'staging',
74
+ },
75
+ ]
76
+
77
+ const {getAllByTestId, getByTestId} = render(
78
+ <SDKProvider config={configs} fallback={<div>Loading...</div>}>
79
+ <div>Child Content</div>
80
+ </SDKProvider>,
81
+ )
82
+
83
+ // Should create two nested ResourceProviders
84
+ const providers = getAllByTestId('resource-provider')
85
+ expect(providers.length).toBe(2)
86
+
87
+ // Should create an AuthBoundary inside the innermost provider
88
+ expect(getByTestId('auth-boundary')).toBeInTheDocument()
89
+
90
+ // Verify each provider has the correct config - order is based on how SDKProvider creates nestings
91
+ // The first provider contains config[1]
92
+ expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual({
93
+ projectId: 'project-2',
94
+ dataset: 'staging',
95
+ })
96
+
97
+ // The second provider contains config[0]
98
+ expect(JSON.parse(providers[1].getAttribute('data-config') || '{}')).toEqual({
99
+ projectId: 'project-1',
100
+ dataset: 'production',
101
+ })
102
+ })
103
+ })
@@ -0,0 +1,52 @@
1
+ import {type SanityConfig} from '@sanity/sdk'
2
+ import {type ReactElement, type ReactNode} from 'react'
3
+
4
+ import {ResourceProvider} from '../context/ResourceProvider'
5
+ import {AuthBoundary, type AuthBoundaryProps} from './auth/AuthBoundary'
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ export interface SDKProviderProps extends AuthBoundaryProps {
11
+ children: ReactNode
12
+ config: SanityConfig | SanityConfig[]
13
+ fallback: ReactNode
14
+ }
15
+
16
+ /**
17
+ * @internal
18
+ *
19
+ * Top-level context provider that provides access to the Sanity SDK.
20
+ * Creates a hierarchy of ResourceProviders, each providing a SanityInstance that can be
21
+ * accessed by hooks. The first configuration in the array becomes the default instance.
22
+ */
23
+ export function SDKProvider({
24
+ children,
25
+ config,
26
+ fallback,
27
+ ...props
28
+ }: SDKProviderProps): ReactElement {
29
+ // reverse because we want the first config to be the default, but the
30
+ // ResourceProvider nesting makes the last one the default
31
+ const configs = (Array.isArray(config) ? config : [config]).slice().reverse()
32
+ const projectIds = configs.map((c) => c.projectId).filter((id): id is string => !!id)
33
+
34
+ // Create a nested structure of ResourceProviders for each config
35
+ const createNestedProviders = (index: number): ReactElement => {
36
+ if (index >= configs.length) {
37
+ return (
38
+ <AuthBoundary {...props} projectIds={projectIds}>
39
+ {children}
40
+ </AuthBoundary>
41
+ )
42
+ }
43
+
44
+ return (
45
+ <ResourceProvider {...configs[index]} fallback={fallback}>
46
+ {createNestedProviders(index + 1)}
47
+ </ResourceProvider>
48
+ )
49
+ }
50
+
51
+ return createNestedProviders(0)
52
+ }
@@ -0,0 +1,244 @@
1
+ import {AuthStateType, type SanityConfig} from '@sanity/sdk'
2
+ import {render, screen} from '@testing-library/react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
+
5
+ import {SanityApp} from './SanityApp'
6
+ import {type SDKProviderProps} from './SDKProvider'
7
+
8
+ // Hoist the mock function definition
9
+ // Rely on vi.fn type inference
10
+ const mockSDKProviderComponent = vi.hoisted(() =>
11
+ vi.fn((_props: SDKProviderProps) => (
12
+ // Simplified mock, doesn't access config directly to avoid type issues
13
+ <div data-testid="sdk-provider">SDKProvider Mock</div>
14
+ )),
15
+ )
16
+
17
+ // Use the hoisted mock in the factory
18
+ vi.mock('./SDKProvider', () => ({
19
+ SDKProvider: mockSDKProviderComponent,
20
+ }))
21
+
22
+ // Mock useEffect to prevent redirect logic from running in tests
23
+ vi.mock('react', async () => {
24
+ const actual = await vi.importActual('react')
25
+ return {
26
+ ...actual,
27
+ createSanityInstance: vi.fn(() => ({
28
+ config: {},
29
+ auth: {
30
+ getSession: vi.fn(),
31
+ signIn: vi.fn(),
32
+ signOut: vi.fn(),
33
+ },
34
+ identity: {
35
+ projectId: 'test-project',
36
+ dataset: 'test-dataset',
37
+ },
38
+ dispose: vi.fn(),
39
+ })),
40
+ }
41
+ })
42
+
43
+ vi.mock('../hooks/auth/useAuthState', () => ({
44
+ useAuthState: () => ({
45
+ type: AuthStateType.LOGGED_IN,
46
+ session: {
47
+ user: {
48
+ id: 'test-user',
49
+ },
50
+ },
51
+ }),
52
+ }))
53
+
54
+ describe('SanityApp', () => {
55
+ beforeEach(() => {
56
+ vi.clearAllMocks()
57
+ // Access the mock instance correctly
58
+ mockSDKProviderComponent.mockClear()
59
+ })
60
+
61
+ it('renders SDKProvider with a single config', () => {
62
+ const singleConfig = {
63
+ projectId: 'test-project',
64
+ dataset: 'production',
65
+ }
66
+
67
+ render(
68
+ <SanityApp config={singleConfig} fallback={<div>Loading...</div>}>
69
+ <div>Child Content</div>
70
+ </SanityApp>,
71
+ )
72
+
73
+ // Check that the SDKProvider is rendered
74
+ expect(screen.getByTestId('sdk-provider')).toBeInTheDocument()
75
+
76
+ // Verify SDKProvider was called with the correct props
77
+ expect(mockSDKProviderComponent).toHaveBeenCalledTimes(1)
78
+ const sdkProviderCalls = mockSDKProviderComponent.mock.calls
79
+ const firstCallArgs1 = sdkProviderCalls[0]
80
+ expect(firstCallArgs1).toBeDefined()
81
+ expect(firstCallArgs1.length).toBeGreaterThan(0)
82
+ const props = firstCallArgs1[0] as unknown as SDKProviderProps
83
+ const config = props?.config
84
+
85
+ // Config is now passed directly as an object for single configs
86
+ expect(config).toEqual(singleConfig)
87
+ expect(props.fallback).toBeTruthy()
88
+ })
89
+
90
+ it('renders SDKProvider with multiple configs in original order', () => {
91
+ const multipleConfigs = [
92
+ {
93
+ projectId: 'project-1',
94
+ dataset: 'production',
95
+ },
96
+ {
97
+ projectId: 'project-2',
98
+ dataset: 'staging',
99
+ },
100
+ {
101
+ projectId: 'project-3',
102
+ dataset: 'development',
103
+ },
104
+ ]
105
+
106
+ render(
107
+ <SanityApp config={multipleConfigs} fallback={<div>Loading...</div>}>
108
+ <div>Child Content</div>
109
+ </SanityApp>,
110
+ )
111
+
112
+ // Check that the SDKProvider is rendered
113
+ expect(screen.getByTestId('sdk-provider')).toBeInTheDocument()
114
+
115
+ // Verify SDKProvider was called with the correct props
116
+ expect(mockSDKProviderComponent).toHaveBeenCalledTimes(1)
117
+ const sdkProviderCalls = mockSDKProviderComponent.mock.calls
118
+ const firstCallArgs2 = sdkProviderCalls[0]
119
+ expect(firstCallArgs2).toBeDefined()
120
+ expect(firstCallArgs2.length).toBeGreaterThan(0)
121
+ const props = firstCallArgs2[0] as unknown as SDKProviderProps
122
+ const config = props?.config
123
+
124
+ // Config should be passed directly to SDKProvider
125
+ expect(config).toEqual(multipleConfigs)
126
+ })
127
+
128
+ it('handles iframe environment correctly', async () => {
129
+ // Mock window.self and window.top to simulate iframe environment
130
+ const originalTop = window.top
131
+ const originalSelf = window.self
132
+
133
+ const mockSanityConfig: SanityConfig = {
134
+ projectId: 'test-project',
135
+ dataset: 'test-dataset',
136
+ }
137
+
138
+ const mockTop = {}
139
+ Object.defineProperty(window, 'top', {
140
+ value: mockTop,
141
+ writable: true,
142
+ })
143
+ Object.defineProperty(window, 'self', {
144
+ value: window,
145
+ writable: true,
146
+ })
147
+
148
+ render(
149
+ <SanityApp config={[mockSanityConfig]} fallback={<div>Fallback</div>}>
150
+ <div>Test Child</div>
151
+ </SanityApp>,
152
+ )
153
+
154
+ // Wait for 1 second
155
+ await new Promise((resolve) => setTimeout(resolve, 1010))
156
+
157
+ // Add assertions based on your iframe-specific behavior
158
+ expect(window.location.href).toBe('http://localhost:3000/')
159
+
160
+ // Clean up the mock
161
+ Object.defineProperty(window, 'top', {
162
+ value: originalTop,
163
+ writable: true,
164
+ })
165
+ Object.defineProperty(window, 'self', {
166
+ value: originalSelf,
167
+ writable: true,
168
+ })
169
+ })
170
+
171
+ it('redirects to core if not inside iframe and not local url', async () => {
172
+ const originalLocation = window.location
173
+
174
+ const mockLocation = {
175
+ replace: vi.fn(),
176
+ href: 'http://sanity-test.app',
177
+ }
178
+
179
+ const mockSanityConfig: SanityConfig = {
180
+ projectId: 'test-project',
181
+ dataset: 'test-dataset',
182
+ }
183
+
184
+ Object.defineProperty(window, 'location', {
185
+ value: mockLocation,
186
+ writable: true,
187
+ })
188
+
189
+ render(
190
+ <SanityApp config={[mockSanityConfig]} fallback={<div>Fallback</div>}>
191
+ <div>Test Child</div>
192
+ </SanityApp>,
193
+ )
194
+
195
+ // Wait for 1 second
196
+ await new Promise((resolve) => setTimeout(resolve, 1010))
197
+
198
+ // Add assertions based on your iframe-specific behavior
199
+ expect(mockLocation.replace).toHaveBeenCalledWith('https://sanity.io/welcome')
200
+
201
+ // Clean up the mock
202
+ Object.defineProperty(window, 'location', {
203
+ value: originalLocation,
204
+ writable: true,
205
+ })
206
+ })
207
+
208
+ it('does not redirect to core if not inside iframe and local url', async () => {
209
+ const originalLocation = window.location
210
+
211
+ const mockSanityConfig: SanityConfig = {
212
+ projectId: 'test-project',
213
+ dataset: 'test-dataset',
214
+ }
215
+
216
+ const mockLocation = {
217
+ replace: vi.fn(),
218
+ href: 'http://localhost:3000',
219
+ }
220
+
221
+ Object.defineProperty(window, 'location', {
222
+ value: mockLocation,
223
+ writable: true,
224
+ })
225
+
226
+ render(
227
+ <SanityApp config={[mockSanityConfig]} fallback={<div>Fallback</div>}>
228
+ <div>Test Child</div>
229
+ </SanityApp>,
230
+ )
231
+
232
+ // Wait for 1 second
233
+ await new Promise((resolve) => setTimeout(resolve, 1010))
234
+
235
+ // Add assertions based on your iframe-specific behavior
236
+ expect(mockLocation.replace).not.toHaveBeenCalled()
237
+
238
+ // Clean up the mock
239
+ Object.defineProperty(window, 'location', {
240
+ value: originalLocation,
241
+ writable: true,
242
+ })
243
+ })
244
+ })