// @vitest-environment jsdom import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { renderHook, waitFor } from '@testing-library/react' import type { PropsWithChildren } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { IPicGoCloudConfigSyncSessionStatus, IPicGoCloudEncryptionMethod, type IPicGoCloudConfigSyncState } from '~/universal/types/cloudConfigSync' vi.mock('@/adapters/cloud', () => ({ cloudAdapter: { getConfigSyncState: vi.fn() } })) vi.mock('@/queries/picgo-cloud', () => ({ usePicGoCloudUserInfo: vi.fn() })) vi.mock('@/queries/query-client', () => ({ rendererQueryClient: { setQueryData: vi.fn(), getQueryData: vi.fn() } })) import { cloudAdapter } from '@/adapters/cloud' import { usePicGoCloudUserInfo } from '@/queries/picgo-cloud' import { rendererQueryClient } from '@/queries/query-client' import { PicGoCloudConfigSyncQueryKeys, setCloudConfigSyncStateQueryData, updateCloudConfigSyncStateQueryData, useCloudConfigSyncStateQuery } from '@/queries/picgo-cloud-config-sync' const SAMPLE_STATE: IPicGoCloudConfigSyncState = { sessionStatus: IPicGoCloudConfigSyncSessionStatus.IDLE, encryptionMethod: IPicGoCloudEncryptionMethod.AUTO, lastSyncedAt: '2026-05-05T10:35:09.000Z' } const buildWrapper = () => { const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } }) const Wrapper = ({ children }: PropsWithChildren) => ( {children} ) return { client, Wrapper } } describe('picgo-cloud-config-sync helpers', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { vi.clearAllMocks() }) describe('setCloudConfigSyncStateQueryData', () => { it('writes state to query cache under the canonical key', () => { setCloudConfigSyncStateQueryData(SAMPLE_STATE) expect(rendererQueryClient.setQueryData).toHaveBeenCalledOnce() expect(rendererQueryClient.setQueryData).toHaveBeenCalledWith( PicGoCloudConfigSyncQueryKeys.state, SAMPLE_STATE ) }) }) describe('updateCloudConfigSyncStateQueryData', () => { it('passes the updater function to setQueryData', () => { const updater = vi.fn( (prev: IPicGoCloudConfigSyncState | undefined): IPicGoCloudConfigSyncState => ({ ...(prev ?? { sessionStatus: IPicGoCloudConfigSyncSessionStatus.IDLE }), sessionStatus: IPicGoCloudConfigSyncSessionStatus.SYNCING }) ) updateCloudConfigSyncStateQueryData(updater) expect(rendererQueryClient.setQueryData).toHaveBeenCalledOnce() const callArgs = vi.mocked(rendererQueryClient.setQueryData).mock.calls[0] expect(callArgs[0]).toEqual(PicGoCloudConfigSyncQueryKeys.state) expect(callArgs[1]).toBe(updater) }) }) }) describe('useCloudConfigSyncStateQuery', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { vi.clearAllMocks() }) it('does not call adapter when user is not logged in', async () => { vi.mocked(usePicGoCloudUserInfo).mockReturnValue({ userInfo: undefined, isPaid: false, isFetched: true } as unknown as ReturnType) const { Wrapper } = buildWrapper() const { result } = renderHook(() => useCloudConfigSyncStateQuery(), { wrapper: Wrapper }) // 等一个 microtask 让 react-query 有机会 schedule fetch await Promise.resolve() expect(cloudAdapter.getConfigSyncState).not.toHaveBeenCalled() expect(result.current.fetchStatus).toBe('idle') }) it('returns parsed state when adapter resolves with success', async () => { vi.mocked(usePicGoCloudUserInfo).mockReturnValue({ userInfo: { user: 'tester', plan: 1 }, isPaid: true, isFetched: true } as unknown as ReturnType) vi.mocked(cloudAdapter.getConfigSyncState).mockResolvedValue({ success: true, data: SAMPLE_STATE } as unknown as Awaited>) const { Wrapper } = buildWrapper() const { result } = renderHook(() => useCloudConfigSyncStateQuery(), { wrapper: Wrapper }) await waitFor(() => { expect(result.current.isSuccess).toBe(true) }) expect(cloudAdapter.getConfigSyncState).toHaveBeenCalledOnce() expect(result.current.data).toEqual(SAMPLE_STATE) }) it('surfaces error state when adapter resolves with success: false', async () => { vi.mocked(usePicGoCloudUserInfo).mockReturnValue({ userInfo: { user: 'tester', plan: 0 }, isPaid: false, isFetched: true } as unknown as ReturnType) vi.mocked(cloudAdapter.getConfigSyncState).mockResolvedValue({ success: false, error: 'boom' } as unknown as Awaited>) const { Wrapper } = buildWrapper() const { result } = renderHook(() => useCloudConfigSyncStateQuery(), { wrapper: Wrapper }) await waitFor(() => { expect(result.current.isError).toBe(true) }) expect(result.current.error).toBeInstanceOf(Error) expect((result.current.error as Error).message).toBe('boom') }) })