From 49e618cbc7e48a7a035a2257cdb0c409b3f1e75b Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 17:37:54 +0200 Subject: [PATCH] fix(journey): hiding suggestions on desktop no longer throws when the preference save fails On the desktop journey page, the eye toggle that hides the suggested entries flipped the view and then saved the preference. When that save was refused (offline, a server error) the promise rejected into a click handler nothing awaited, leaving an unhandled rejection behind. The phone already treats this as a view preference: it keeps the local flip until the next load and swallows the failure on purpose. The desktop toggle now does the same. Red before the fix: src/pages/JourneyDetailPage.wiring.test.tsx FE-JRN-DETWIRE-042 failed with "expected vi.fn() to not be called at all, but actually been called 1 times" on the unhandledRejection listener, called with [Error: offline]. No existing assertions changed. --- client/src/pages/JourneyDetailPage.tsx | 6 +++++- .../pages/JourneyDetailPage.wiring.test.tsx | 21 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/client/src/pages/JourneyDetailPage.tsx b/client/src/pages/JourneyDetailPage.tsx index 56ff67cd0..a8fb00109 100644 --- a/client/src/pages/JourneyDetailPage.tsx +++ b/client/src/pages/JourneyDetailPage.tsx @@ -118,7 +118,11 @@ function JourneyDetailPageDesktop() { const toggleSkeletons = async () => { const next = !hideSkeletons setHideSkeletons(next) - await journeyApi.updatePreferences(current.id, { hide_skeletons: next }) + try { + await journeyApi.updatePreferences(current.id, { hide_skeletons: next }) + } catch { + // A view preference, as on the phone: the local flip stands until the next load. + } } const skeletonLabel = hideSkeletons ? t('journey.skeletons.show') : t('journey.skeletons.hide') const barButton = 'w-10 h-10 flex-shrink-0 rounded-lg bg-surface-elevated backdrop-blur-lg border border-edge shadow-lg text-content-secondary flex items-center justify-center hover:bg-surface-hover active:scale-95 transition-transform' diff --git a/client/src/pages/JourneyDetailPage.wiring.test.tsx b/client/src/pages/JourneyDetailPage.wiring.test.tsx index 34d044c82..b56c6dce1 100644 --- a/client/src/pages/JourneyDetailPage.wiring.test.tsx +++ b/client/src/pages/JourneyDetailPage.wiring.test.tsx @@ -1,4 +1,4 @@ -// FE-JRN-DETWIRE-001 to FE-JRN-DETWIRE-041 +// FE-JRN-DETWIRE-001 to FE-JRN-DETWIRE-042 import { describe, it, expect, beforeEach, vi } from 'vitest'; import { render, screen, fireEvent, waitFor, within } from '../../tests/helpers/render'; import { journeyApi } from '../api/client'; @@ -582,4 +582,23 @@ describe('JourneyDetailPage wiring', () => { expect(screen.queryByText('common.upload')).not.toBeInTheDocument(); expect(screen.queryByText('Immich')).not.toBeInTheDocument(); }); + + it('FE-JRN-DETWIRE-042: a rejected suggestions preference keeps the local flip and rejects nothing', async () => { + const unhandled = vi.fn(); + process.on('unhandledRejection', unhandled); + try { + const update = vi.spyOn(journeyApi, 'updatePreferences').mockRejectedValue(new Error('offline')); + const { hook } = setup({ isMobile: true }); + + fireEvent.click(screen.getByRole('button', { name: 'journey.skeletons.hide' })); + + expect(hook.setHideSkeletons).toHaveBeenCalledWith(true); + await waitFor(() => expect(update).toHaveBeenCalledWith(7, { hide_skeletons: true })); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(unhandled).not.toHaveBeenCalled(); + expect(toast.error).not.toHaveBeenCalled(); + } finally { + process.off('unhandledRejection', unhandled); + } + }); });