diff --git a/client/src/components/Settings/PhotoProvidersSection.test.tsx b/client/src/components/Settings/PhotoProvidersSection.test.tsx index e26c72da5..cff5e5f70 100644 --- a/client/src/components/Settings/PhotoProvidersSection.test.tsx +++ b/client/src/components/Settings/PhotoProvidersSection.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-PHOTOPROVIDERS-001 to FE-COMP-PHOTOPROVIDERS-026 +// FE-COMP-PHOTOPROVIDERS-001 to FE-COMP-PHOTOPROVIDERS-027 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; @@ -263,6 +263,23 @@ describe('PhotoProvidersSection', () => { expect(await screen.findByText(/Auth failed/i)).toBeInTheDocument(); }); + it('FE-COMP-PHOTOPROVIDERS-027: a failed test reads "provider: reason" with no stray space', async () => { + const user = userEvent.setup(); + server.use( + http.post('/api/addons/immich/test', () => HttpResponse.json({ connected: false, error: 'Auth failed' })), + ); + seedMemoriesEnabled(); + render( + <> + + + , + ); + await screen.findByText('Immich'); + await user.click(screen.getByRole('button', { name: /test connection/i })); + expect(await screen.findByText('Could not connect to Immich: Auth failed')).toBeInTheDocument(); + }); + it('FE-COMP-PHOTOPROVIDERS-016: Test button is disabled while test is in progress', async () => { const user = userEvent.setup(); let resolveTest!: () => void; diff --git a/client/src/components/Settings/PhotoProvidersSection.tsx b/client/src/components/Settings/PhotoProvidersSection.tsx index 4c58bd7e8..ae2db0df1 100644 --- a/client/src/components/Settings/PhotoProvidersSection.tsx +++ b/client/src/components/Settings/PhotoProvidersSection.tsx @@ -27,7 +27,7 @@ export default function PhotoProvidersSection(): React.ReactElement { isProviderSaveDisabled, handleSaveProvider, handleTestProvider, - } = usePhotoProviderConnections({ statusRouteOwnsBadge: false, testErrorGap: ' ' }) + } = usePhotoProviderConnections({ statusRouteOwnsBadge: false }) const renderPhotoProviderSection = (provider: PhotoProviderAddon): React.ReactElement => { const fields = getProviderFields(provider) diff --git a/client/src/components/Settings/usePhotoProviderConnections.test.ts b/client/src/components/Settings/usePhotoProviderConnections.test.ts index 1c5fe6e93..cbf202302 100644 --- a/client/src/components/Settings/usePhotoProviderConnections.test.ts +++ b/client/src/components/Settings/usePhotoProviderConnections.test.ts @@ -81,8 +81,8 @@ function stubGets(byPath: Record Promise)>) { }); } -function render(statusRouteOwnsBadge: boolean, testErrorGap = '') { - return renderHook(() => usePhotoProviderConnections({ statusRouteOwnsBadge, testErrorGap })); +function render(statusRouteOwnsBadge: boolean) { + return renderHook(() => usePhotoProviderConnections({ statusRouteOwnsBadge })); } beforeEach(() => { @@ -216,16 +216,19 @@ describe('usePhotoProviderConnections', () => { expect(result.current.providerTesting.immich).toBe(false); }); - it('FE-COMP-PHOTOCONN-010: a failed probe joins the server reason with the gap each shell asks for', async () => { + it('FE-COMP-PHOTOCONN-010: a failed probe joins the server reason as ": reason" on both shells', async () => { seedProviders([PROVIDER]); stubGets({ '/addons/immich/settings': { data: {} }, '/addons/immich/status': { data: {} } }); vi.spyOn(apiClient, 'post').mockResolvedValue({ data: { connected: false, error: 'bad key' } }); - const phone = render(true, ''); + const phone = render(true); await act(() => phone.result.current.handleTestProvider(PROVIDER)); expect(toast.error).toHaveBeenLastCalledWith('memories.connectionError(Immich): bad key'); - const desktop = render(false, ' '); + const desktop = render(false); await act(() => desktop.result.current.handleTestProvider(PROVIDER)); - expect(toast.error).toHaveBeenLastCalledWith('memories.connectionError(Immich) : bad key'); + expect(toast.error).toHaveBeenLastCalledWith('memories.connectionError(Immich): bad key'); + vi.mocked(apiClient.post).mockResolvedValue({ data: { connected: false } }); + await act(() => desktop.result.current.handleTestProvider(PROVIDER)); + expect(toast.error).toHaveBeenLastCalledWith('memories.connectionError(Immich)'); vi.mocked(apiClient.post).mockRejectedValue(new Error('x')); await act(() => desktop.result.current.handleTestProvider(PROVIDER)); expect(toast.error).toHaveBeenLastCalledWith('memories.connectionError(Immich)'); diff --git a/client/src/components/Settings/usePhotoProviderConnections.ts b/client/src/components/Settings/usePhotoProviderConnections.ts index 98ff592c9..2d379c785 100644 --- a/client/src/components/Settings/usePhotoProviderConnections.ts +++ b/client/src/components/Settings/usePhotoProviderConnections.ts @@ -56,8 +56,6 @@ export interface PhotoProviderConnectionOptions { * race. The phone does this; the desktop takes whichever answer lands last. */ statusRouteOwnsBadge: boolean; - /** What goes between the failed test sentence and the server's ": reason" ('' on the phone, ' ' on the desktop). */ - testErrorGap: string; } /** @@ -66,7 +64,7 @@ export interface PhotoProviderConnectionOptions { * (secrets never prefilled), the connection badge, and save and test. The desktop * section and its phone twin render their own markup over this. */ -export function usePhotoProviderConnections({ statusRouteOwnsBadge, testErrorGap }: PhotoProviderConnectionOptions) { +export function usePhotoProviderConnections({ statusRouteOwnsBadge }: PhotoProviderConnectionOptions) { const { t } = useTranslation(); const toast = useToast(); const { isEnabled: addonEnabled, addons } = useAddonStore(); @@ -220,7 +218,7 @@ export function usePhotoProviderConnections({ statusRouteOwnsBadge, testErrorGap toast.success(t('memories.connectionSuccess', { provider_name: provider.name })); } else { const detail = res.data?.error ? `: ${String(res.data.error)}` : ''; - toast.error(`${t('memories.connectionError', { provider_name: provider.name })}${testErrorGap}${detail}`); + toast.error(`${t('memories.connectionError', { provider_name: provider.name })}${detail}`); } } catch { toast.error(t('memories.connectionError', { provider_name: provider.name })); diff --git a/client/src/mobile/screens/settings/MPhotoProvidersSection.tsx b/client/src/mobile/screens/settings/MPhotoProvidersSection.tsx index 5d7be597a..29f9e2820 100644 --- a/client/src/mobile/screens/settings/MPhotoProvidersSection.tsx +++ b/client/src/mobile/screens/settings/MPhotoProvidersSection.tsx @@ -30,7 +30,7 @@ export default function MPhotoProvidersSection(): React.ReactElement { isProviderSaveDisabled, handleSaveProvider, handleTestProvider, - } = usePhotoProviderConnections({ statusRouteOwnsBadge: true, testErrorGap: '' }) + } = usePhotoProviderConnections({ statusRouteOwnsBadge: true }) const renderPhotoProviderSection = (provider: PhotoProviderAddon): React.ReactElement => { const fields = getProviderFields(provider)