mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(journey): a failed entry save on the phone says so instead of failing silently
Saving a journey entry on the phone sheet while the server refused it
(a locked journey, a dropped connection) left the sheet sitting there
with no message, and the rejection went unhandled. The desktop dialog
toasted journey.settings.saveFailed or the server's reason.
The cause was the toastSaveError option of the shared useJourneyEntryForm
hook: only the desktop set it, and without it handleSave rethrew into a
click handler that nothing awaited. The option only kept the phone on
the broken path, so it is gone and both views toast and keep the form
open.
Red before the fix: src/components/Journey/useJourneyEntryForm.test.ts
FE-JRN-ENTRYFORM-021 failed because handleSave rejected with
"{ response: { data: { error: 'Locked' } } }" instead of toasting.
Changed assertions in FE-JRN-ENTRYFORM-007 (renamed to "a failed save
toasts for the desktop dialog and the sheet alike"): the sheet's save
was expected to reject (rejected true) with one toast in total; it now
resolves (rejected false) and the toast count is 2.
This commit is contained in:
@@ -149,7 +149,7 @@ export function EntryEditor({ entry, journeyId, tripDates, galleryPhotos, trips,
|
||||
handleSave, handleFileChange, pickGalleryPhoto, removePhoto, searchLocation, pickLocation, handleUseCurrentLocation,
|
||||
} = useJourneyEntryForm({
|
||||
entry, journeyId, trips, galleryPhotos, onSave, onUploadPhotos, onAddProviderPhotos, onDone,
|
||||
blankVerdictRow: true, dirtyOnCoordinates: true, toastSaveError: true, autoFillWeather: showWeather,
|
||||
blankVerdictRow: true, dirtyOnCoordinates: true, autoFillWeather: showWeather,
|
||||
})
|
||||
const [photoTab, setPhotoTab] = useState<'upload' | 'gallery' | 'external'>('upload')
|
||||
const [availableProviders, setAvailableProviders] = useState<{ id: string; name: string }[]>([])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// FE-JRN-ENTRYFORM-001 to FE-JRN-ENTRYFORM-020: the journey entry form logic behind both
|
||||
// FE-JRN-ENTRYFORM-001 to FE-JRN-ENTRYFORM-021: the journey entry form logic behind both
|
||||
// the desktop editor dialog and the phone entry sheet.
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import type React from 'react';
|
||||
@@ -198,9 +198,9 @@ describe('useJourneyEntryForm', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('FE-JRN-ENTRYFORM-007: a failed save toasts for the desktop dialog and rejects for the sheet', async () => {
|
||||
it('FE-JRN-ENTRYFORM-007: a failed save toasts for the desktop dialog and the sheet alike', async () => {
|
||||
const onSave = vi.fn().mockRejectedValue({ response: { data: { error: 'Locked' } } });
|
||||
const desktop = setup({ onSave, toastSaveError: true });
|
||||
const desktop = setup({ onSave, blankVerdictRow: true, dirtyOnCoordinates: true });
|
||||
await act(() => desktop.result.current.handleSave());
|
||||
expect(addToast).toHaveBeenCalledWith('Locked', 'error', undefined);
|
||||
expect(desktop.result.current.saving).toBe(false);
|
||||
@@ -208,9 +208,9 @@ describe('useJourneyEntryForm', () => {
|
||||
const sheet = setup({ onSave });
|
||||
let rejected = false;
|
||||
await act(() => sheet.result.current.handleSave().catch(() => void (rejected = true)));
|
||||
expect(rejected).toBe(true);
|
||||
expect(rejected).toBe(false);
|
||||
expect(sheet.result.current.saving).toBe(false);
|
||||
expect(addToast).toHaveBeenCalledTimes(1);
|
||||
expect(addToast).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('FE-JRN-ENTRYFORM-008: links gallery photos picked before the first save', async () => {
|
||||
@@ -378,4 +378,14 @@ describe('useJourneyEntryForm', () => {
|
||||
.offersStatsToggle
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('FE-JRN-ENTRYFORM-021: a failed save on the phone sheet toasts and keeps the form open', async () => {
|
||||
const onSave = vi.fn().mockRejectedValue({ response: { data: { error: 'Locked' } } });
|
||||
const onDone = vi.fn();
|
||||
const sheet = setup({ onSave, onDone, withTags: true });
|
||||
await act(() => sheet.result.current.handleSave());
|
||||
expect(addToast).toHaveBeenCalledWith('Locked', 'error', undefined);
|
||||
expect(onDone).not.toHaveBeenCalled();
|
||||
expect(sheet.result.current.saving).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,8 +49,6 @@ export interface JourneyEntryFormOptions {
|
||||
withTags?: boolean;
|
||||
/** Moving the point alone, without renaming it, counts as an unsaved change (the desktop dialog). */
|
||||
dirtyOnCoordinates?: boolean;
|
||||
/** Toast a failed save and swallow it (the desktop dialog); otherwise the save rejects. */
|
||||
toastSaveError?: boolean;
|
||||
/** Show a failed "use my location" under the field (the phone sheet) instead of as a toast. */
|
||||
inlineLocateError?: boolean;
|
||||
/** Fill an empty weather field from the forecast once the place and day are known (the desktop dialog). */
|
||||
@@ -81,7 +79,6 @@ export function useJourneyEntryForm({
|
||||
blankVerdictRow = false,
|
||||
withTags = false,
|
||||
dirtyOnCoordinates = false,
|
||||
toastSaveError = false,
|
||||
inlineLocateError = false,
|
||||
autoFillWeather = false,
|
||||
}: JourneyEntryFormOptions) {
|
||||
@@ -378,7 +375,6 @@ export function useJourneyEntryForm({
|
||||
}
|
||||
onDone();
|
||||
} catch (err) {
|
||||
if (!toastSaveError) throw err;
|
||||
// Neither the page callback nor journeyStore toasts, so without this the
|
||||
// whole entry just fails to save with no sign of it.
|
||||
toast.error(getApiErrorMessage(err, t('journey.settings.saveFailed')));
|
||||
|
||||
Reference in New Issue
Block a user