Files
TREK/client/src/components/Planner/useBookingFileAttach.test.ts
T
Maurice b06e3e6266 fix(bookings): keep the editor open until a booking's files are uploaded
Saving a booking or a transport closed the editor and reported success
before the travellers and picked files were written. A failed upload left
a booking without some of its PDFs, no list to retry from, and a create
form that would add a duplicate when saved again. On desktop a file picked
on an existing booking went up at once and vanished when that failed.

The planner's save now takes the editor's follow-up as a step it waits
for: it stores the booking, runs the traveller write and the uploads, and
only then says so and closes. Each file that lands leaves the list, so a
failure leaves exactly the files still to send, and the editor keeps the
stored booking so the retry updates it instead of creating another one,
without a second linked cost or stay. All four editors (desktop booking
and transport dialogs, both phone sheets) share this through
useBookingFollowUp, an import review only moves on once an item's files
are up, and desktop files picked on a saved booking wait for Update like
on the phone.

Closes #2883
2026-10-29 14:20:57 +01:00

64 lines
2.9 KiB
TypeScript

// FE-PLANNER-BOOKINGFILES-001 to -005: the Files row of the desktop booking and
// transport dialogs.
import { act, renderHook } from '@testing-library/react';
import type React from 'react';
import { useState } from 'react';
import { describe, expect, it } from 'vitest';
import { buildTripFile } from '../../../tests/helpers/factories';
import type { TripFile } from '../../types';
import { type BookingFileAttachOptions, useBookingFileAttach } from './useBookingFileAttach';
type FileEvent = React.ChangeEvent<HTMLInputElement> & { target: { value: string } };
function changeEvent(file?: File): FileEvent {
const target = { files: file ? [file] : [], value: 'picked.pdf' };
return { target } as unknown as FileEvent;
}
function setup(overrides: Partial<BookingFileAttachOptions> = {}, files: TripFile[] = []) {
return renderHook(() => {
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
const attach = useBookingFileAttach({ reservation: { id: 5 }, files, setPendingFiles, ...overrides });
return { attach, pendingFiles };
});
}
describe('useBookingFileAttach', () => {
it('FE-PLANNER-BOOKINGFILES-001: a file picked on a saved booking waits for the save like on a new one (#2883)', () => {
const { result } = setup();
const a = new File(['a'], 'a.pdf');
const e = changeEvent(a);
act(() => result.current.attach.handleFileChange(e));
expect(result.current.pendingFiles).toEqual([a]);
expect(e.target.value).toBe('');
});
it('FE-PLANNER-BOOKINGFILES-002: on a new booking the files gather and can be taken back', () => {
const { result } = setup({ reservation: null });
const a = new File(['a'], 'a.pdf');
const b = new File(['b'], 'b.pdf');
act(() => result.current.attach.handleFileChange(changeEvent(a)));
act(() => result.current.attach.handleFileChange(changeEvent(b)));
act(() => result.current.attach.handleFileChange(changeEvent()));
expect(result.current.pendingFiles).toEqual([a, b]);
act(() => result.current.attach.removePending(0));
expect(result.current.pendingFiles).toEqual([b]);
});
it('FE-PLANNER-BOOKINGFILES-003: files linked and detached in the dialog show on the booking', () => {
const files = [buildTripFile({ id: 1, reservation_id: 5 }), buildTripFile({ id: 2, reservation_id: null })];
const { result } = setup({}, files);
expect(result.current.attach.attachedFiles.map((f) => f.id)).toEqual([1]);
act(() => result.current.attach.linkFile(2));
expect(result.current.attach.attachedFiles.map((f) => f.id)).toEqual([1, 2]);
act(() => result.current.attach.detachFile(2));
expect(result.current.attach.attachedFiles.map((f) => f.id)).toEqual([1]);
});
it('FE-PLANNER-BOOKINGFILES-004: a new booking lists no attached files', () => {
const { result } = setup({ reservation: null }, [buildTripFile({ id: 1, reservation_id: 5 })]);
expect(result.current.attach.attachedFiles).toEqual([]);
});
});