mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(settings): a failed photo provider test reads "Immich: reason" on the desktop
On the desktop, a failed photo provider connection test toasted "Could not connect to Immich : Auth failed" with a stray space before the colon, and "Could not connect to Immich " with a trailing space when the server gave no reason. The phone already read "Could not connect to Immich: Auth failed". usePhotoProviderConnections took a testErrorGap option that put a space between the sentence and ": reason", and the desktop passed ' '. The option is gone, so both views join the reason directly. Regression test FE-COMP-PHOTOPROVIDERS-027 (PhotoProvidersSection.test.tsx) was red on the old code: "Unable to find an element with the text: Could not connect to Immich: Auth failed" (the toast read "Could not connect to Immich : Auth failed"). Changed assertions: - usePhotoProviderConnections.test.ts FE-COMP-PHOTOCONN-010: the desktop toast was 'memories.connectionError(Immich) : bad key', now 'memories.connectionError(Immich): bad key'; it also checks that a failure without a reason toasts the bare sentence.
This commit is contained in:
@@ -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(
|
||||
<>
|
||||
<ToastContainer />
|
||||
<PhotoProvidersSection />
|
||||
</>,
|
||||
);
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -81,8 +81,8 @@ function stubGets(byPath: Record<string, Resp | (() => Promise<Resp>)>) {
|
||||
});
|
||||
}
|
||||
|
||||
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)');
|
||||
|
||||
@@ -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 }));
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user