From 5b8d48e68de60ffbbce586f519698e839030a9bd Mon Sep 17 00:00:00 2001 From: Rita Date: Fri, 27 Sep 2024 11:25:31 +0200 Subject: [PATCH 01/22] feat(sanity): add config for onStudioError --- dev/test-studio/sanity.config.ts | 7 ++++++ .../src/core/config/configPropertyReducers.ts | 22 ++++++++++++++++++- .../sanity/src/core/config/prepareConfig.ts | 19 +++++++++++++++- packages/sanity/src/core/config/types.ts | 10 ++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/dev/test-studio/sanity.config.ts b/dev/test-studio/sanity.config.ts index 43eefde3dc4..cdb850915ea 100644 --- a/dev/test-studio/sanity.config.ts +++ b/dev/test-studio/sanity.config.ts @@ -77,6 +77,13 @@ const sharedSettings = definePlugin({ }, }, + onStudioError: (error, errorMessage) => { + // eslint-disable-next-line no-console + console.log(error) + // eslint-disable-next-line no-console + console.log(errorMessage) + }, + document: { actions: documentActions, inspectors: (prev, ctx) => { diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index 0c93ce5cc1c..d92a897f4e8 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -1,5 +1,5 @@ import {type AssetSource, type SchemaTypeDefinition} from '@sanity/types' -import {type ReactNode} from 'react' +import {type ErrorInfo, type ReactNode} from 'react' import {type LocaleConfigContext, type LocaleDefinition, type LocaleResourceBundle} from '../i18n' import {type Template, type TemplateItem} from '../templates' @@ -310,6 +310,26 @@ export const documentCommentsEnabledReducer = (opts: { return result } +export const onStudioErrorResolver = (opts: { + config: PluginOptions + context: {error: Error; errorInfo: ErrorInfo} +}) => { + const {config, context} = opts + + // There is no concept of 'previous value' in this API. We only care about the final value. + // That is, if a plugin returns true, but the next plugin returns false, the result will be false. + // The last plugin 'wins'. + const resolver = config.onStudioError + + if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) + + throw new Error( + `Expected \`document.onStudioERror\` to be a a function, but received ${getPrintableType( + resolver, + )}`, + ) +} + export const internalTasksReducer = (opts: { config: PluginOptions }): {footerAction: ReactNode} | undefined => { diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index f90070cbbf6..42dad278d62 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -4,7 +4,13 @@ import {type CurrentUser, type Schema, type SchemaValidationProblem} from '@sani import {studioTheme} from '@sanity/ui' import {type i18n} from 'i18next' import {startCase} from 'lodash' -import {type ComponentType, createElement, type ElementType, isValidElement} from 'react' +import { + type ComponentType, + createElement, + type ElementType, + type ErrorInfo, + isValidElement, +} from 'react' import {isValidElementType} from 'react-is' import {map, shareReplay} from 'rxjs/operators' @@ -32,6 +38,7 @@ import { internalTasksReducer, legacySearchEnabledReducer, newDocumentOptionsResolver, + onStudioErrorResolver, partialIndexingEnabledReducer, resolveProductionUrlReducer, schemaTemplatesReducer, @@ -626,6 +633,16 @@ function resolveSource({ staticInitialValueTemplateItems, options: config, }, + onStudioError: (error: Error, errorInfo: ErrorInfo) => { + return onStudioErrorResolver({ + config, + context: { + error: error, + errorInfo: errorInfo, + }, + }) + }, + beta: { treeArrayEditing: { // This beta feature is no longer available. diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index 6f223b42e60..adb9c143355 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -10,7 +10,7 @@ import { type SchemaTypeDefinition, } from '@sanity/types' import {type i18n} from 'i18next' -import {type ComponentType, type ReactNode} from 'react' +import {type ComponentType, type ErrorInfo, type ReactNode} from 'react' import {type Observable} from 'rxjs' import {type Router, type RouterState} from 'sanity/router' @@ -392,6 +392,10 @@ export interface PluginOptions { * @internal */ beta?: BetaFeatures + /** Configuration for error handling. + * @internal + */ + onStudioError?: (error: Error, errorInfo: ErrorInfo) => void } /** @internal */ @@ -781,6 +785,10 @@ export interface Source { * @internal */ beta?: BetaFeatures + /** Configuration for error handling. + * @internal + */ + onStudioError?: (error: Error, errorInfo: ErrorInfo) => void } /** @internal */ From 717804d4b2eadcb1561c287bdc41b5d2ef39e8c2 Mon Sep 17 00:00:00 2001 From: Rita Date: Fri, 27 Sep 2024 14:43:30 +0200 Subject: [PATCH 02/22] feat(sanity): add call to config from WorkspaceRouterProvider --- dev/test-studio/sanity.config.ts | 10 ++++++-- .../src/core/config/configPropertyReducers.ts | 2 +- .../WorkspaceRouterProvider.tsx | 24 ++++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/dev/test-studio/sanity.config.ts b/dev/test-studio/sanity.config.ts index cdb850915ea..5ad76c08c34 100644 --- a/dev/test-studio/sanity.config.ts +++ b/dev/test-studio/sanity.config.ts @@ -77,11 +77,11 @@ const sharedSettings = definePlugin({ }, }, - onStudioError: (error, errorMessage) => { + onStudioError: (error, errorInfo) => { // eslint-disable-next-line no-console console.log(error) // eslint-disable-next-line no-console - console.log(errorMessage) + console.log(errorInfo) }, document: { @@ -253,6 +253,12 @@ export default defineConfig([ dataset: 'test', plugins: [sharedSettings(), studioComponentsPlugin(), formComponentsPlugin()], basePath: '/custom-components', + onStudioError: (error, errorInfo) => { + // eslint-disable-next-line no-console + console.log(error) + // eslint-disable-next-line no-console + console.log(errorInfo) + }, form: { components: { input: Input, diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index d92a897f4e8..e84d99f5286 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -324,7 +324,7 @@ export const onStudioErrorResolver = (opts: { if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) throw new Error( - `Expected \`document.onStudioERror\` to be a a function, but received ${getPrintableType( + `Expected \`document.onStudioError\` to be a a function, but received ${getPrintableType( resolver, )}`, ) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index eeaeb2f99c2..458d97986c4 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -1,12 +1,15 @@ +import {ErrorBoundary} from '@sanity/ui' import {escapeRegExp, isEqual} from 'lodash' import { type ComponentType, type MutableRefObject, type ReactNode, + useCallback, useEffect, useMemo, useRef, } from 'react' +import {useSource} from 'sanity' import {type Router, RouterProvider, type RouterState} from 'sanity/router' import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector.js' @@ -30,15 +33,30 @@ export function WorkspaceRouterProvider({ const history = useRouterHistory() const router = useMemo(() => createRouter({basePath, tools}), [basePath, tools]) const [state, onNavigate] = useRouterFromWorkspaceHistory(history, router, tools) + const {onStudioError} = useSource() + + const handleCatchError = useCallback( + ({error, info}: {error: Error; info: React.ErrorInfo}) => { + /** catches errors in studio to be able to be caught in the config */ + if (onStudioError) { + onStudioError(error, info) + } + + throw new Error(error.message) + }, + [onStudioError], + ) // `state` is only null if the Studio is somehow rendering in SSR or using hydrateRoot in combination with `unstable_noAuthBoundary`. // Which makes this loading condition extremely rare, most of the time it'll render `RouteProvider` right away. if (!state) return return ( - - {children} - + + + {children} + + ) } From 16dcca929efebeb4831ab6127082ef2e5947c6c9 Mon Sep 17 00:00:00 2001 From: Rita Date: Fri, 27 Sep 2024 15:22:09 +0200 Subject: [PATCH 03/22] feat(sanity): add call on FormBuilderInput, add ability to have config as undefined --- dev/test-studio/sanity.config.ts | 14 +++++++------- .../src/core/config/configPropertyReducers.ts | 1 + .../form/studio/FormBuilderInputErrorBoundary.tsx | 14 +++++++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/dev/test-studio/sanity.config.ts b/dev/test-studio/sanity.config.ts index 5ad76c08c34..6202b34213a 100644 --- a/dev/test-studio/sanity.config.ts +++ b/dev/test-studio/sanity.config.ts @@ -77,13 +77,6 @@ const sharedSettings = definePlugin({ }, }, - onStudioError: (error, errorInfo) => { - // eslint-disable-next-line no-console - console.log(error) - // eslint-disable-next-line no-console - console.log(errorInfo) - }, - document: { actions: documentActions, inspectors: (prev, ctx) => { @@ -160,6 +153,13 @@ const defaultWorkspace = { projectId: 'ppsg7ml5', dataset: 'test', plugins: [sharedSettings()], + + onStudioError: (error, errorInfo) => { + // eslint-disable-next-line no-console + console.log(error) + // eslint-disable-next-line no-console + console.log(errorInfo) + }, basePath: '/test', icon: SanityMonogram, // eslint-disable-next-line camelcase diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index e84d99f5286..29a330ba3be 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -322,6 +322,7 @@ export const onStudioErrorResolver = (opts: { const resolver = config.onStudioError if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) + if (!resolver) return undefined throw new Error( `Expected \`document.onStudioError\` to be a a function, but received ${getPrintableType( diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx index 8bf4b795a90..c3f53d073bc 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx @@ -1,5 +1,6 @@ import {Box, Card, Code, ErrorBoundary, Stack, Text} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' +import {useSource} from 'sanity' import {useHotModuleReload} from 'use-hot-module-reload' import {SchemaError} from '../../config' @@ -27,10 +28,21 @@ export function FormBuilderInputErrorBoundary( error: null, info: {}, }) + const {onStudioError} = useSource() const handleRetry = useCallback(() => setError({error: null, info: {}}), []) + const handleCatch = useCallback( + ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { + setError({error: caughtError, info: caughtInfo}) + + if (onStudioError) { + onStudioError(caughtError, caughtInfo) + } + }, + [onStudioError], + ) if (!error) { - return {children} + return {children} } return From bf266dd96c346829649a67a2cd274938bf97e7d5 Mon Sep 17 00:00:00 2001 From: Rita Date: Mon, 30 Sep 2024 08:45:32 +0200 Subject: [PATCH 04/22] feat(sanity): add call to config from Structure --- .../components/structureTool/StructureTitle.tsx | 1 + .../structureTool/StructureToolBoundary.tsx | 17 +++++++++++++++-- .../validation/ValidationInspector.tsx | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx b/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx index 426f684e448..3bd2b940702 100644 --- a/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx +++ b/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx @@ -46,6 +46,7 @@ const DocumentTitle = (props: {documentId: string; documentType: string}) => { document.title = newTitle }, [documentTitle, settled, newTitle]) + throw new Error('This is an error from the structure tool') return null } diff --git a/packages/sanity/src/structure/components/structureTool/StructureToolBoundary.tsx b/packages/sanity/src/structure/components/structureTool/StructureToolBoundary.tsx index a9fbb7a3063..7582d1cf971 100644 --- a/packages/sanity/src/structure/components/structureTool/StructureToolBoundary.tsx +++ b/packages/sanity/src/structure/components/structureTool/StructureToolBoundary.tsx @@ -1,5 +1,5 @@ import {ErrorBoundary} from '@sanity/ui' -import {useEffect, useState} from 'react' +import {useCallback, useEffect, useState} from 'react' import {SourceProvider, type Tool, useWorkspace} from 'sanity' import {setActivePanes} from '../../getIntentState' @@ -25,11 +25,24 @@ export function StructureToolBoundary({tool: {options}}: StructureToolBoundaryPr }, []) const [{error}, setError] = useState<{error: unknown}>({error: null}) + + const handleCatchError = useCallback( + ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { + setError({error: caughtError}) + const {onStudioError} = firstSource + + if (onStudioError) { + onStudioError(caughtError, caughtInfo) + } + }, + [firstSource], + ) + // this re-throws if the error it catches is not a PaneResolutionError if (error) return return ( - + diff --git a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx index b3d7c94e582..419ad16cbec 100644 --- a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx +++ b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx @@ -13,7 +13,7 @@ import { } from '@sanity/types' import {Box, Card, type CardTone, ErrorBoundary, Flex, Stack, Text} from '@sanity/ui' import {createElement, type ErrorInfo, Fragment, useCallback, useMemo, useState} from 'react' -import {type DocumentInspectorProps, useTranslation} from 'sanity' +import {type DocumentInspectorProps, useSource, useTranslation} from 'sanity' import {DocumentInspectorHeader} from '../../documentInspector' import {useDocumentPane} from '../../useDocumentPane' @@ -44,6 +44,7 @@ export function ValidationInspector(props: DocumentInspectorProps) { [onFocus, onPathOpen], ) + throw new Error('This is an error from the validation inspector') return ( Date: Mon, 30 Sep 2024 13:40:51 +0200 Subject: [PATCH 05/22] refactor(sanity): remove throw errors --- .../src/structure/components/structureTool/StructureTitle.tsx | 1 - .../document/inspectors/validation/ValidationInspector.tsx | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx b/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx index 3bd2b940702..426f684e448 100644 --- a/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx +++ b/packages/sanity/src/structure/components/structureTool/StructureTitle.tsx @@ -46,7 +46,6 @@ const DocumentTitle = (props: {documentId: string; documentType: string}) => { document.title = newTitle }, [documentTitle, settled, newTitle]) - throw new Error('This is an error from the structure tool') return null } diff --git a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx index 419ad16cbec..b3d7c94e582 100644 --- a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx +++ b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx @@ -13,7 +13,7 @@ import { } from '@sanity/types' import {Box, Card, type CardTone, ErrorBoundary, Flex, Stack, Text} from '@sanity/ui' import {createElement, type ErrorInfo, Fragment, useCallback, useMemo, useState} from 'react' -import {type DocumentInspectorProps, useSource, useTranslation} from 'sanity' +import {type DocumentInspectorProps, useTranslation} from 'sanity' import {DocumentInspectorHeader} from '../../documentInspector' import {useDocumentPane} from '../../useDocumentPane' @@ -44,7 +44,6 @@ export function ValidationInspector(props: DocumentInspectorProps) { [onFocus, onPathOpen], ) - throw new Error('This is an error from the validation inspector') return ( Date: Mon, 30 Sep 2024 16:17:08 +0200 Subject: [PATCH 06/22] chore(sanity): update import on formBuilderInputErrorBoundary --- .../src/core/form/studio/FormBuilderInputErrorBoundary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx index c3f53d073bc..1220ce83578 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx @@ -1,12 +1,12 @@ import {Box, Card, Code, ErrorBoundary, Stack, Text} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' -import {useSource} from 'sanity' import {useHotModuleReload} from 'use-hot-module-reload' import {SchemaError} from '../../config' import {isDev} from '../../environment' import {useTranslation} from '../../i18n' import {CorsOriginError} from '../../store' +import {useSource} from '../../studio/source' import {isRecord} from '../../util' import {Alert} from '../components/Alert' From 7a5b89c50ae6c010b56bde7686c900769da87f0d Mon Sep 17 00:00:00 2001 From: Rita Date: Mon, 30 Sep 2024 16:22:13 +0200 Subject: [PATCH 07/22] chore(sanity): update import on WorkspaceRouterProvider --- .../src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index 458d97986c4..01059030905 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -9,7 +9,6 @@ import { useMemo, useRef, } from 'react' -import {useSource} from 'sanity' import {type Router, RouterProvider, type RouterState} from 'sanity/router' import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector.js' @@ -17,6 +16,7 @@ import {type Tool, type Workspace} from '../../config' import {createRouter, type RouterHistory, type RouterStateEvent} from '../router' import {decodeUrlState, resolveDefaultState, resolveIntentState} from '../router/helpers' import {useRouterHistory} from '../router/RouterHistoryContext' +import {useSource} from '../source' interface WorkspaceRouterProviderProps { children: ReactNode From 9eefd07b12760f338c10e3d2dc50fadd44952b6f Mon Sep 17 00:00:00 2001 From: Rita Date: Tue, 1 Oct 2024 09:23:54 +0200 Subject: [PATCH 08/22] test(sanity): add test to formBuilderInputErrorBoundary --- .../FormBuilderInputErrorBoundary.test.tsx | 69 +++++++++++++++++++ .../studio/FormBuilderInputErrorBoundary.tsx | 7 +- 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx new file mode 100644 index 00000000000..dfb64748038 --- /dev/null +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx @@ -0,0 +1,69 @@ +import {beforeAll, describe, expect, it, jest} from '@jest/globals' +import {studioTheme, ThemeProvider} from '@sanity/ui' +import {render, screen} from '@testing-library/react' + +import {LocaleProviderBase} from '../../i18n/components/LocaleProvider' +import {prepareI18n} from '../../i18n/i18nConfig' +import {usEnglishLocale} from '../../i18n/locales' +import {useSource} from '../../studio/source' +import {FormBuilderInputErrorBoundary} from './FormBuilderInputErrorBoundary' + +// Mock dependencies +jest.mock('../../studio/source', () => ({ + useSource: jest.fn(), +})) + +jest.mock('use-hot-module-reload', () => ({ + useHotModuleReload: jest.fn(), +})) + +const useSourceMock = useSource as jest.Mock + +describe('FormBuilderInputErrorBoundary', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + it('renders children when there is no error', async () => { + render( + +
Child Component
+
, + ) + + expect(screen.getByTestId('child')).toBeInTheDocument() + }) + + it('calls onStudioError when an error is caught', async () => { + const onStudioError = jest.fn() + useSourceMock.mockReturnValue({onStudioError}) + + const ThrowErrorComponent = () => { + throw new Error('An EXPECTED, testing error occurred!') + } + + const locales = [usEnglishLocale] + const {i18next} = prepareI18n({ + projectId: 'test', + dataset: 'test', + name: 'test', + }) + + render( + + + + + + + , + ) + + expect(onStudioError).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx index 1220ce83578..e2b3dafed48 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx @@ -28,17 +28,18 @@ export function FormBuilderInputErrorBoundary( error: null, info: {}, }) - const {onStudioError} = useSource() + const source = useSource() const handleRetry = useCallback(() => setError({error: null, info: {}}), []) const handleCatch = useCallback( ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { setError({error: caughtError, info: caughtInfo}) - if (onStudioError) { + if (source?.onStudioError) { + const {onStudioError} = source onStudioError(caughtError, caughtInfo) } }, - [onStudioError], + [source], ) if (!error) { From 9fd24f416ddd2a9e1b1572c24da8dbf9e73feeae Mon Sep 17 00:00:00 2001 From: Rita Date: Tue, 1 Oct 2024 10:21:48 +0200 Subject: [PATCH 09/22] test(sanity): add test to WorkspaceRouterProvider --- .../WorkspaceRouterProvider.test.tsx | 114 ++++++++++++++++++ .../WorkspaceRouterProvider.tsx | 9 +- 2 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx new file mode 100644 index 00000000000..c4635f1efe9 --- /dev/null +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx @@ -0,0 +1,114 @@ +import {describe, expect, it, jest} from '@jest/globals' +import {ErrorBoundary, studioTheme, ThemeProvider} from '@sanity/ui' +import {render, screen} from '@testing-library/react' + +import {LocaleProviderBase, usEnglishLocale} from '../../i18n' +import {prepareI18n} from '../../i18n/i18nConfig' +import {useSource} from '../source' +import {WorkspaceRouterProvider} from './WorkspaceRouterProvider' + +jest.mock('../router/RouterHistoryContext', () => ({ + useRouterHistory: () => ({ + location: {pathname: '/'}, + listen: jest.fn(), + }), +})) + +jest.mock('../source', () => ({ + useSource: jest.fn(), +})) + +jest.mock('../router', () => ({ + createRouter: () => ({ + getBasePath: jest.fn(), + decode: jest.fn(), + isNotFound: jest.fn(), + }), +})) + +jest.mock('sanity/router', () => ({ + RouterProvider: ({children}: {children: React.ReactNode}) =>
{children}
, + IntentLink: () =>
IntentLink
, +})) + +jest.mock('./WorkspaceRouterProvider', () => ({ + ...(jest.requireActual('./WorkspaceRouterProvider') as object), + useRouterFromWorkspaceHistory: jest.fn(), +})) + +const useSourceMock = useSource as jest.Mock + +describe('WorkspaceRouterProvider', () => { + const LoadingComponent = () =>
Loading...
+ const children =
Children
+ const workspace = { + basePath: '', + tools: [], + icon: null, + unstable_sources: [], + scheduledPublishing: false, + document: {}, + form: {}, + search: {}, + title: 'Default Workspace', + name: 'default', + projectId: 'test', + dataset: 'test', + schema: {}, + templates: {}, + currentUser: {}, + authenticated: true, + auth: {}, + getClient: jest.fn(), + i18n: {}, + __internal: {}, + type: 'workspace', + // Add other required properties with appropriate default values + } as unknown as Workspace + + it('renders children when state is not null', () => { + render( + + {children} + , + ) + + expect(screen.getByText('Children')).toBeInTheDocument() + }) + + it('calls onStudioError when an error is caught', () => { + const onStudioError = jest.fn() + useSourceMock.mockReturnValue({onStudioError}) + + const ThrowErrorComponent = () => { + throw new Error('An EXPECTED, testing error occurred!') + } + + const locales = [usEnglishLocale] + const {i18next} = prepareI18n({ + projectId: 'test', + dataset: 'test', + name: 'test', + }) + + render( + + + {/* prevents thrown error from breaking the test */} + <>}> + + + + + + , + ) + + expect(onStudioError).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index 01059030905..43f080d15a8 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -33,18 +33,19 @@ export function WorkspaceRouterProvider({ const history = useRouterHistory() const router = useMemo(() => createRouter({basePath, tools}), [basePath, tools]) const [state, onNavigate] = useRouterFromWorkspaceHistory(history, router, tools) - const {onStudioError} = useSource() + const source = useSource() const handleCatchError = useCallback( ({error, info}: {error: Error; info: React.ErrorInfo}) => { /** catches errors in studio to be able to be caught in the config */ - if (onStudioError) { + if (source?.onStudioError) { + const {onStudioError} = source onStudioError(error, info) } throw new Error(error.message) }, - [onStudioError], + [source], ) // `state` is only null if the Studio is somehow rendering in SSR or using hydrateRoot in combination with `unstable_noAuthBoundary`. @@ -65,7 +66,7 @@ type HandleNavigate = (opts: {path: string; replace?: boolean}) => void /** * @internal */ -function useRouterFromWorkspaceHistory( +export function useRouterFromWorkspaceHistory( history: RouterHistory, router: Router, tools: Tool[], From c385b90dba9dccc3e8b1069ae1dec5b5a19bf93e Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 11:56:16 +0200 Subject: [PATCH 10/22] refactor(router): add ErrorBoundary component that should be used instead, update tests --- .../studio/FormBuilderInputErrorBoundary.tsx | 18 +--- .../components/filters/filter/FilterForm.tsx | 1 + .../WorkspaceRouterProvider.tsx | 20 ++--- .../components/confirmDeleteDialog/index.tsx | 1 + .../structureTool/StructureToolBoundary.tsx | 18 +--- .../validation/ValidationInspector.tsx | 1 + .../errorBoundary/ErrorBoundary.tsx | 31 +++++++ .../__test__/ErrorBoundary.test.tsx | 82 +++++++++++++++++++ .../src/ui-components/errorBoundary/index.ts | 1 + packages/sanity/src/ui-components/index.ts | 1 + 10 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx create mode 100644 packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx create mode 100644 packages/sanity/src/ui-components/errorBoundary/index.ts diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx index e2b3dafed48..17caafefdb0 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.tsx @@ -1,12 +1,12 @@ -import {Box, Card, Code, ErrorBoundary, Stack, Text} from '@sanity/ui' +import {Box, Card, Code, Stack, Text} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' import {useHotModuleReload} from 'use-hot-module-reload' +import {ErrorBoundary} from '../../../ui-components/errorBoundary' import {SchemaError} from '../../config' import {isDev} from '../../environment' import {useTranslation} from '../../i18n' import {CorsOriginError} from '../../store' -import {useSource} from '../../studio/source' import {isRecord} from '../../util' import {Alert} from '../components/Alert' @@ -28,22 +28,10 @@ export function FormBuilderInputErrorBoundary( error: null, info: {}, }) - const source = useSource() const handleRetry = useCallback(() => setError({error: null, info: {}}), []) - const handleCatch = useCallback( - ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { - setError({error: caughtError, info: caughtInfo}) - - if (source?.onStudioError) { - const {onStudioError} = source - onStudioError(caughtError, caughtInfo) - } - }, - [source], - ) if (!error) { - return {children} + return {children} } return diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx index 237fc7d500f..d1412177f0c 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx @@ -64,6 +64,7 @@ export function FilterForm({filter}: FilterFormProps) { } // Flex order is reversed to ensure form inputs are focusable first + // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return ( diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index 43f080d15a8..1d2d4b4f7d6 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -1,4 +1,3 @@ -import {ErrorBoundary} from '@sanity/ui' import {escapeRegExp, isEqual} from 'lodash' import { type ComponentType, @@ -12,11 +11,11 @@ import { import {type Router, RouterProvider, type RouterState} from 'sanity/router' import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector.js' +import {ErrorBoundary} from '../../../ui-components/errorBoundary' import {type Tool, type Workspace} from '../../config' import {createRouter, type RouterHistory, type RouterStateEvent} from '../router' import {decodeUrlState, resolveDefaultState, resolveIntentState} from '../router/helpers' import {useRouterHistory} from '../router/RouterHistoryContext' -import {useSource} from '../source' interface WorkspaceRouterProviderProps { children: ReactNode @@ -33,20 +32,11 @@ export function WorkspaceRouterProvider({ const history = useRouterHistory() const router = useMemo(() => createRouter({basePath, tools}), [basePath, tools]) const [state, onNavigate] = useRouterFromWorkspaceHistory(history, router, tools) - const source = useSource() - - const handleCatchError = useCallback( - ({error, info}: {error: Error; info: React.ErrorInfo}) => { - /** catches errors in studio to be able to be caught in the config */ - if (source?.onStudioError) { - const {onStudioError} = source - onStudioError(error, info) - } - throw new Error(error.message) - }, - [source], - ) + const handleCatchError = useCallback(({error}: {error: Error}) => { + /** catches errors in studio that bubble up, throwing the error */ + throw new Error(error.message) + }, []) // `state` is only null if the Studio is somehow rendering in SSR or using hydrateRoot in combination with `unstable_noAuthBoundary`. // Which makes this loading condition extremely rare, most of the time it'll render `RouteProvider` right away. diff --git a/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx b/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx index 9d4a76510aa..787c884333a 100644 --- a/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx +++ b/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx @@ -18,6 +18,7 @@ function ConfirmDeleteDialogContainer(props: ConfirmDeleteDialogProps) { const [error, setError] = useState(null) const handleRetry = useCallback(() => setError(null), []) + // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return error ? ( ({error: null}) - const handleCatchError = useCallback( - ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { - setError({error: caughtError}) - const {onStudioError} = firstSource - - if (onStudioError) { - onStudioError(caughtError, caughtInfo) - } - }, - [firstSource], - ) - // this re-throws if the error it catches is not a PaneResolutionError if (error) return return ( - + diff --git a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx index b3d7c94e582..edeb231dd35 100644 --- a/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx +++ b/packages/sanity/src/structure/panes/document/inspectors/validation/ValidationInspector.tsx @@ -92,6 +92,7 @@ function ValidationCard(props: { const handleOpen = useCallback(() => onOpen(marker.path), [marker, onOpen]) const [errorInfo, setErrorInfo] = useState<{error: Error; info: ErrorInfo} | null>(null) + // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return ( {errorInfo && ( diff --git a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx new file mode 100644 index 00000000000..84bbfc6ce80 --- /dev/null +++ b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx @@ -0,0 +1,31 @@ +import { + ErrorBoundary as UIErrorBoundary, + type ErrorBoundaryProps as UIErrorBoundaryProps, +} from '@sanity/ui' +import {useCallback} from 'react' + +import {useSource} from '../../core/studio/source' + +export type ErrorBoundaryProps = UIErrorBoundaryProps + +/** + * ErrorBoundary component that catches errors and uses onStudioError config property + * It also calls the onCatch prop if it exists. + */ +export function ErrorBoundary({onCatch, ...rest}: ErrorBoundaryProps): JSX.Element { + // Use context, because source could be undefined and we don't want to throw in that case + const source = useSource() + + const handleCatch = useCallback( + ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { + // Send the error to the source if it has an onStudioError method + source?.onStudioError?.(caughtError, caughtInfo) + + // Call the onCatch prop if it exists + onCatch?.({error: caughtError, info: caughtInfo}) + }, + [source, onCatch], + ) + + return +} diff --git a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx new file mode 100644 index 00000000000..7bbb7304aa9 --- /dev/null +++ b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx @@ -0,0 +1,82 @@ +import {beforeAll, describe, expect, it, jest} from '@jest/globals' +import {studioTheme, ThemeProvider} from '@sanity/ui' +import {render} from '@testing-library/react' + +import {LocaleProviderBase, usEnglishLocale} from '../../../core/i18n' +import {prepareI18n} from '../../../core/i18n/i18nConfig' +import {useSource} from '../../../core/studio/source' +import {ErrorBoundary} from '../ErrorBoundary' + +// Mock dependencies +jest.mock('../../../core/studio/source', () => ({ + useSource: jest.fn(), +})) + +const useSourceMock = useSource as jest.Mock + +describe('ErrorBoundary', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + const Wrapper = ({children}: {children: React.ReactNode}) => { + const locales = [usEnglishLocale] + const {i18next} = prepareI18n({ + projectId: 'test', + dataset: 'test', + name: 'test', + }) + + return ( + + + {children} + + + ) + } + + it('calls onStudioError when an error is caught', () => { + const onStudioError = jest.fn() + const onCatch = jest.fn() + + useSourceMock.mockReturnValue({onStudioError}) + + const ThrowErrorComponent = () => { + throw new Error('An EXPECTED, testing error occurred!') + } + + render( + + + + + , + ) + + expect(onStudioError).toHaveBeenCalledTimes(1) + }) + + it('calls onCatch prop when an error is caught when no onStudioError exists', () => { + const onCatch = jest.fn() + + const ThrowErrorComponent = () => { + throw new Error('An EXPECTED, testing error occurred!') + } + + render( + + + + + , + ) + + expect(onCatch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/sanity/src/ui-components/errorBoundary/index.ts b/packages/sanity/src/ui-components/errorBoundary/index.ts new file mode 100644 index 00000000000..2bca71da107 --- /dev/null +++ b/packages/sanity/src/ui-components/errorBoundary/index.ts @@ -0,0 +1 @@ +export * from './ErrorBoundary' diff --git a/packages/sanity/src/ui-components/index.ts b/packages/sanity/src/ui-components/index.ts index 8e1eeedabc2..a0d7cbccdd9 100644 --- a/packages/sanity/src/ui-components/index.ts +++ b/packages/sanity/src/ui-components/index.ts @@ -1,6 +1,7 @@ export * from './button' export * from './conditionalWrapper' export * from './dialog' +export * from './errorBoundary' export * from './menuButton' export * from './menuGroup' export * from './menuItem' From 79004809fab1473dc286c385f03d573ae29d61a4 Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 13:44:21 +0200 Subject: [PATCH 11/22] refactor(ui-componets): replace useSource with useContext(SourceContext) --- .../errorBoundary/ErrorBoundary.tsx | 6 ++-- .../__test__/ErrorBoundary.test.tsx | 36 ++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx index 84bbfc6ce80..7156758537a 100644 --- a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx @@ -2,9 +2,9 @@ import { ErrorBoundary as UIErrorBoundary, type ErrorBoundaryProps as UIErrorBoundaryProps, } from '@sanity/ui' -import {useCallback} from 'react' +import {useCallback, useContext} from 'react' -import {useSource} from '../../core/studio/source' +import {SourceContext} from '../../_singletons' export type ErrorBoundaryProps = UIErrorBoundaryProps @@ -14,7 +14,7 @@ export type ErrorBoundaryProps = UIErrorBoundaryProps */ export function ErrorBoundary({onCatch, ...rest}: ErrorBoundaryProps): JSX.Element { // Use context, because source could be undefined and we don't want to throw in that case - const source = useSource() + const source = useContext(SourceContext) const handleCatch = useCallback( ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { diff --git a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx index 7bbb7304aa9..b26b0604883 100644 --- a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx @@ -4,16 +4,8 @@ import {render} from '@testing-library/react' import {LocaleProviderBase, usEnglishLocale} from '../../../core/i18n' import {prepareI18n} from '../../../core/i18n/i18nConfig' -import {useSource} from '../../../core/studio/source' import {ErrorBoundary} from '../ErrorBoundary' -// Mock dependencies -jest.mock('../../../core/studio/source', () => ({ - useSource: jest.fn(), -})) - -const useSourceMock = useSource as jest.Mock - describe('ErrorBoundary', () => { beforeAll(() => { jest.clearAllMocks() @@ -45,8 +37,6 @@ describe('ErrorBoundary', () => { const onStudioError = jest.fn() const onCatch = jest.fn() - useSourceMock.mockReturnValue({onStudioError}) - const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') } @@ -65,16 +55,38 @@ describe('ErrorBoundary', () => { it('calls onCatch prop when an error is caught when no onStudioError exists', () => { const onCatch = jest.fn() + const WrapperWithoutError = ({children}: {children: React.ReactNode}) => { + const locales = [usEnglishLocale] + const {i18next} = prepareI18n({ + projectId: 'test', + dataset: 'test', + name: 'test', + }) + + return ( + + + {children} + + + ) + } + const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') } render( - + - , + , ) expect(onCatch).toHaveBeenCalledTimes(1) From 077366069ad71afed24b85f3face676f5d66268b Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 13:44:35 +0200 Subject: [PATCH 12/22] test(sanity): update tests after refactor --- .../FormBuilderInputErrorBoundary.test.tsx | 48 ++++++---------- .../WorkspaceRouterProvider.test.tsx | 55 ++++++++----------- 2 files changed, 41 insertions(+), 62 deletions(-) diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx index dfb64748038..84b718a2660 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx @@ -1,24 +1,15 @@ import {beforeAll, describe, expect, it, jest} from '@jest/globals' -import {studioTheme, ThemeProvider} from '@sanity/ui' import {render, screen} from '@testing-library/react' +import {type SanityClient} from 'sanity' -import {LocaleProviderBase} from '../../i18n/components/LocaleProvider' -import {prepareI18n} from '../../i18n/i18nConfig' -import {usEnglishLocale} from '../../i18n/locales' -import {useSource} from '../../studio/source' +import {createMockSanityClient} from '../../../../test/mocks/mockSanityClient' +import {createTestProvider} from '../../../../test/testUtils/TestProvider' import {FormBuilderInputErrorBoundary} from './FormBuilderInputErrorBoundary' -// Mock dependencies -jest.mock('../../studio/source', () => ({ - useSource: jest.fn(), -})) - jest.mock('use-hot-module-reload', () => ({ useHotModuleReload: jest.fn(), })) -const useSourceMock = useSource as jest.Mock - describe('FormBuilderInputErrorBoundary', () => { beforeAll(() => { jest.clearAllMocks() @@ -36,32 +27,29 @@ describe('FormBuilderInputErrorBoundary', () => { it('calls onStudioError when an error is caught', async () => { const onStudioError = jest.fn() - useSourceMock.mockReturnValue({onStudioError}) const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') } - const locales = [usEnglishLocale] - const {i18next} = prepareI18n({ - projectId: 'test', - dataset: 'test', - name: 'test', + const client = createMockSanityClient() as unknown as SanityClient + + const TestProvider = await createTestProvider({ + client, + config: { + name: 'default', + projectId: 'test', + dataset: 'test', + onStudioError, + }, }) render( - - - - - - - , + + + + + , ) expect(onStudioError).toHaveBeenCalledTimes(1) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx index c4635f1efe9..0c9cb018ef0 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx @@ -1,10 +1,10 @@ import {describe, expect, it, jest} from '@jest/globals' -import {ErrorBoundary, studioTheme, ThemeProvider} from '@sanity/ui' +import {ErrorBoundary} from '@sanity/ui' import {render, screen} from '@testing-library/react' +import {type SanityClient} from 'sanity' -import {LocaleProviderBase, usEnglishLocale} from '../../i18n' -import {prepareI18n} from '../../i18n/i18nConfig' -import {useSource} from '../source' +import {createMockSanityClient} from '../../../../test/mocks/mockSanityClient' +import {createTestProvider} from '../../../../test/testUtils/TestProvider' import {WorkspaceRouterProvider} from './WorkspaceRouterProvider' jest.mock('../router/RouterHistoryContext', () => ({ @@ -14,10 +14,6 @@ jest.mock('../router/RouterHistoryContext', () => ({ }), })) -jest.mock('../source', () => ({ - useSource: jest.fn(), -})) - jest.mock('../router', () => ({ createRouter: () => ({ getBasePath: jest.fn(), @@ -36,8 +32,6 @@ jest.mock('./WorkspaceRouterProvider', () => ({ useRouterFromWorkspaceHistory: jest.fn(), })) -const useSourceMock = useSource as jest.Mock - describe('WorkspaceRouterProvider', () => { const LoadingComponent = () =>
Loading...
const children =
Children
@@ -76,37 +70,34 @@ describe('WorkspaceRouterProvider', () => { expect(screen.getByText('Children')).toBeInTheDocument() }) - it('calls onStudioError when an error is caught', () => { + it('calls onStudioError when an error is caught', async () => { const onStudioError = jest.fn() - useSourceMock.mockReturnValue({onStudioError}) const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') } - const locales = [usEnglishLocale] - const {i18next} = prepareI18n({ - projectId: 'test', - dataset: 'test', - name: 'test', + const client = createMockSanityClient() as unknown as SanityClient + + const TestProvider = await createTestProvider({ + client, + config: { + name: 'default', + projectId: 'test', + dataset: 'test', + onStudioError, + }, }) render( - - - {/* prevents thrown error from breaking the test */} - <>}> - - - - - - , + + {/* prevents thrown error from breaking the test */} + <>}> + + + + + , ) expect(onStudioError).toHaveBeenCalledTimes(1) From 7e5d861d15be4fe0988ad2b54977229f5aac3a0f Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 13:45:31 +0200 Subject: [PATCH 13/22] refactor(sanity): remove unneeded export --- .../src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index 1d2d4b4f7d6..d206f83c2cf 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -56,7 +56,7 @@ type HandleNavigate = (opts: {path: string; replace?: boolean}) => void /** * @internal */ -export function useRouterFromWorkspaceHistory( +function useRouterFromWorkspaceHistory( history: RouterHistory, router: Router, tools: Tool[], From 6545c653b3ee210f85ffa007f602f75173df3cd9 Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 13:54:33 +0200 Subject: [PATCH 14/22] test(ui-components): add test provider --- .../__test__/ErrorBoundary.test.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx index b26b0604883..3fe97653eae 100644 --- a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx @@ -1,7 +1,10 @@ import {beforeAll, describe, expect, it, jest} from '@jest/globals' import {studioTheme, ThemeProvider} from '@sanity/ui' import {render} from '@testing-library/react' +import {type SanityClient} from 'sanity' +import {createMockSanityClient} from '../../../../test/mocks/mockSanityClient' +import {createTestProvider} from '../../../../test/testUtils/TestProvider' import {LocaleProviderBase, usEnglishLocale} from '../../../core/i18n' import {prepareI18n} from '../../../core/i18n/i18nConfig' import {ErrorBoundary} from '../ErrorBoundary' @@ -33,7 +36,7 @@ describe('ErrorBoundary', () => { ) } - it('calls onStudioError when an error is caught', () => { + it('calls onStudioError when an error is caught', async () => { const onStudioError = jest.fn() const onCatch = jest.fn() @@ -41,12 +44,24 @@ describe('ErrorBoundary', () => { throw new Error('An EXPECTED, testing error occurred!') } + const client = createMockSanityClient() as unknown as SanityClient + + const TestProvider = await createTestProvider({ + client, + config: { + name: 'default', + projectId: 'test', + dataset: 'test', + onStudioError, + }, + }) + render( - + - , + , ) expect(onStudioError).toHaveBeenCalledTimes(1) From 7926fd3acdd404b85d8d2a1542897bc088203f81 Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 14:00:27 +0200 Subject: [PATCH 15/22] test(ui-components): remove unused code --- .../__test__/ErrorBoundary.test.tsx | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx index 3fe97653eae..c1cde62f3aa 100644 --- a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx @@ -14,28 +14,6 @@ describe('ErrorBoundary', () => { jest.clearAllMocks() }) - const Wrapper = ({children}: {children: React.ReactNode}) => { - const locales = [usEnglishLocale] - const {i18next} = prepareI18n({ - projectId: 'test', - dataset: 'test', - name: 'test', - }) - - return ( - - - {children} - - - ) - } - it('calls onStudioError when an error is caught', async () => { const onStudioError = jest.fn() const onCatch = jest.fn() From f3459d7cb30faafec2f38e4663cb25a6ac484bd2 Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 14:22:32 +0200 Subject: [PATCH 16/22] refactor(sanity): update ErrorBoundary to use ui-components --- .../navbar/search/components/filters/filter/FilterForm.tsx | 5 ++--- .../src/structure/components/confirmDeleteDialog/index.tsx | 5 ++--- .../document/inspectors/validation/ValidationInspector.tsx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx index d1412177f0c..005603a0e90 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/FilterForm.tsx @@ -1,9 +1,9 @@ import {TrashIcon} from '@sanity/icons' -import {Box, Card, ErrorBoundary, Flex, Stack, Text} from '@sanity/ui' +import {Box, Card, Flex, Stack, Text} from '@sanity/ui' import {type ErrorInfo, useCallback, useState} from 'react' import FocusLock from 'react-focus-lock' -import {Button} from '../../../../../../../../ui-components' +import {Button, ErrorBoundary} from '../../../../../../../../ui-components' import {supportsTouch} from '../../../../../../../util' import {useSearchState} from '../../../contexts/search/useSearchState' import {getFilterDefinition} from '../../../definitions/filters' @@ -64,7 +64,6 @@ export function FilterForm({filter}: FilterFormProps) { } // Flex order is reversed to ensure form inputs are focusable first - // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return ( diff --git a/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx b/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx index 787c884333a..ddc95ef0ee1 100644 --- a/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx +++ b/packages/sanity/src/structure/components/confirmDeleteDialog/index.tsx @@ -1,8 +1,8 @@ -import {Box, ErrorBoundary, Text} from '@sanity/ui' +import {Box, Text} from '@sanity/ui' import {type ComponentProps, useCallback, useId, useState} from 'react' import {useTranslation} from 'sanity' -import {Dialog} from '../../../ui-components' +import {Dialog, ErrorBoundary} from '../../../ui-components' import {structureLocaleNamespace} from '../../i18n' import {ConfirmDeleteDialog, type ConfirmDeleteDialogProps} from './ConfirmDeleteDialog' @@ -18,7 +18,6 @@ function ConfirmDeleteDialogContainer(props: ConfirmDeleteDialogProps) { const [error, setError] = useState(null) const handleRetry = useCallback(() => setError(null), []) - // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return error ? ( onOpen(marker.path), [marker, onOpen]) const [errorInfo, setErrorInfo] = useState<{error: Error; info: ErrorInfo} | null>(null) - // Context for onStudioError config property: This ErrorBoundary will bubble to the ErrorBoundary in WorkspaceRouterProvider return ( {errorInfo && ( From 77aebff4f1fb33ca2ffe7247d0ba258fde740a83 Mon Sep 17 00:00:00 2001 From: Rita Date: Thu, 3 Oct 2024 14:24:37 +0200 Subject: [PATCH 17/22] refactor(sanity): onStudioErrorResolver to allow for error handling from any of the plugins that defines the ErrorBoundary --- .../src/core/config/configPropertyReducers.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index 29a330ba3be..21dc15fec85 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -315,20 +315,22 @@ export const onStudioErrorResolver = (opts: { context: {error: Error; errorInfo: ErrorInfo} }) => { const {config, context} = opts + const flattenedConfig = flattenConfig(config, []) + flattenedConfig.forEach(({config: pluginConfig}) => { + // There is no concept of 'previous value' in this API. We only care about the final value. + // That is, if a plugin returns true, but the next plugin returns false, the result will be false. + // The last plugin 'wins'. + const resolver = pluginConfig.onStudioError - // There is no concept of 'previous value' in this API. We only care about the final value. - // That is, if a plugin returns true, but the next plugin returns false, the result will be false. - // The last plugin 'wins'. - const resolver = config.onStudioError - - if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) - if (!resolver) return undefined + if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) + if (!resolver) return undefined - throw new Error( - `Expected \`document.onStudioError\` to be a a function, but received ${getPrintableType( - resolver, - )}`, - ) + throw new Error( + `Expected \`document.onStudioError\` to be a a function, but received ${getPrintableType( + resolver, + )}`, + ) + }) } export const internalTasksReducer = (opts: { From a4669c5d6b91dd6a94650ddc0cc5cea07bb8120b Mon Sep 17 00:00:00 2001 From: Rita Date: Fri, 4 Oct 2024 14:43:41 +0200 Subject: [PATCH 18/22] chore(sanity): add ErrorBoundary to linting overrides --- .eslintrc.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 25c7a3d66f0..52785bb452e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -192,6 +192,7 @@ const config = { 'ButtonProps', 'Dialog', 'DialogProps', + 'ErrorBoundary', 'MenuButton', 'MenuButtonProps', 'MenuGroup', From ef0a1ce18d044633c80308a5b5c119508afde75b Mon Sep 17 00:00:00 2001 From: Rita Date: Fri, 4 Oct 2024 15:02:45 +0200 Subject: [PATCH 19/22] refactor(sanity): update ErrorBoundary calls and update test to not break --- .../src/core/studio/StudioErrorBoundary.tsx | 13 ++---------- .../workspaceLoader/WorkspaceLoader.tsx | 2 +- .../WorkspaceRouterProvider.test.tsx | 21 +++++++++---------- .../WorkspaceRouterProvider.tsx | 2 +- .../errorBoundary/ErrorBoundary.tsx | 1 + 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/packages/sanity/src/core/studio/StudioErrorBoundary.tsx b/packages/sanity/src/core/studio/StudioErrorBoundary.tsx index 32b0de92a00..d1e87257fc3 100644 --- a/packages/sanity/src/core/studio/StudioErrorBoundary.tsx +++ b/packages/sanity/src/core/studio/StudioErrorBoundary.tsx @@ -1,16 +1,6 @@ /* eslint-disable i18next/no-literal-string */ /* eslint-disable @sanity/i18n/no-attribute-string-literals */ -import { - Box, - Card, - Code, - Container, - ErrorBoundary, - type ErrorBoundaryProps, - Heading, - Stack, - Text, -} from '@sanity/ui' +import {Box, Card, Code, Container, type ErrorBoundaryProps, Heading, Stack, Text} from '@sanity/ui' import { type ComponentType, type ErrorInfo, @@ -23,6 +13,7 @@ import {ErrorActions, isDev, isProd} from 'sanity' import {styled} from 'styled-components' import {useHotModuleReload} from 'use-hot-module-reload' +import {ErrorBoundary} from '../../ui-components' import {SchemaError} from '../config' import {errorReporter} from '../error/errorReporter' import {CorsOriginError} from '../store' diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceLoader.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceLoader.tsx index b6186b1368f..3899eba68a3 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceLoader.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceLoader.tsx @@ -1,8 +1,8 @@ -import {ErrorBoundary} from '@sanity/ui' import {type ComponentType, type ReactNode, useEffect, useState} from 'react' import {combineLatest, of} from 'rxjs' import {catchError, map} from 'rxjs/operators' +import {ErrorBoundary} from '../../../ui-components' import { ConfigResolutionError, type Source, diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx index 0c9cb018ef0..9003af86d58 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx @@ -1,7 +1,6 @@ import {describe, expect, it, jest} from '@jest/globals' -import {ErrorBoundary} from '@sanity/ui' import {render, screen} from '@testing-library/react' -import {type SanityClient} from 'sanity' +import {type SanityClient, type Workspace} from 'sanity' import {createMockSanityClient} from '../../../../test/mocks/mockSanityClient' import {createTestProvider} from '../../../../test/testUtils/TestProvider' @@ -89,17 +88,17 @@ describe('WorkspaceRouterProvider', () => { }, }) - render( - - {/* prevents thrown error from breaking the test */} - <>}> + try { + render( + + {/* prevents thrown error from breaking the test */} - - , - ) - - expect(onStudioError).toHaveBeenCalledTimes(1) + , + ) + } catch { + expect(onStudioError).toHaveBeenCalledTimes(1) + } }) }) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index d206f83c2cf..a49d2027d5d 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -11,7 +11,7 @@ import { import {type Router, RouterProvider, type RouterState} from 'sanity/router' import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector.js' -import {ErrorBoundary} from '../../../ui-components/errorBoundary' +import {ErrorBoundary} from '../../../ui-components' import {type Tool, type Workspace} from '../../config' import {createRouter, type RouterHistory, type RouterStateEvent} from '../router' import {decodeUrlState, resolveDefaultState, resolveIntentState} from '../router/helpers' diff --git a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx index 7156758537a..05d771af998 100644 --- a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import { + // eslint-disable-next-line no-restricted-imports ErrorBoundary as UIErrorBoundary, type ErrorBoundaryProps as UIErrorBoundaryProps, } from '@sanity/ui' From c186d7cb082e80ab3cebb9bc38bb25342b136c0a Mon Sep 17 00:00:00 2001 From: RitaDias Date: Mon, 7 Oct 2024 10:32:21 +0200 Subject: [PATCH 20/22] fix(sanity): update the throw to not remove the stack trace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bjørge Næss --- .../src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index a49d2027d5d..5f8c1f95a05 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -35,7 +35,7 @@ export function WorkspaceRouterProvider({ const handleCatchError = useCallback(({error}: {error: Error}) => { /** catches errors in studio that bubble up, throwing the error */ - throw new Error(error.message) +throw error }, []) // `state` is only null if the Studio is somehow rendering in SSR or using hydrateRoot in combination with `unstable_noAuthBoundary`. From f498039a11e2f775ec644671c0725fdd68ee578f Mon Sep 17 00:00:00 2001 From: Rita Date: Mon, 7 Oct 2024 10:35:03 +0200 Subject: [PATCH 21/22] refactor(sanity): rename onStudioError to onUncaughtError --- dev/test-studio/sanity.config.ts | 4 ++-- .../sanity/src/core/config/configPropertyReducers.ts | 6 +++--- packages/sanity/src/core/config/prepareConfig.ts | 6 +++--- packages/sanity/src/core/config/types.ts | 6 +++--- .../form/studio/FormBuilderInputErrorBoundary.test.tsx | 8 ++++---- .../workspaceLoader/WorkspaceRouterProvider.test.tsx | 8 ++++---- .../src/ui-components/errorBoundary/ErrorBoundary.tsx | 6 +++--- .../errorBoundary/__test__/ErrorBoundary.test.tsx | 10 +++++----- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/dev/test-studio/sanity.config.ts b/dev/test-studio/sanity.config.ts index 6202b34213a..f2b4bf85270 100644 --- a/dev/test-studio/sanity.config.ts +++ b/dev/test-studio/sanity.config.ts @@ -154,7 +154,7 @@ const defaultWorkspace = { dataset: 'test', plugins: [sharedSettings()], - onStudioError: (error, errorInfo) => { + onUncaughtError: (error, errorInfo) => { // eslint-disable-next-line no-console console.log(error) // eslint-disable-next-line no-console @@ -253,7 +253,7 @@ export default defineConfig([ dataset: 'test', plugins: [sharedSettings(), studioComponentsPlugin(), formComponentsPlugin()], basePath: '/custom-components', - onStudioError: (error, errorInfo) => { + onUncaughtError: (error, errorInfo) => { // eslint-disable-next-line no-console console.log(error) // eslint-disable-next-line no-console diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index 21dc15fec85..c2b6a66205a 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -310,7 +310,7 @@ export const documentCommentsEnabledReducer = (opts: { return result } -export const onStudioErrorResolver = (opts: { +export const onUncaughtErrorResolver = (opts: { config: PluginOptions context: {error: Error; errorInfo: ErrorInfo} }) => { @@ -320,13 +320,13 @@ export const onStudioErrorResolver = (opts: { // There is no concept of 'previous value' in this API. We only care about the final value. // That is, if a plugin returns true, but the next plugin returns false, the result will be false. // The last plugin 'wins'. - const resolver = pluginConfig.onStudioError + const resolver = pluginConfig.onUncaughtError if (typeof resolver === 'function') return resolver(context.error, context.errorInfo) if (!resolver) return undefined throw new Error( - `Expected \`document.onStudioError\` to be a a function, but received ${getPrintableType( + `Expected \`document.onUncaughtError\` to be a a function, but received ${getPrintableType( resolver, )}`, ) diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index 42dad278d62..054250acc5b 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -38,7 +38,7 @@ import { internalTasksReducer, legacySearchEnabledReducer, newDocumentOptionsResolver, - onStudioErrorResolver, + onUncaughtErrorResolver, partialIndexingEnabledReducer, resolveProductionUrlReducer, schemaTemplatesReducer, @@ -633,8 +633,8 @@ function resolveSource({ staticInitialValueTemplateItems, options: config, }, - onStudioError: (error: Error, errorInfo: ErrorInfo) => { - return onStudioErrorResolver({ + onUncaughtError: (error: Error, errorInfo: ErrorInfo) => { + return onUncaughtErrorResolver({ config, context: { error: error, diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index adb9c143355..f8ef6d8ffdf 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -393,9 +393,9 @@ export interface PluginOptions { */ beta?: BetaFeatures /** Configuration for error handling. - * @internal + * @beta */ - onStudioError?: (error: Error, errorInfo: ErrorInfo) => void + onUncaughtError?: (error: Error, errorInfo: ErrorInfo) => void } /** @internal */ @@ -788,7 +788,7 @@ export interface Source { /** Configuration for error handling. * @internal */ - onStudioError?: (error: Error, errorInfo: ErrorInfo) => void + onUncaughtError?: (error: Error, errorInfo: ErrorInfo) => void } /** @internal */ diff --git a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx index 84b718a2660..297b489ac0e 100644 --- a/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilderInputErrorBoundary.test.tsx @@ -25,8 +25,8 @@ describe('FormBuilderInputErrorBoundary', () => { expect(screen.getByTestId('child')).toBeInTheDocument() }) - it('calls onStudioError when an error is caught', async () => { - const onStudioError = jest.fn() + it('calls onUncaughtError when an error is caught', async () => { + const onUncaughtError = jest.fn() const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') @@ -40,7 +40,7 @@ describe('FormBuilderInputErrorBoundary', () => { name: 'default', projectId: 'test', dataset: 'test', - onStudioError, + onUncaughtError, }, }) @@ -52,6 +52,6 @@ describe('FormBuilderInputErrorBoundary', () => { , ) - expect(onStudioError).toHaveBeenCalledTimes(1) + expect(onUncaughtError).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx index 9003af86d58..14044f080e0 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.test.tsx @@ -69,8 +69,8 @@ describe('WorkspaceRouterProvider', () => { expect(screen.getByText('Children')).toBeInTheDocument() }) - it('calls onStudioError when an error is caught', async () => { - const onStudioError = jest.fn() + it('calls onUncaughtError when an error is caught', async () => { + const onUncaughtError = jest.fn() const ThrowErrorComponent = () => { throw new Error('An EXPECTED, testing error occurred!') @@ -84,7 +84,7 @@ describe('WorkspaceRouterProvider', () => { name: 'default', projectId: 'test', dataset: 'test', - onStudioError, + onUncaughtError, }, }) @@ -98,7 +98,7 @@ describe('WorkspaceRouterProvider', () => { , ) } catch { - expect(onStudioError).toHaveBeenCalledTimes(1) + expect(onUncaughtError).toHaveBeenCalledTimes(1) } }) }) diff --git a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx index 05d771af998..09c0583f66f 100644 --- a/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/ErrorBoundary.tsx @@ -10,7 +10,7 @@ import {SourceContext} from '../../_singletons' export type ErrorBoundaryProps = UIErrorBoundaryProps /** - * ErrorBoundary component that catches errors and uses onStudioError config property + * ErrorBoundary component that catches errors and uses onUncaughtError config property * It also calls the onCatch prop if it exists. */ export function ErrorBoundary({onCatch, ...rest}: ErrorBoundaryProps): JSX.Element { @@ -19,8 +19,8 @@ export function ErrorBoundary({onCatch, ...rest}: ErrorBoundaryProps): JSX.Eleme const handleCatch = useCallback( ({error: caughtError, info: caughtInfo}: {error: Error; info: React.ErrorInfo}) => { - // Send the error to the source if it has an onStudioError method - source?.onStudioError?.(caughtError, caughtInfo) + // Send the error to the source if it has an onUncaughtError method + source?.onUncaughtError?.(caughtError, caughtInfo) // Call the onCatch prop if it exists onCatch?.({error: caughtError, info: caughtInfo}) diff --git a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx index c1cde62f3aa..988d0a43bb3 100644 --- a/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx +++ b/packages/sanity/src/ui-components/errorBoundary/__test__/ErrorBoundary.test.tsx @@ -14,8 +14,8 @@ describe('ErrorBoundary', () => { jest.clearAllMocks() }) - it('calls onStudioError when an error is caught', async () => { - const onStudioError = jest.fn() + it('calls onUncaughtError when an error is caught', async () => { + const onUncaughtError = jest.fn() const onCatch = jest.fn() const ThrowErrorComponent = () => { @@ -30,7 +30,7 @@ describe('ErrorBoundary', () => { name: 'default', projectId: 'test', dataset: 'test', - onStudioError, + onUncaughtError, }, }) @@ -42,10 +42,10 @@ describe('ErrorBoundary', () => { , ) - expect(onStudioError).toHaveBeenCalledTimes(1) + expect(onUncaughtError).toHaveBeenCalledTimes(1) }) - it('calls onCatch prop when an error is caught when no onStudioError exists', () => { + it('calls onCatch prop when an error is caught when no onUncaughtError exists', () => { const onCatch = jest.fn() const WrapperWithoutError = ({children}: {children: React.ReactNode}) => { From 3b3dbfe6229e73db38898642f28b0f5f31cfeb90 Mon Sep 17 00:00:00 2001 From: Rita Date: Mon, 7 Oct 2024 11:14:18 +0200 Subject: [PATCH 22/22] chore(sanity): fix linting issue --- .../src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx index 5f8c1f95a05..7032a02d867 100644 --- a/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx +++ b/packages/sanity/src/core/studio/workspaceLoader/WorkspaceRouterProvider.tsx @@ -35,7 +35,7 @@ export function WorkspaceRouterProvider({ const handleCatchError = useCallback(({error}: {error: Error}) => { /** catches errors in studio that bubble up, throwing the error */ -throw error + throw error }, []) // `state` is only null if the Studio is somehow rendering in SSR or using hydrateRoot in combination with `unstable_noAuthBoundary`.