From 34089041147d44bf6fc56378580ba38f82b80957 Mon Sep 17 00:00:00 2001 From: Magnus Holm Date: Fri, 26 Sep 2025 13:09:33 +0200 Subject: [PATCH] test: removing mocking of useSanityInstance hook This is easily replaced by actually placing a real instance in the context. --- .../src/components/auth/AuthBoundary.test.tsx | 53 ++++--- .../src/context/ComlinkTokenRefresh.test.tsx | 93 +++++++----- .../auth/useDashboardOrganizationId.test.tsx | 23 ++- .../hooks/auth/useVerifyOrgProjects.test.tsx | 70 +++++++-- ...ite.test.ts => useManageFavorite.test.tsx} | 143 ++++++++++++------ ...eDocument.test.ts => useDocument.test.tsx} | 47 +++--- ...vent.test.ts => useDocumentEvent.test.tsx} | 33 ++-- ...est.ts => useDocumentPermissions.test.tsx} | 141 ++++++++++++----- ...ument.test.ts => useEditDocument.test.tsx} | 74 ++++++--- .../src/hooks/documents/useDocuments.test.tsx | 84 +++++++--- .../hooks/helpers/createCallbackHook.test.tsx | 78 +++++----- .../src/hooks/presence/usePresence.test.tsx | 15 +- .../hooks/preview/useDocumentPreview.test.tsx | 31 ++-- .../projection/useDocumentProjection.test.tsx | 39 +++-- .../react/src/hooks/query/useQuery.test.tsx | 28 ++-- .../hooks/releases/useActiveReleases.test.tsx | 46 +++--- .../hooks/releases/usePerspective.test.tsx | 38 ++--- .../react/src/hooks/users/useUser.test.tsx | 47 ++++-- .../react/src/hooks/users/useUsers.test.tsx | 30 ++-- 19 files changed, 707 insertions(+), 406 deletions(-) rename packages/react/src/hooks/dashboard/{useManageFavorite.test.ts => useManageFavorite.test.tsx} (71%) rename packages/react/src/hooks/document/{useDocument.test.ts => useDocument.test.tsx} (77%) rename packages/react/src/hooks/document/{useDocumentEvent.test.ts => useDocumentEvent.test.tsx} (72%) rename packages/react/src/hooks/document/{useDocumentPermissions.test.ts => useDocumentPermissions.test.tsx} (62%) rename packages/react/src/hooks/document/{useEditDocument.test.ts => useEditDocument.test.tsx} (81%) diff --git a/packages/react/src/components/auth/AuthBoundary.test.tsx b/packages/react/src/components/auth/AuthBoundary.test.tsx index d4e88df91..b6682512f 100644 --- a/packages/react/src/components/auth/AuthBoundary.test.tsx +++ b/packages/react/src/components/auth/AuthBoundary.test.tsx @@ -8,7 +8,6 @@ import {ResourceProvider} from '../../context/ResourceProvider' import {useAuthState} from '../../hooks/auth/useAuthState' import {useLoginUrl} from '../../hooks/auth/useLoginUrl' import {useVerifyOrgProjects} from '../../hooks/auth/useVerifyOrgProjects' -import {useSanityInstance} from '../../hooks/context/useSanityInstance' import {AuthBoundary} from './AuthBoundary' // Mock hooks @@ -23,9 +22,6 @@ vi.mock('../../hooks/auth/useHandleAuthCallback', () => ({ vi.mock('../../hooks/auth/useLogOut', () => ({ useLogOut: vi.fn(() => async () => {}), })) -vi.mock('../../hooks/context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) // Mock AuthError throwing scenario vi.mock('./AuthError', async (importOriginal) => { @@ -109,7 +105,6 @@ describe('AuthBoundary', () => { const mockUseAuthState = vi.mocked(useAuthState) const mockUseLoginUrl = vi.mocked(useLoginUrl) const mockUseVerifyOrgProjects = vi.mocked(useVerifyOrgProjects) - const mockUseSanityInstance = vi.mocked(useSanityInstance) const testProjectIds = ['proj-test'] // Example project ID for tests // Mock Sanity instance @@ -139,8 +134,6 @@ describe('AuthBoundary', () => { mockUseLoginUrl.mockReturnValue('http://example.com/login') // Default mock for useVerifyOrgProjects - returns null (no error) mockUseVerifyOrgProjects.mockImplementation(() => null) - // Mock useSanityInstance to return our mock instance - mockUseSanityInstance.mockReturnValue(mockSanityInstance) }) afterEach(() => { @@ -170,7 +163,9 @@ describe('AuthBoundary', () => { isExchangingToken: false, }) const {container} = render( - Protected Content, + + Protected Content + , ) // The callback screen renders null check that it renders nothing @@ -184,7 +179,11 @@ describe('AuthBoundary', () => { currentUser: null, token: 'exampleToken', }) - render(Protected Content) + render( + + Protected Content + , + ) expect(screen.getByText('Protected Content')).toBeInTheDocument() }) @@ -194,7 +193,11 @@ describe('AuthBoundary', () => { type: AuthStateType.ERROR, error: new Error('test error'), }) - render(Protected Content) + render( + + Protected Content + , + ) // The AuthBoundary should throw an AuthError internally // and then display the LoginError component as the fallback. @@ -207,7 +210,11 @@ describe('AuthBoundary', () => { }) it('renders children when logged in and org verification passes', () => { - render(Protected Content) + render( + + Protected Content + , + ) expect(screen.getByText('Protected Content')).toBeInTheDocument() }) @@ -226,9 +233,11 @@ describe('AuthBoundary', () => { // Need to catch the error thrown during render. ErrorBoundary mock handles this. render( - -
Protected Content
-
, + + +
Protected Content
+
+
, ) // The ErrorBoundary's FallbackComponent should be rendered @@ -256,9 +265,11 @@ describe('AuthBoundary', () => { }) render( - -
Protected Content
-
, + + +
Protected Content
+
+
, ) // Should render children because verification is disabled @@ -279,9 +290,11 @@ describe('AuthBoundary', () => { mockUseVerifyOrgProjects.mockImplementation(() => null) render( - -
Protected Content
-
, + + +
Protected Content
+
+
, ) await waitFor(() => { diff --git a/packages/react/src/context/ComlinkTokenRefresh.test.tsx b/packages/react/src/context/ComlinkTokenRefresh.test.tsx index e6e1316b6..d0b253a0b 100644 --- a/packages/react/src/context/ComlinkTokenRefresh.test.tsx +++ b/packages/react/src/context/ComlinkTokenRefresh.test.tsx @@ -5,8 +5,8 @@ import {afterEach, beforeEach, describe, expect, it, type Mock, vi} from 'vitest import {useAuthState} from '../hooks/auth/useAuthState' import {useWindowConnection} from '../hooks/comlink/useWindowConnection' -import {useSanityInstance} from '../hooks/context/useSanityInstance' import {ComlinkTokenRefreshProvider} from './ComlinkTokenRefresh' +import {ResourceProvider} from './ResourceProvider' // Mocks vi.mock('@sanity/sdk', async () => { @@ -26,20 +26,13 @@ vi.mock('../hooks/comlink/useWindowConnection', () => ({ useWindowConnection: vi.fn(), })) -vi.mock('../hooks/context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - // Use simpler mock typings const mockGetIsInDashboardState = getIsInDashboardState as Mock const mockSetAuthToken = setAuthToken as Mock const mockUseAuthState = useAuthState as Mock const mockUseWindowConnection = useWindowConnection as Mock -const mockUseSanityInstance = useSanityInstance as Mock const mockFetch = vi.fn() -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockSanityInstance: any = {projectId: 'test', dataset: 'test'} describe('ComlinkTokenRefresh', () => { beforeEach(() => { @@ -47,7 +40,6 @@ describe('ComlinkTokenRefresh', () => { mockGetIsInDashboardState.mockReturnValue({getCurrent: vi.fn(() => false)}) mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN}) mockUseWindowConnection.mockReturnValue({fetch: mockFetch}) - mockUseSanityInstance.mockReturnValue(mockSanityInstance) }) afterEach(() => { @@ -64,9 +56,11 @@ describe('ComlinkTokenRefresh', () => { it('should not request new token on 401 if not in dashboard', async () => { mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN}) const {rerender} = render( - -
Test
-
, + + +
Test
+
+
, ) mockUseAuthState.mockReturnValue({ @@ -75,9 +69,11 @@ describe('ComlinkTokenRefresh', () => { }) act(() => { rerender( - -
Test
-
, + + +
Test
+
+
, ) }) @@ -95,9 +91,11 @@ describe('ComlinkTokenRefresh', () => { it('should initialize useWindowConnection with correct parameters', () => { render( - -
Test
-
, + + +
Test
+
+
, ) expect(mockUseWindowConnection).toHaveBeenCalledWith( @@ -116,16 +114,18 @@ describe('ComlinkTokenRefresh', () => { mockFetch.mockResolvedValueOnce({token: 'new-token'}) render( - -
Test
-
, + + +
Test
+
+
, ) await act(async () => { await vi.advanceTimersByTimeAsync(100) }) - expect(mockSetAuthToken).toHaveBeenCalledWith(mockSanityInstance, 'new-token') + expect(mockSetAuthToken).toHaveBeenCalledWith(expect.any(Object), 'new-token') expect(mockFetch).toHaveBeenCalledTimes(1) }) @@ -137,9 +137,11 @@ describe('ComlinkTokenRefresh', () => { mockFetch.mockResolvedValueOnce({token: null}) render( - -
Test
-
, + + +
Test
+
+
, ) await act(async () => { @@ -157,9 +159,11 @@ describe('ComlinkTokenRefresh', () => { mockFetch.mockRejectedValueOnce(new Error('Fetch failed')) render( - -
Test
-
, + + +
Test
+
+
, ) await act(async () => { @@ -173,9 +177,12 @@ describe('ComlinkTokenRefresh', () => { it('should not request new token for non-401 errors', async () => { mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN}) const {rerender} = render( - -
Test
-
, + + +
Test
+
+ , +
, ) mockUseAuthState.mockReturnValue({ @@ -184,9 +191,11 @@ describe('ComlinkTokenRefresh', () => { }) act(() => { rerender( - -
Test
-
, + + +
Test
+
+
, ) }) @@ -199,17 +208,21 @@ describe('ComlinkTokenRefresh', () => { it('should request new token on LOGGED_OUT state', async () => { mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN}) const {rerender} = render( - -
Test
-
, + + +
Test
+
+
, ) mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_OUT}) act(() => { rerender( - -
Test
-
, + + +
Test
+
+
, ) }) diff --git a/packages/react/src/hooks/auth/useDashboardOrganizationId.test.tsx b/packages/react/src/hooks/auth/useDashboardOrganizationId.test.tsx index 9d5d01f48..174b580c1 100644 --- a/packages/react/src/hooks/auth/useDashboardOrganizationId.test.tsx +++ b/packages/react/src/hooks/auth/useDashboardOrganizationId.test.tsx @@ -1,14 +1,11 @@ -import {createSanityInstance, getDashboardOrganizationId} from '@sanity/sdk' +import {getDashboardOrganizationId} from '@sanity/sdk' import {renderHook} from '@testing-library/react' import {throwError} from 'rxjs' import {describe, expect, it, vi} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDashboardOrganizationId} from './useDashboardOrganizationId' -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn().mockReturnValue(createSanityInstance({projectId: 'p', dataset: 'd'})), -})) - vi.mock('@sanity/sdk', async (importOriginal) => { const actual = await importOriginal() return {...(actual || {}), getDashboardOrganizationId: vi.fn()} @@ -23,7 +20,13 @@ describe('useDashboardOrganizationId', () => { observable: throwError(() => new Error('Unexpected usage of observable')), }) - const {result} = renderHook(() => useDashboardOrganizationId()) + const {result} = renderHook(() => useDashboardOrganizationId(), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toBeUndefined() }) @@ -36,7 +39,13 @@ describe('useDashboardOrganizationId', () => { observable: throwError(() => new Error('Unexpected usage of observable')), }) - const {result} = renderHook(() => useDashboardOrganizationId()) + const {result} = renderHook(() => useDashboardOrganizationId(), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toBe(mockOrgId) }) }) diff --git a/packages/react/src/hooks/auth/useVerifyOrgProjects.test.tsx b/packages/react/src/hooks/auth/useVerifyOrgProjects.test.tsx index fdb2d472f..472254326 100644 --- a/packages/react/src/hooks/auth/useVerifyOrgProjects.test.tsx +++ b/packages/react/src/hooks/auth/useVerifyOrgProjects.test.tsx @@ -3,7 +3,7 @@ import {act, renderHook, waitFor} from '@testing-library/react' import {Subject} from 'rxjs' import {describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useVerifyOrgProjects} from './useVerifyOrgProjects' // Mock dependencies @@ -14,33 +14,46 @@ vi.mock('@sanity/sdk', async (importOriginal) => { observeOrganizationVerificationState: vi.fn(), } }) -vi.mock('../context/useSanityInstance') describe('useVerifyOrgProjects', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockInstance = {config: {}} as any // Dummy instance const mockObserve = vi.mocked(observeOrganizationVerificationState) - const mockUseInstance = vi.mocked(useSanityInstance) const testProjectIds = ['proj-1'] beforeEach(() => { vi.clearAllMocks() - mockUseInstance.mockReturnValue(mockInstance) }) it('should return null and not observe state if disabled', () => { - const {result} = renderHook(() => useVerifyOrgProjects(true, testProjectIds)) + const {result} = renderHook(() => useVerifyOrgProjects(true, testProjectIds), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toBeNull() expect(mockObserve).not.toHaveBeenCalled() }) it('should return null and not observe state if projectIds is missing or empty', () => { - const {result: resultUndefined} = renderHook(() => useVerifyOrgProjects(false, undefined)) + const {result: resultUndefined} = renderHook(() => useVerifyOrgProjects(false, undefined), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(resultUndefined.current).toBeNull() expect(mockObserve).not.toHaveBeenCalled() - const {result: resultEmpty} = renderHook(() => useVerifyOrgProjects(false, [])) + const {result: resultEmpty} = renderHook(() => useVerifyOrgProjects(false, []), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(resultEmpty.current).toBeNull() expect(mockObserve).not.toHaveBeenCalled() }) @@ -49,17 +62,29 @@ describe('useVerifyOrgProjects', () => { const subject = new Subject() mockObserve.mockReturnValue(subject.asObservable()) - const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds)) + const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toBeNull() - expect(mockObserve).toHaveBeenCalledWith(mockInstance, testProjectIds) + expect(mockObserve).toHaveBeenCalledWith(expect.any(Object), testProjectIds) }) it('should return null if observable emits { error: null }', async () => { const subject = new Subject() mockObserve.mockReturnValue(subject.asObservable()) - const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds)) + const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), { + wrapper: ({children}) => ( + + {children} + + ), + }) act(() => { subject.next({error: null}) @@ -75,7 +100,13 @@ describe('useVerifyOrgProjects', () => { const errorMessage = 'Org mismatch' mockObserve.mockReturnValue(subject.asObservable()) - const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds)) + const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), { + wrapper: ({children}) => ( + + {children} + + ), + }) act(() => { subject.next({error: errorMessage}) @@ -91,7 +122,13 @@ describe('useVerifyOrgProjects', () => { const unsubscribeSpy = vi.spyOn(subject, 'unsubscribe') mockObserve.mockReturnValue(subject) - const {unmount} = renderHook(() => useVerifyOrgProjects(false, testProjectIds)) + const {unmount} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(unsubscribeSpy).not.toHaveBeenCalled() unmount() @@ -114,6 +151,11 @@ describe('useVerifyOrgProjects', () => { ({disabled, pIds}) => useVerifyOrgProjects(disabled, pIds), { initialProps: {disabled: false, pIds: testProjectIds}, + wrapper: ({children}) => ( + + {children} + + ), }, ) diff --git a/packages/react/src/hooks/dashboard/useManageFavorite.test.ts b/packages/react/src/hooks/dashboard/useManageFavorite.test.tsx similarity index 71% rename from packages/react/src/hooks/dashboard/useManageFavorite.test.ts rename to packages/react/src/hooks/dashboard/useManageFavorite.test.tsx index 3ce70b1b9..f346a34c7 100644 --- a/packages/react/src/hooks/dashboard/useManageFavorite.test.ts +++ b/packages/react/src/hooks/dashboard/useManageFavorite.test.tsx @@ -1,16 +1,11 @@ import {type Message} from '@sanity/comlink' -import { - type FavoriteStatusResponse, - getFavoritesState, - resolveFavoritesState, - type SanityInstance, -} from '@sanity/sdk' +import {type FavoriteStatusResponse, getFavoritesState, resolveFavoritesState} from '@sanity/sdk' +import {act, renderHook} from '@testing-library/react' import {BehaviorSubject} from 'rxjs' import {beforeEach, describe, expect, it, vi} from 'vitest' -import {act, renderHook} from '../../../test/test-utils' +import {ResourceProvider} from '../../context/ResourceProvider' import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection' -import {useSanityInstance} from '../context/useSanityInstance' import {useManageFavorite} from './useManageFavorite' vi.mock(import('@sanity/sdk'), async (importOriginal) => { @@ -22,8 +17,6 @@ vi.mock(import('@sanity/sdk'), async (importOriginal) => { } }) -vi.mock('../context/useSanityInstance') - vi.mock('../comlink/useWindowConnection', () => ({ useWindowConnection: vi.fn(), })) @@ -62,14 +55,6 @@ describe('useManageFavorite', () => { return newValue }) - // Default mock for useSanityInstance - vi.mocked(useSanityInstance).mockReturnValue({ - config: { - projectId: 'test', - dataset: 'test', - }, - } as unknown as SanityInstance) - // Mock useWindowConnection mockFetch = vi.fn().mockResolvedValue({success: true}) mockSendMessage = vi.fn() @@ -88,13 +73,25 @@ describe('useManageFavorite', () => { }) it('should initialize with default states', () => { - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.isFavorited).toBe(false) }) it('should handle favorite action and update state', async () => { - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.isFavorited).toBe(false) @@ -123,7 +120,13 @@ describe('useManageFavorite', () => { }) it('should handle unfavorite action and update state', async () => { - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) // Set initial state to favorited await act(async () => { @@ -158,7 +161,13 @@ describe('useManageFavorite', () => { it('should not update state if favorite action fails', async () => { mockFetch.mockResolvedValueOnce({success: false}) - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.isFavorited).toBe(false) @@ -178,7 +187,13 @@ describe('useManageFavorite', () => { throw new Error(errorMessage) }) - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await expect(result.current.favorite()).rejects.toThrow(errorMessage) @@ -196,23 +211,21 @@ describe('useManageFavorite', () => { }) it('should throw error when studio resource is missing projectId or dataset', () => { - // Mock the Sanity instance to not have projectId or dataset - vi.mocked(useSanityInstance).mockReturnValue({ - config: { - projectId: undefined, - dataset: undefined, - }, - } as unknown as SanityInstance) - const mockDocumentHandleWithoutProjectId = { documentId: 'mock-id', documentType: 'mock-type', resourceType: 'studio' as const, } - expect(() => renderHook(() => useManageFavorite(mockDocumentHandleWithoutProjectId))).toThrow( - 'projectId and dataset are required for studio resources', - ) + expect(() => + renderHook(() => useManageFavorite(mockDocumentHandleWithoutProjectId), { + wrapper: ({children}) => ( + + {children} + + ), + }), + ).toThrow('projectId and dataset are required for studio resources') }) it('should throw error when resourceId is missing for non-studio resources', () => { @@ -223,9 +236,15 @@ describe('useManageFavorite', () => { resourceId: undefined, } - expect(() => renderHook(() => useManageFavorite(mockMediaDocumentHandle))).toThrow( - 'resourceId is required for media-library and canvas resources', - ) + expect(() => + renderHook(() => useManageFavorite(mockMediaDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }), + ).toThrow('resourceId is required for media-library and canvas resources') }) it('should include schemaName in payload when provided', async () => { @@ -233,7 +252,13 @@ describe('useManageFavorite', () => { ...mockDocumentHandle, schemaName: 'testSchema', } - const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await result.current.favorite() @@ -246,7 +271,7 @@ describe('useManageFavorite', () => { id: 'mock-id', type: 'mock-type', resource: { - id: 'test.test', + id: 'test-project.test-dataset', type: 'studio', schemaName: 'testSchema', }, @@ -268,7 +293,13 @@ describe('useManageFavorite', () => { getCurrent: () => undefined, observable: favoriteStatusSubject.asObservable(), })) - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.isFavorited).toBe(false) }) @@ -277,7 +308,13 @@ describe('useManageFavorite', () => { fetch: undefined, sendMessage: mockSendMessage, } as unknown as WindowConnection) - const {result} = renderHook(() => useManageFavorite(mockDocumentHandle)) + const {result} = renderHook(() => useManageFavorite(mockDocumentHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await result.current.favorite() await result.current.unfavorite() @@ -288,7 +325,13 @@ describe('useManageFavorite', () => { it('should do nothing if documentId is missing', async () => { const handle = {...mockDocumentHandle, documentId: undefined} // @ts-expect-error -- no access to ManageFavorite props type - const {result} = renderHook(() => useManageFavorite(handle)) + const {result} = renderHook(() => useManageFavorite(handle), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await result.current.favorite() await result.current.unfavorite() @@ -299,7 +342,13 @@ describe('useManageFavorite', () => { it('should do nothing if documentType is missing', async () => { const handle = {...mockDocumentHandle, documentType: undefined} // @ts-expect-error -- no access to ManageFavorite props type - const {result} = renderHook(() => useManageFavorite(handle)) + const {result} = renderHook(() => useManageFavorite(handle), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await result.current.favorite() await result.current.unfavorite() @@ -310,7 +359,13 @@ describe('useManageFavorite', () => { it('should do nothing if resourceType is missing', async () => { const handle = {...mockDocumentHandle, resourceType: undefined, resourceId: 'studio'} // @ts-expect-error -- no access to ManageFavorite props type - const {result} = renderHook(() => useManageFavorite(handle)) + const {result} = renderHook(() => useManageFavorite(handle), { + wrapper: ({children}) => ( + + {children} + + ), + }) await act(async () => { await result.current.favorite() await result.current.unfavorite() diff --git a/packages/react/src/hooks/document/useDocument.test.ts b/packages/react/src/hooks/document/useDocument.test.tsx similarity index 77% rename from packages/react/src/hooks/document/useDocument.test.ts rename to packages/react/src/hooks/document/useDocument.test.tsx index e7ba6c910..7160c50d7 100644 --- a/packages/react/src/hooks/document/useDocument.test.ts +++ b/packages/react/src/hooks/document/useDocument.test.tsx @@ -1,16 +1,11 @@ // tests/useDocument.test.ts -import { - createSanityInstance, - getDocumentState, - resolveDocument, - type StateSource, -} from '@sanity/sdk' +import {getDocumentState, resolveDocument, type StateSource} from '@sanity/sdk' import {type SanityDocument} from '@sanity/types' import {renderHook} from '@testing-library/react' import {type SchemaOrigin} from 'groq' import {beforeEach, describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDocument} from './useDocument' vi.mock('@sanity/sdk', async (importOriginal) => { @@ -18,10 +13,6 @@ vi.mock('@sanity/sdk', async (importOriginal) => { return {...original, getDocumentState: vi.fn(), resolveDocument: vi.fn()} }) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - // Define a single generic TestDocument type type UseDocumentTestType = SchemaOrigin< SanityDocument & { @@ -56,8 +47,6 @@ declare module 'groq' { } } -// Create a fake instance to be returned by useSanityInstance. -const instance = createSanityInstance({projectId: 'p', dataset: 'd'}) const book: SanityDocument = { _id: 'doc1', foo: 'bar', @@ -70,7 +59,6 @@ const book: SanityDocument = { describe('useDocument hook', () => { beforeEach(() => { vi.resetAllMocks() - vi.mocked(useSanityInstance).mockReturnValue(instance) }) it('returns the current document when ready (without a path)', () => { @@ -81,7 +69,13 @@ describe('useDocument hook', () => { subscribe, } as unknown as StateSource) - const {result} = renderHook(() => useDocument({documentId: 'doc1', documentType: 'book'})) + const {result} = renderHook(() => useDocument({documentId: 'doc1', documentType: 'book'}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.data).toEqual(book) expect(getCurrent).toHaveBeenCalled() @@ -102,13 +96,22 @@ describe('useDocument hook', () => { vi.mocked(resolveDocument).mockReturnValue(resolveDocPromise) // Render the hook and capture the thrown promise. - const {result} = renderHook(() => { - try { - return useDocument({documentId: 'doc1', documentType: 'book'}) - } catch (e) { - return e - } - }) + const {result} = renderHook( + () => { + try { + return useDocument({documentId: 'doc1', documentType: 'book'}) + } catch (e) { + return e + } + }, + { + wrapper: ({children}) => ( + + {children} + + ), + }, + ) // When the document is not ready, the hook throws the promise from resolveDocument. expect(result.current).toBe(resolveDocPromise) diff --git a/packages/react/src/hooks/document/useDocumentEvent.test.ts b/packages/react/src/hooks/document/useDocumentEvent.test.tsx similarity index 72% rename from packages/react/src/hooks/document/useDocumentEvent.test.ts rename to packages/react/src/hooks/document/useDocumentEvent.test.tsx index e933b9a53..e0848b526 100644 --- a/packages/react/src/hooks/document/useDocumentEvent.test.ts +++ b/packages/react/src/hooks/document/useDocumentEvent.test.tsx @@ -1,14 +1,9 @@ // tests/useDocumentEvent.test.ts -import { - createSanityInstance, - type DocumentEvent, - type DocumentHandle, - subscribeDocumentEvents, -} from '@sanity/sdk' +import {type DocumentEvent, type DocumentHandle, subscribeDocumentEvents} from '@sanity/sdk' import {renderHook} from '@testing-library/react' import {beforeEach, describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDocumentEvent} from './useDocumentEvent' vi.mock('@sanity/sdk', async (importOriginal) => { @@ -16,11 +11,6 @@ vi.mock('@sanity/sdk', async (importOriginal) => { return {...original, subscribeDocumentEvents: vi.fn()} }) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - -const instance = createSanityInstance({projectId: 'p', dataset: 'd'}) const docHandle: DocumentHandle = { documentId: 'doc1', documentType: 'book', @@ -29,7 +19,6 @@ const docHandle: DocumentHandle = { describe('useDocumentEvent hook', () => { beforeEach(() => { vi.resetAllMocks() - vi.mocked(useSanityInstance).mockReturnValue(instance) }) it('calls subscribeDocumentEvents with instance and a stable handler', () => { @@ -37,10 +26,16 @@ describe('useDocumentEvent hook', () => { const unsubscribe = vi.fn() vi.mocked(subscribeDocumentEvents).mockReturnValue(unsubscribe) - renderHook(() => useDocumentEvent({...docHandle, onEvent: handleEvent})) + renderHook(() => useDocumentEvent({...docHandle, onEvent: handleEvent}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(vi.mocked(subscribeDocumentEvents)).toHaveBeenCalledTimes(1) - expect(vi.mocked(subscribeDocumentEvents).mock.calls[0][0]).toBe(instance) + expect(vi.mocked(subscribeDocumentEvents).mock.calls[0][0]).toEqual(expect.any(Object)) const stableHandler = vi.mocked(subscribeDocumentEvents).mock.calls[0][1] expect(typeof stableHandler).toBe('function') @@ -55,7 +50,13 @@ describe('useDocumentEvent hook', () => { const unsubscribe = vi.fn() vi.mocked(subscribeDocumentEvents).mockReturnValue(unsubscribe) - const {unmount} = renderHook(() => useDocumentEvent({...docHandle, onEvent: handleEvent})) + const {unmount} = renderHook(() => useDocumentEvent({...docHandle, onEvent: handleEvent}), { + wrapper: ({children}) => ( + + {children} + + ), + }) unmount() expect(unsubscribe).toHaveBeenCalledTimes(1) }) diff --git a/packages/react/src/hooks/document/useDocumentPermissions.test.ts b/packages/react/src/hooks/document/useDocumentPermissions.test.tsx similarity index 62% rename from packages/react/src/hooks/document/useDocumentPermissions.test.ts rename to packages/react/src/hooks/document/useDocumentPermissions.test.tsx index 380404011..72a1928e0 100644 --- a/packages/react/src/hooks/document/useDocumentPermissions.test.ts +++ b/packages/react/src/hooks/document/useDocumentPermissions.test.tsx @@ -1,24 +1,18 @@ -import { - type DocumentAction, - type DocumentPermissionsResult, - getPermissionsState, - type SanityInstance, -} from '@sanity/sdk' +import {type DocumentAction, type DocumentPermissionsResult, getPermissionsState} from '@sanity/sdk' import {act, renderHook, waitFor} from '@testing-library/react' import {BehaviorSubject, firstValueFrom} from 'rxjs' import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDocumentPermissions} from './useDocumentPermissions' -// Mock dependencies before any imports -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - -vi.mock('@sanity/sdk', () => ({ - getPermissionsState: vi.fn(), -})) +vi.mock('@sanity/sdk', async (importActual) => { + const actual = await importActual() + return { + ...actual, + getPermissionsState: vi.fn(), + } +}) // Move this mock to the top level vi.mock('rxjs', async (importOriginal) => { @@ -30,7 +24,6 @@ vi.mock('rxjs', async (importOriginal) => { }) describe('usePermissions', () => { - const mockInstance = {id: 'mock-instance'} as unknown as SanityInstance const mockAction: DocumentAction = { type: 'document.publish', documentId: 'doc1', @@ -59,8 +52,6 @@ describe('usePermissions', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - // Create a subject to simulate permissions state updates permissionsSubject = new BehaviorSubject( mockPermissionAllowed, @@ -93,13 +84,20 @@ describe('usePermissions', () => { permissionsSubject.next(mockPermissionAllowed) }) - const {result} = renderHook(() => useDocumentPermissions(mockAction)) - - expect(useSanityInstance).toHaveBeenCalledWith({ - projectId: mockAction.projectId, - dataset: mockAction.dataset, + const {result} = renderHook(() => useDocumentPermissions(mockAction), { + wrapper: ({children}) => ( + + {children} + + ), }) - expect(getPermissionsState).toHaveBeenCalledWith(mockInstance, mockAction) + + // ResourceProvider handles the instance configuration + expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), mockAction) expect(result.current).toEqual(mockPermissionAllowed) }) @@ -109,7 +107,17 @@ describe('usePermissions', () => { permissionsSubject.next(mockPermissionDenied) }) - const {result} = renderHook(() => useDocumentPermissions(mockAction)) + const {result} = renderHook(() => useDocumentPermissions(mockAction), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toEqual(mockPermissionDenied) expect(result.current.allowed).toBe(false) @@ -120,9 +128,19 @@ describe('usePermissions', () => { it('should accept an array of actions', () => { const actions = [mockAction, {...mockAction, documentId: 'doc2'}] - renderHook(() => useDocumentPermissions(actions)) + renderHook(() => useDocumentPermissions(actions), { + wrapper: ({children}) => ( + + {children} + + ), + }) - expect(getPermissionsState).toHaveBeenCalledWith(mockInstance, actions) + expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), actions) }) it('should throw an error if actions have mismatched project IDs', () => { @@ -132,7 +150,17 @@ describe('usePermissions', () => { ] expect(() => { - renderHook(() => useDocumentPermissions(actions)) + renderHook(() => useDocumentPermissions(actions), { + wrapper: ({children}) => ( + + {children} + + ), + }) }).toThrow(/Mismatched project IDs found in actions/) }) @@ -140,7 +168,17 @@ describe('usePermissions', () => { const actions = [mockAction, {...mockAction, dataset: 'different-dataset', documentId: 'doc2'}] expect(() => { - renderHook(() => useDocumentPermissions(actions)) + renderHook(() => useDocumentPermissions(actions), { + wrapper: ({children}) => ( + + {children} + + ), + }) }).toThrow(/Mismatched datasets found in actions/) }) @@ -155,16 +193,29 @@ describe('usePermissions', () => { vi.mocked(firstValueFrom).mockReturnValueOnce(mockPromise) // This should throw the promise and suspend - const {result} = renderHook(() => { - try { - return useDocumentPermissions(mockAction) - } catch (error) { - if (error instanceof Promise) { - return 'suspended' + const {result} = renderHook( + () => { + try { + return useDocumentPermissions(mockAction) + } catch (error) { + if (error instanceof Promise) { + return 'suspended' + } + throw error } - throw error - } - }) + }, + { + wrapper: ({children}) => ( + + {children} + + ), + }, + ) expect(result.current).toBe('suspended') @@ -175,7 +226,7 @@ describe('usePermissions', () => { // Now it should render properly await waitFor(() => { - expect(getPermissionsState).toHaveBeenCalledWith(mockInstance, mockAction) + expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), mockAction) }) }) @@ -185,7 +236,17 @@ describe('usePermissions', () => { permissionsSubject.next(mockPermissionAllowed) }) - const {result, rerender} = renderHook(() => useDocumentPermissions(mockAction)) + const {result, rerender} = renderHook(() => useDocumentPermissions(mockAction), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current).toEqual(mockPermissionAllowed) diff --git a/packages/react/src/hooks/document/useEditDocument.test.ts b/packages/react/src/hooks/document/useEditDocument.test.tsx similarity index 81% rename from packages/react/src/hooks/document/useEditDocument.test.ts rename to packages/react/src/hooks/document/useEditDocument.test.tsx index d042c321c..24e605a30 100644 --- a/packages/react/src/hooks/document/useEditDocument.test.ts +++ b/packages/react/src/hooks/document/useEditDocument.test.tsx @@ -1,7 +1,6 @@ // tests/useEditDocument.test.ts import { createDocumentHandle, - createSanityInstance, editDocument, getDocumentState, resolveDocument, @@ -11,7 +10,7 @@ import {type SanityDocument} from '@sanity/types' import {renderHook} from '@testing-library/react' import {beforeEach, describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useApplyDocumentActions} from './useApplyDocumentActions' import {useEditDocument} from './useEditDocument' @@ -25,17 +24,10 @@ vi.mock('@sanity/sdk', async (importOriginal) => { } }) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - vi.mock('./useApplyDocumentActions', () => ({ useApplyDocumentActions: vi.fn(), })) -// Create a fake instance to be returned by useSanityInstance. -const instance = createSanityInstance({projectId: 'p', dataset: 'd'}) - const doc = { _id: 'doc1', foo: 'bar', @@ -71,7 +63,6 @@ declare module 'groq' { describe('useEditDocument hook', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(useSanityInstance).mockReturnValue(instance) }) it('applies a single edit action for the given path', async () => { @@ -85,7 +76,13 @@ describe('useEditDocument hook', () => { const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'}) vi.mocked(useApplyDocumentActions).mockReturnValue(apply) - const {result} = renderHook(() => useEditDocument({...docHandle, path: 'foo'})) + const {result} = renderHook(() => useEditDocument({...docHandle, path: 'foo'}), { + wrapper: ({children}) => ( + + {children} + + ), + }) const promise = result.current('newValue') expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'newValue'}}) expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'newValue'}})) @@ -106,7 +103,13 @@ describe('useEditDocument hook', () => { const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'}) vi.mocked(useApplyDocumentActions).mockReturnValue(apply) - const {result} = renderHook(() => useEditDocument(docHandle)) + const {result} = renderHook(() => useEditDocument(docHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) const promise = result.current({...doc, foo: 'baz', extra: 'old', _id: 'doc1'}) expect(apply).toHaveBeenCalledWith([editDocument(docHandle, {set: {foo: 'baz'}})]) const actionsResult = await promise @@ -124,7 +127,13 @@ describe('useEditDocument hook', () => { const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'}) vi.mocked(useApplyDocumentActions).mockReturnValue(apply) - const {result} = renderHook(() => useEditDocument({...docHandle, path: 'foo'})) + const {result} = renderHook(() => useEditDocument({...docHandle, path: 'foo'}), { + wrapper: ({children}) => ( + + {children} + + ), + }) const promise = result.current((prev: unknown) => `${prev}Updated`) // 'bar' becomes 'barUpdated' expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'barUpdated'}}) expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'barUpdated'}})) @@ -144,7 +153,13 @@ describe('useEditDocument hook', () => { const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'}) vi.mocked(useApplyDocumentActions).mockReturnValue(apply) - const {result} = renderHook(() => useEditDocument(docHandle)) + const {result} = renderHook(() => useEditDocument(docHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) const promise = result.current((prevDoc) => ({...prevDoc, foo: 'baz'})) expect(apply).toHaveBeenCalledWith([editDocument(docHandle, {set: {foo: 'baz'}})]) const actionsResult = await promise @@ -162,7 +177,13 @@ describe('useEditDocument hook', () => { const fakeApply = vi.fn() vi.mocked(useApplyDocumentActions).mockReturnValue(fakeApply) - const {result} = renderHook(() => useEditDocument(docHandle)) + const {result} = renderHook(() => useEditDocument(docHandle), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(() => result.current('notAnObject' as unknown as Book)).toThrowError( 'No path was provided to `useEditDocument` and the value provided was not a document object.', ) @@ -182,13 +203,22 @@ describe('useEditDocument hook', () => { vi.mocked(resolveDocument).mockReturnValue(resolveDocPromise) // Render the hook and capture the thrown promise. - const {result} = renderHook(() => { - try { - return useEditDocument(docHandle) - } catch (e) { - return e - } - }) + const {result} = renderHook( + () => { + try { + return useEditDocument(docHandle) + } catch (e) { + return e + } + }, + { + wrapper: ({children}) => ( + + {children} + + ), + }, + ) // When the document is not ready, the hook throws the promise from resolveDocument. expect(result.current).toBe(resolveDocPromise) diff --git a/packages/react/src/hooks/documents/useDocuments.test.tsx b/packages/react/src/hooks/documents/useDocuments.test.tsx index 73e51972d..7b6167dff 100644 --- a/packages/react/src/hooks/documents/useDocuments.test.tsx +++ b/packages/react/src/hooks/documents/useDocuments.test.tsx @@ -1,14 +1,12 @@ -import {type SanityInstance} from '@sanity/sdk' import {act, renderHook} from '@testing-library/react' import {evaluateSync, parse, toJS} from 'groq-js' import {describe, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useQuery} from '../query/useQuery' import {useDocuments} from './useDocuments' vi.mock('../query/useQuery') -vi.mock('../context/useSanityInstance') describe('useDocuments', () => { beforeEach(() => { @@ -75,18 +73,29 @@ describe('useDocuments', () => { isPending: false, } }) - vi.mocked(useSanityInstance).mockReturnValue({config: {}} as SanityInstance) }) it('should respect custom page size', () => { const customBatchSize = 2 - const {result} = renderHook(() => useDocuments({batchSize: customBatchSize})) + const {result} = renderHook(() => useDocuments({batchSize: customBatchSize}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.data.length).toBe(customBatchSize) }) it('should filter by document type', () => { - const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'})) + const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.data.every((doc) => doc.documentType === 'movie')).toBe(true) expect(result.current.count).toBe(5) // 5 movies in the dataset @@ -94,18 +103,32 @@ describe('useDocuments', () => { // groq-js doesn't support search filters yet it.skip('should apply search filter', () => { - const {result} = renderHook(() => useDocuments({search: 'inter'})) + const {result} = renderHook(() => useDocuments({search: 'inter'}), { + wrapper: ({children}) => ( + + {children} + + ), + }) // Should match "Interstellar" expect(result.current.data.some((doc) => doc.documentId === 'movie3')).toBe(true) }) it('should apply ordering', () => { - const {result} = renderHook(() => - useDocuments({ - filter: '_type == "movie"', - orderings: [{field: 'releaseYear', direction: 'desc'}], - }), + const {result} = renderHook( + () => + useDocuments({ + filter: '_type == "movie"', + orderings: [{field: 'releaseYear', direction: 'desc'}], + }), + { + wrapper: ({children}) => ( + + {children} + + ), + }, ) // First item should be the most recent movie (Interstellar, 2014) @@ -114,7 +137,13 @@ describe('useDocuments', () => { it('should load more data when loadMore is called', () => { const batchSize = 2 - const {result} = renderHook(() => useDocuments({batchSize: batchSize})) + const {result} = renderHook(() => useDocuments({batchSize: batchSize}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.data.length).toBe(batchSize) @@ -126,7 +155,13 @@ describe('useDocuments', () => { }) it('should indicate when there is more data to load', () => { - const {result} = renderHook(() => useDocuments({batchSize: 3})) + const {result} = renderHook(() => useDocuments({batchSize: 3}), { + wrapper: ({children}) => ( + + {children} + + ), + }) expect(result.current.hasMore).toBe(true) // Load all remaining data act(() => { @@ -139,6 +174,11 @@ describe('useDocuments', () => { it('should reset limit when filter changes', () => { const {result, rerender} = renderHook((props) => useDocuments(props), { initialProps: {batchSize: 2, filter: ''}, + wrapper: ({children}) => ( + + {children} + + ), }) // Initially, data length equals pageSize (2) expect(result.current.data.length).toBe(2) @@ -155,15 +195,13 @@ describe('useDocuments', () => { }) it('should add projectId and dataset to document handles', () => { - // Update the mock to include specific projectId and dataset - vi.mocked(useSanityInstance).mockReturnValue({ - config: { - projectId: 'test-project', - dataset: 'test-dataset', - }, - } as SanityInstance) - - const {result} = renderHook(() => useDocuments({})) + const {result} = renderHook(() => useDocuments({}), { + wrapper: ({children}) => ( + + {children} + + ), + }) // Check that the first document handle has the projectId and dataset expect(result.current.data[0].projectId).toBe('test-project') diff --git a/packages/react/src/hooks/helpers/createCallbackHook.test.tsx b/packages/react/src/hooks/helpers/createCallbackHook.test.tsx index b49271feb..ef02dcb1f 100644 --- a/packages/react/src/hooks/helpers/createCallbackHook.test.tsx +++ b/packages/react/src/hooks/helpers/createCallbackHook.test.tsx @@ -1,15 +1,10 @@ -import {createSanityInstance, type SanityInstance} from '@sanity/sdk' +import {type SanityInstance} from '@sanity/sdk' import {renderHook} from '@testing-library/react' import {describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {createCallbackHook} from './createCallbackHook' -// Mock the useSanityInstance hook -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - describe('createCallbackHook', () => { // Reset all mocks before each test beforeEach(() => { @@ -17,12 +12,6 @@ describe('createCallbackHook', () => { }) it('should create a hook that provides a memoized callback', () => { - // Create a mock Sanity instance - const mockInstance = createSanityInstance({projectId: 'p', dataset: 'd'}) - - // Mock the useSanityInstance to return our mock instance - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - // Create a test callback function const testCallback = (instance: object, param1: string, param2: number) => { return `${param1}-${param2}-${instance ? 'valid' : 'invalid'}` @@ -32,7 +21,13 @@ describe('createCallbackHook', () => { const useTestHook = createCallbackHook(testCallback) // Render the hook - const {result, rerender} = renderHook(() => useTestHook()) + const {result, rerender} = renderHook(() => useTestHook(), { + wrapper: ({children}) => ( + + {children} + + ), + }) // Test the callback with parameters const result1 = result.current('test', 123) @@ -48,38 +43,41 @@ describe('createCallbackHook', () => { }) it('should create new callback when instance changes', () => { - // Create two different mock instances - const mockInstance1 = createSanityInstance({projectId: 'p1', dataset: 'd'}) - const mockInstance2 = createSanityInstance({projectId: 'p2', dataset: 'd'}) - - vi.mocked(useSanityInstance).mockReturnValueOnce(mockInstance1) - // Create a test callback const testCallback = (instance: SanityInstance) => instance.config.projectId - // Create and render our hook + // Create and render our hook with first provider const useTestHook = createCallbackHook(testCallback) - const {result, rerender} = renderHook(() => useTestHook()) + const {result, unmount} = renderHook(() => useTestHook(), { + wrapper: ({children}) => ( + + {children} + + ), + }) - // Store the first callback reference + // Store the first callback reference and result const firstCallback = result.current + const firstResult = firstCallback() + expect(firstResult).toBe('p1') + + unmount() + + // Re-render with different provider configuration + const {result: result2} = renderHook(() => useTestHook(), { + wrapper: ({children}) => ( + + {children} + + ), + }) - // Change the instance - vi.mocked(useSanityInstance).mockReturnValueOnce(mockInstance2) - rerender() - - // Verify the callback reference changed - expect(result.current).not.toBe(firstCallback) - - // Verify the callbacks return different results - expect(firstCallback()).toBe('p1') - expect(result.current()).toBe('p2') + // Verify the callback reference changed and returns different result + expect(result2.current).not.toBe(firstCallback) + expect(result2.current()).toBe('p2') }) it('should handle callbacks with multiple parameters', () => { - const mockInstance = createSanityInstance({projectId: 'p', dataset: 'd'}) - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - // Create a callback with multiple parameters const testCallback = ( instance: SanityInstance, @@ -93,7 +91,13 @@ describe('createCallbackHook', () => { }) const useTestHook = createCallbackHook(testCallback) - const {result} = renderHook(() => useTestHook()) + const {result} = renderHook(() => useTestHook(), { + wrapper: ({children}) => ( + + {children} + + ), + }) const response = result.current('/users', 'POST', {name: 'Test User'}) diff --git a/packages/react/src/hooks/presence/usePresence.test.tsx b/packages/react/src/hooks/presence/usePresence.test.tsx index f0db8270a..02e577df4 100644 --- a/packages/react/src/hooks/presence/usePresence.test.tsx +++ b/packages/react/src/hooks/presence/usePresence.test.tsx @@ -1,8 +1,9 @@ import {getPresence, type SanityUser, type UserPresence} from '@sanity/sdk' +import {act, renderHook} from '@testing-library/react' import {NEVER} from 'rxjs' import {describe, expect, it, vi} from 'vitest' -import {act, renderHook} from '../../../test/test-utils' +import {ResourceProvider} from '../../context/ResourceProvider' import {usePresence} from './usePresence' vi.mock('@sanity/sdk', () => ({ @@ -14,10 +15,6 @@ vi.mock('@sanity/sdk', () => ({ })), })) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(() => ({config: {projectId: 'test', dataset: 'test'}})), -})) - describe('usePresence', () => { it('should return presence locations and update when the store changes', () => { const initialLocations: UserPresence[] = [ @@ -60,7 +57,13 @@ describe('usePresence', () => { } vi.mocked(getPresence).mockReturnValue(mockPresenceSource) - const {result, unmount} = renderHook(() => usePresence()) + const {result, unmount} = renderHook(() => usePresence(), { + wrapper: ({children}) => ( + + {children} + + ), + }) // Initial state should be correct expect(result.current.locations).toEqual(initialLocations) diff --git a/packages/react/src/hooks/preview/useDocumentPreview.test.tsx b/packages/react/src/hooks/preview/useDocumentPreview.test.tsx index 4b4a002eb..12f3460fa 100644 --- a/packages/react/src/hooks/preview/useDocumentPreview.test.tsx +++ b/packages/react/src/hooks/preview/useDocumentPreview.test.tsx @@ -1,8 +1,9 @@ import {type DocumentHandle, getPreviewState, type PreviewValue, resolvePreview} from '@sanity/sdk' import {act, render, screen} from '@testing-library/react' -import {Suspense, useRef} from 'react' +import {useRef} from 'react' import {type Mock} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDocumentPreview} from './useDocumentPreview' // Mock IntersectionObserver @@ -24,20 +25,18 @@ beforeAll(() => { }) // Mock the preview store -vi.mock('@sanity/sdk', () => { +vi.mock('@sanity/sdk', async (importOriginal) => { + const actual = await importOriginal() const getCurrent = vi.fn() const subscribe = vi.fn() return { + ...actual, resolvePreview: vi.fn(), getPreviewState: vi.fn().mockReturnValue({getCurrent, subscribe}), } }) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: () => ({}), -})) - const mockDocument: DocumentHandle = { documentId: 'doc1', documentType: 'exampleType', @@ -82,9 +81,9 @@ describe('useDocumentPreview', () => { subscribe.mockImplementation(() => eventsUnsubscribe) render( - Loading...}> + Loading...}> - , + , ) // Initially, element is not intersecting @@ -127,9 +126,9 @@ describe('useDocumentPreview', () => { }) render( - Loading...}> + Loading...}> - , + , ) expect(screen.getByText('Loading...')).toBeInTheDocument() @@ -162,9 +161,9 @@ describe('useDocumentPreview', () => { subscribe.mockImplementation(() => vi.fn()) render( - Loading...}> + Loading...}> - , + , ) expect(screen.getByText('Fallback Title')).toBeInTheDocument() @@ -192,9 +191,9 @@ describe('useDocumentPreview', () => { } render( - Loading...}> + Loading...}> - , + , ) // Should subscribe immediately without waiting for intersection @@ -222,9 +221,9 @@ describe('useDocumentPreview', () => { } render( - Loading...}> + Loading...}> - , + , ) // Should subscribe immediately without waiting for intersection diff --git a/packages/react/src/hooks/projection/useDocumentProjection.test.tsx b/packages/react/src/hooks/projection/useDocumentProjection.test.tsx index 0e529f19a..a61fb34cc 100644 --- a/packages/react/src/hooks/projection/useDocumentProjection.test.tsx +++ b/packages/react/src/hooks/projection/useDocumentProjection.test.tsx @@ -1,8 +1,9 @@ import {type DocumentHandle, getProjectionState, resolveProjection} from '@sanity/sdk' import {act, render, screen} from '@testing-library/react' -import {Suspense, useRef} from 'react' +import {useRef} from 'react' import {type Mock} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useDocumentProjection} from './useDocumentProjection' // Mock IntersectionObserver @@ -24,20 +25,18 @@ beforeAll(() => { }) // Mock the projection store -vi.mock('@sanity/sdk', () => { +vi.mock('@sanity/sdk', async (importOriginal) => { + const actual = await importOriginal() const getCurrent = vi.fn() const subscribe = vi.fn() return { + ...actual, resolveProjection: vi.fn(), getProjectionState: vi.fn().mockReturnValue({getCurrent, subscribe}), } }) -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: () => ({}), -})) - const mockDocument: DocumentHandle = { documentId: 'doc1', documentType: 'exampleType', @@ -87,9 +86,9 @@ describe('useDocumentProjection', () => { subscribe.mockImplementation(() => eventsUnsubscribe) render( - Loading...}> + Loading...}> - , + , ) // Initially, element is not intersecting @@ -137,9 +136,9 @@ describe('useDocumentProjection', () => { subscribe.mockReturnValue(() => {}) render( - Loading...}> + Loading...}> - , + , ) await act(async () => { @@ -164,9 +163,9 @@ describe('useDocumentProjection', () => { subscribe.mockImplementation(() => vi.fn()) render( - Loading...}> + Loading...}> - , + , ) expect(screen.getByText('Fallback Title')).toBeInTheDocument() @@ -184,9 +183,9 @@ describe('useDocumentProjection', () => { subscribe.mockImplementation(() => eventsUnsubscribe) const {rerender} = render( - Loading...}> + Loading...}> - , + , ) // Change projection @@ -196,9 +195,9 @@ describe('useDocumentProjection', () => { }) rerender( - Loading...}> + Loading...}> - , + , ) expect(screen.getByText('Updated Title')).toBeInTheDocument() @@ -224,9 +223,9 @@ describe('useDocumentProjection', () => { } render( - Loading...}> + Loading...}> - , + , ) // Should subscribe immediately without waiting for intersection @@ -257,9 +256,9 @@ describe('useDocumentProjection', () => { } render( - Loading...}> + Loading...}> - , + , ) // Should subscribe immediately without waiting for intersection diff --git a/packages/react/src/hooks/query/useQuery.test.tsx b/packages/react/src/hooks/query/useQuery.test.tsx index 21a2b2d1c..09e4f6407 100644 --- a/packages/react/src/hooks/query/useQuery.test.tsx +++ b/packages/react/src/hooks/query/useQuery.test.tsx @@ -1,9 +1,10 @@ import {getQueryState, resolveQuery, type StateSource} from '@sanity/sdk' import {act, render, screen} from '@testing-library/react' -import {Suspense, useState} from 'react' +import {useState} from 'react' import {type Observable, Subject} from 'rxjs' import {beforeEach, describe, expect, it, vi} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useQuery} from './useQuery' // Mock the functions from '@sanity/sdk' @@ -16,11 +17,6 @@ vi.mock('@sanity/sdk', async (importOriginal) => { } }) -// Mock the Sanity instance hook to return a dummy instance -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn().mockReturnValue({}), -})) - describe('useQuery', () => { beforeEach(() => { vi.resetAllMocks() @@ -45,7 +41,11 @@ describe('useQuery', () => { ) } - render() + render( + Loading...

}> + +
, + ) // Verify that the output contains the data and that isPending is false expect(screen.getByTestId('output').textContent).toContain('test data') @@ -87,9 +87,13 @@ describe('useQuery', () => { } render( - Loading...}> + Loading...} + > - , + , ) // Initially, since storeValue is undefined, the component should suspend and fallback is shown @@ -159,7 +163,11 @@ describe('useQuery', () => { ) } - render() + render( + Loading...

}> + +
, + ) // Initially, should show data1 and not pending expect(screen.getByTestId('output').textContent).toContain('data1') diff --git a/packages/react/src/hooks/releases/useActiveReleases.test.tsx b/packages/react/src/hooks/releases/useActiveReleases.test.tsx index 907c64de8..c2ff0ec7e 100644 --- a/packages/react/src/hooks/releases/useActiveReleases.test.tsx +++ b/packages/react/src/hooks/releases/useActiveReleases.test.tsx @@ -1,16 +1,11 @@ -import {getActiveReleasesState, type ReleaseDocument, type SanityInstance} from '@sanity/sdk' +import {getActiveReleasesState, type ReleaseDocument} from '@sanity/sdk' import {renderHook} from '@testing-library/react' import {BehaviorSubject} from 'rxjs' import {describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {useActiveReleases} from './useActiveReleases' -// Mock the useSanityInstance hook -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - // Mock the getActiveReleasesState function vi.mock('@sanity/sdk', async () => { const actual = await vi.importActual('@sanity/sdk') @@ -26,9 +21,6 @@ describe('useActiveReleases', () => { }) it('should suspend when initial state is undefined', () => { - const mockInstance = {} as SanityInstance - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - const mockSubject = new BehaviorSubject(undefined) const mockStateSource = { subscribe: vi.fn((callback) => { @@ -41,13 +33,22 @@ describe('useActiveReleases', () => { vi.mocked(getActiveReleasesState).mockReturnValue(mockStateSource) - const {result} = renderHook(() => { - try { - return useActiveReleases() - } catch (e) { - return e - } - }) + const {result} = renderHook( + () => { + try { + return useActiveReleases() + } catch (e) { + return e + } + }, + { + wrapper: ({children}) => ( + Loading...

}> + {children} +
+ ), + }, + ) // Verify that the hook threw a promise (suspended) expect(result.current).toBeInstanceOf(Promise) @@ -55,9 +56,6 @@ describe('useActiveReleases', () => { }) it('should resolve with releases when data is available', () => { - const mockInstance = {} as SanityInstance - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - const mockReleases: ReleaseDocument[] = [ {_id: 'release1', _type: 'release'} as ReleaseDocument, {_id: 'release2', _type: 'release'} as ReleaseDocument, @@ -75,7 +73,13 @@ describe('useActiveReleases', () => { vi.mocked(getActiveReleasesState).mockReturnValue(mockStateSource) - const {result} = renderHook(() => useActiveReleases()) + const {result} = renderHook(() => useActiveReleases(), { + wrapper: ({children}) => ( + Loading...

}> + {children} +
+ ), + }) // Verify that the hook returned the releases without suspending expect(result.current).toEqual(mockReleases) diff --git a/packages/react/src/hooks/releases/usePerspective.test.tsx b/packages/react/src/hooks/releases/usePerspective.test.tsx index 34eb3aeea..210fc2de6 100644 --- a/packages/react/src/hooks/releases/usePerspective.test.tsx +++ b/packages/react/src/hooks/releases/usePerspective.test.tsx @@ -4,20 +4,14 @@ import { getPerspectiveState, type PerspectiveHandle, type ReleaseDocument, - type SanityInstance, } from '@sanity/sdk' import {renderHook} from '@testing-library/react' import {BehaviorSubject} from 'rxjs' import {describe, expect, it, vi} from 'vitest' -import {useSanityInstance} from '../context/useSanityInstance' +import {ResourceProvider} from '../../context/ResourceProvider' import {usePerspective} from './usePerspective' -// Mock the useSanityInstance hook -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn(), -})) - // Mock the SDK functions vi.mock('@sanity/sdk', async () => { const actual = await vi.importActual('@sanity/sdk') @@ -36,9 +30,6 @@ describe('usePerspective', () => { }) it('should suspend when initial state is undefined', () => { - const mockInstance = {} as SanityInstance - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - const perspectiveHandle: PerspectiveHandle = { perspective: 'published', } @@ -76,13 +67,18 @@ describe('usePerspective', () => { vi.mocked(getPerspectiveState).mockReturnValue(mockStateSource) vi.mocked(getActiveReleasesState).mockReturnValue(mockReleasesStateSource) - const {result} = renderHook(() => { - try { - return usePerspective(perspectiveHandle) - } catch (e) { - return e - } - }) + const {result} = renderHook( + () => { + try { + return usePerspective(perspectiveHandle) + } catch (e) { + return e + } + }, + { + wrapper: ({children}) => {children}, + }, + ) // Verify that the hook threw a promise (suspended) expect(result.current).toBeInstanceOf(Promise) @@ -90,9 +86,6 @@ describe('usePerspective', () => { }) it('should resolve with perspective when data is available', () => { - const mockInstance = {} as SanityInstance - vi.mocked(useSanityInstance).mockReturnValue(mockInstance) - const perspectiveHandle: PerspectiveHandle = { perspective: 'published', } @@ -110,11 +103,12 @@ describe('usePerspective', () => { vi.mocked(getPerspectiveState).mockReturnValue(mockStateSource) - const {result} = renderHook(() => usePerspective(perspectiveHandle)) + const {result} = renderHook(() => usePerspective(perspectiveHandle), { + wrapper: ({children}) => {children}, + }) // Verify that the hook returned the perspective without suspending expect(result.current).toEqual(mockPerspective) expect(mockStateSource.getCurrent).toHaveBeenCalled() - expect(getPerspectiveState).toHaveBeenCalledWith(mockInstance, perspectiveHandle) }) }) diff --git a/packages/react/src/hooks/users/useUser.test.tsx b/packages/react/src/hooks/users/useUser.test.tsx index b23891e31..df9663e31 100644 --- a/packages/react/src/hooks/users/useUser.test.tsx +++ b/packages/react/src/hooks/users/useUser.test.tsx @@ -6,10 +6,11 @@ import { type UserProfile, } from '@sanity/sdk' import {act, fireEvent, render, screen} from '@testing-library/react' -import {Suspense, useState} from 'react' +import {useState} from 'react' import {type Observable, Subject} from 'rxjs' import {describe, expect, it, vi} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useUser} from './useUser' // Mock the functions from '@sanity/sdk' @@ -22,11 +23,6 @@ vi.mock('@sanity/sdk', async (importOriginal) => { } }) -// Mock the Sanity instance hook to return a dummy instance -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn().mockReturnValue({config: {projectId: 'p'}}), -})) - describe('useUser', () => { // Create mock user profiles with all required fields const mockUserProfile: UserProfile = { @@ -91,7 +87,11 @@ describe('useUser', () => { ) } - render() + render( + + + , + ) // Verify that the output contains the user data and that isPending is false expect(screen.getByTestId('output').textContent).toContain('John Doe (gabc123)') @@ -146,9 +146,9 @@ describe('useUser', () => { } render( - Loading...}> + Loading...}> - , + , ) // Initially, since storeValue is undefined, the component should suspend and fallback is shown @@ -237,7 +237,11 @@ describe('useUser', () => { ) } - render() + render( + + + , + ) // Initially, should show data for first user and not pending expect(screen.getByTestId('output').textContent).toContain('John Doe') @@ -290,7 +294,11 @@ describe('useUser', () => { ) } - render() + render( + + + , + ) expect(screen.getByTestId('output').textContent).toContain('User not found') expect(screen.getByTestId('output').textContent).toContain('not pending') @@ -331,7 +339,11 @@ describe('useUser', () => { ) } - render() + render( + + + , + ) expect(screen.getByTestId('output').textContent).toContain('John Doe (p12345)') }) @@ -360,8 +372,9 @@ describe('useUser', () => { const {data} = useUser({ userId: 'gabc123', resourceType, - projectId: resourceType === 'project' ? 'test-project' : undefined, - organizationId: resourceType === 'organization' ? 'test-org' : undefined, + ...(resourceType === 'project' + ? {projectId: 'test-project'} + : {organizationId: 'test-org'}), }) return (
@@ -373,7 +386,11 @@ describe('useUser', () => { ) } - render() + render( + + + , + ) // Initially should show project user expect(screen.getByTestId('output').textContent).toContain('John Doe') diff --git a/packages/react/src/hooks/users/useUsers.test.tsx b/packages/react/src/hooks/users/useUsers.test.tsx index 0e5de6174..780034a14 100644 --- a/packages/react/src/hooks/users/useUsers.test.tsx +++ b/packages/react/src/hooks/users/useUsers.test.tsx @@ -7,10 +7,11 @@ import { type UserProfile, } from '@sanity/sdk' import {act, fireEvent, render, screen} from '@testing-library/react' -import {Suspense, useState} from 'react' +import {useState} from 'react' import {type Observable, Subject} from 'rxjs' import {describe, expect, it, vi} from 'vitest' +import {ResourceProvider} from '../../context/ResourceProvider' import {useUsers} from './useUsers' // Mock the functions from '@sanity/sdk' @@ -24,11 +25,6 @@ vi.mock('@sanity/sdk', async (importOriginal) => { } }) -// Mock the Sanity instance hook to return a dummy instance -vi.mock('../context/useSanityInstance', () => ({ - useSanityInstance: vi.fn().mockReturnValue({config: {projectId: 'p'}}), -})) - describe('useUsers', () => { // Create mock user profiles with all required fields const mockUserProfile1: UserProfile = { @@ -94,7 +90,11 @@ describe('useUsers', () => { ) } - render() + render( + Loading...

}> + +
, + ) // Verify that the output contains the data and that isPending is false expect(screen.getByTestId('output').textContent).toContain('2 users') @@ -152,9 +152,9 @@ describe('useUsers', () => { } render( - Loading...
}> + Loading...}> - , + , ) // Initially, since storeValue is undefined, the component should suspend and fallback is shown @@ -244,7 +244,11 @@ describe('useUsers', () => { ) } - render() + render( + Loading...}> + + , + ) // Initially, should show data for org1 and not pending expect(screen.getByTestId('output').textContent).toContain('User One') @@ -308,7 +312,11 @@ describe('useUsers', () => { ) } - render() + render( + Loading...}> + + , + ) // Verify initial state expect(screen.getByTestId('output').textContent).toContain('2 users')