mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
64ceecefb3
Every booking and stay write went straight to the API, so offline a new
booking failed with "Network Error" and was lost, and a status tick, the
travellers or a moved transport snapped back. Bookings (add, edit, status,
travellers, delete, day order) and stays (add, edit, delete) now go through
reservationRepo and accommodationRepo and the mutation queue like places and
costs: a temporary row offline, held behind an older queued write online,
since neither carries a version token. Desktop, the phone sheets, the day
plan, the road trip nights and the PDF all write and read through them.
The replay reads the booking from `reservation` only, so the travellers
answer ({ travelers, reservation }) lands on the booking it names. A booking
or its stay that names a place or stay created offline is sent with the real
id once that one has synced, inside create_accommodation too, so an imported
hotel reviewed offline can be linked to a place made for it.
What only works online says so instead of failing: an AirTrail import, a
booking file import, the AI retry and an attachment on a saved booking show
that they need a connection, and a save with newly picked files is stopped
with a hint, since an upload cannot wait in the queue.
Refs #2601
107 lines
4.2 KiB
TypeScript
107 lines
4.2 KiB
TypeScript
/**
|
|
* mutationQueue replay of offline booking writes (#2601): a created booking
|
|
* moves to its real id, the travelers answer only ever touches the booking it
|
|
* names, and a hotel booked offline at a place created offline reaches the
|
|
* server linked to the place's real id, in the booking and in its stay.
|
|
*/
|
|
import 'fake-indexeddb/auto';
|
|
import { http, HttpResponse } from 'msw';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { clearAll, offlineDb } from '../../../src/db/offlineDb';
|
|
import { placeRepo } from '../../../src/repo/placeRepo';
|
|
import { reservationRepo } from '../../../src/repo/reservationRepo';
|
|
import { setAuthed } from '../../../src/sync/authGate';
|
|
import { mutationQueue } from '../../../src/sync/mutationQueue';
|
|
import { buildPlace, buildReservation } from '../../helpers/factories';
|
|
import { server } from '../../helpers/msw/server';
|
|
|
|
function setOnline(v: boolean): void {
|
|
Object.defineProperty(navigator, 'onLine', { value: v, writable: true, configurable: true });
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await clearAll();
|
|
mutationQueue._resetFlushing();
|
|
setAuthed(true);
|
|
setOnline(false);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
setAuthed(false);
|
|
setOnline(true);
|
|
});
|
|
|
|
describe('mutationQueue.flush of bookings', () => {
|
|
it('FE-SYNC-RESV-001: a replayed booking moves to the real id and the queued edit follows it', async () => {
|
|
const { reservation } = await reservationRepo.create(3, { title: 'Flight' });
|
|
await reservationRepo.update(3, reservation.id, { title: 'Flight LH 400' });
|
|
const puts: string[] = [];
|
|
server.use(
|
|
http.post('/api/trips/3/reservations', () =>
|
|
HttpResponse.json({ reservation: buildReservation({ id: 901, trip_id: 3, title: 'Flight' }) })
|
|
),
|
|
http.put('/api/trips/3/reservations/:id', ({ params }) => {
|
|
puts.push(String(params.id));
|
|
return HttpResponse.json({ reservation: buildReservation({ id: 901, trip_id: 3, title: 'Flight LH 400' }) });
|
|
})
|
|
);
|
|
setOnline(true);
|
|
|
|
await mutationQueue.flush();
|
|
|
|
expect(puts).toEqual(['901']);
|
|
expect(await offlineDb.mutationQueue.count()).toBe(0);
|
|
expect(await offlineDb.reservations.get(reservation.id)).toBeUndefined();
|
|
expect(await offlineDb.reservations.get(901)).toMatchObject({ title: 'Flight LH 400' });
|
|
});
|
|
|
|
it('FE-SYNC-RESV-002: the travelers answer lands on the booking it names', async () => {
|
|
await offlineDb.reservations.put(buildReservation({ id: 92, trip_id: 3, title: 'Tour', travelers: [] }));
|
|
await reservationRepo.setTravelers(3, 92, [4]);
|
|
const travelers = [{ user_id: 4, username: 'lena', avatar_url: null }];
|
|
server.use(
|
|
http.put('/api/trips/3/reservations/92/travelers', () =>
|
|
HttpResponse.json({ travelers, reservation: buildReservation({ id: 92, trip_id: 3, title: 'Tour' }) })
|
|
)
|
|
);
|
|
setOnline(true);
|
|
|
|
await mutationQueue.flush();
|
|
|
|
expect(await offlineDb.mutationQueue.count()).toBe(0);
|
|
const rows = await offlineDb.reservations.toArray();
|
|
expect(rows.map((r) => r.id)).toEqual([92]);
|
|
expect(rows[0].travelers).toEqual(travelers);
|
|
});
|
|
|
|
it('FE-SYNC-RESV-003: a hotel booked at a place created offline is sent with the real place id', async () => {
|
|
const { place } = await placeRepo.create(3, { name: 'Hotel Adler' });
|
|
await reservationRepo.create(3, {
|
|
title: 'Hotel Adler',
|
|
type: 'hotel',
|
|
place_id: place.id,
|
|
create_accommodation: { place_id: place.id, start_day_id: 1, end_day_id: 2 },
|
|
});
|
|
let sent: Record<string, unknown> | null = null;
|
|
server.use(
|
|
http.post('/api/trips/3/places', () =>
|
|
HttpResponse.json({ place: buildPlace({ id: 77, trip_id: 3, name: 'Hotel Adler' }) })
|
|
),
|
|
http.post('/api/trips/3/reservations', async ({ request }) => {
|
|
sent = (await request.json()) as Record<string, unknown>;
|
|
return HttpResponse.json({ reservation: buildReservation({ id: 902, trip_id: 3, place_id: 77 }) });
|
|
})
|
|
);
|
|
setOnline(true);
|
|
|
|
await mutationQueue.flush();
|
|
|
|
expect(sent).toMatchObject({
|
|
place_id: 77,
|
|
create_accommodation: { place_id: 77, start_day_id: 1, end_day_id: 2 },
|
|
});
|
|
expect(await offlineDb.mutationQueue.count()).toBe(0);
|
|
});
|
|
});
|