mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
285b9bcde0
The v3.1.3 fix re-anchored dated bookings after a trip date change but
explicitly excluded hotels: day_accommodations has no absolute date columns,
so stays remained glued to positionally re-dated day rows and shifted with
the range. updateTrip now snapshots day dates before generateDays and a new
resyncAccommodationDays re-anchors each stay (and its linked hotel
reservation, restamping its stale reservation_time) to the days holding its
pre-change dates; out-of-range stays stay glued so whole-trip moves still
shift together. Unlinked dated hotels resync like any booking, and the
date-change block is wrapped in a transaction.
Changing the start date now also asks how plans should follow via a new
date_shift_mode field ('keep_bookings' default / 'shift_all', which reuses
the reorder/insert restamp path to glue everything), exposed in the trip
edit modal (all 22 locales), the shared contract, and the MCP update_trip
tool. Clients no longer show stale state: the initiator reloads
reservations + accommodations after saving, collaborators refetch on a
date-changing trip:updated, and reconnect hydration nudges the planner's
accommodations too.
77 lines
4.0 KiB
TypeScript
77 lines
4.0 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { useTripStore } from '../../../src/store/tripStore';
|
|
import { resetAllStores } from '../../helpers/store';
|
|
import { buildTrip, buildPlace } from '../../helpers/factories';
|
|
|
|
beforeEach(() => {
|
|
resetAllStores();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('remoteEventHandler > trip', () => {
|
|
it('FE-WSEVT-TRIP-001: trip:updated replaces trip in state', () => {
|
|
const originalTrip = buildTrip({ id: 1, title: 'Paris Trip' });
|
|
useTripStore.setState({ trip: originalTrip });
|
|
const updatedTrip = buildTrip({ id: 1, title: 'Paris & Lyon Trip' });
|
|
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
|
const { trip } = useTripStore.getState();
|
|
expect(trip?.title).toBe('Paris & Lyon Trip');
|
|
});
|
|
|
|
it('FE-WSEVT-TRIP-002: trip:updated does not affect other state fields', () => {
|
|
const existingPlace = buildPlace({ id: 55, name: 'Eiffel Tower' });
|
|
useTripStore.setState({
|
|
trip: buildTrip({ id: 1, title: 'Original' }),
|
|
places: [existingPlace],
|
|
});
|
|
const updatedTrip = buildTrip({ id: 1, title: 'Updated' });
|
|
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
|
const { places } = useTripStore.getState();
|
|
expect(places).toHaveLength(1);
|
|
expect(places[0].id).toBe(55);
|
|
});
|
|
|
|
// A remote date-range change re-anchors bookings/accommodations server-side, so the
|
|
// handler must pull authoritative days + reservations and nudge the planner (#1288).
|
|
describe('date-range change refresh (#1288)', () => {
|
|
const stubRefresh = () => {
|
|
const refreshDays = vi.fn().mockResolvedValue(undefined);
|
|
const loadReservations = vi.fn().mockResolvedValue(undefined);
|
|
useTripStore.setState({ refreshDays, loadReservations });
|
|
const dispatchSpy = vi.spyOn(window, 'dispatchEvent');
|
|
return { refreshDays, loadReservations, dispatchSpy };
|
|
};
|
|
const firedAccommodationsRefresh = (spy: ReturnType<typeof vi.spyOn>) =>
|
|
spy.mock.calls.some(([e]) => (e as Event).type === 'accommodations:refresh');
|
|
|
|
it('FE-WSEVT-TRIP-003: trip:updated with a changed start_date refetches days + reservations and fires accommodations:refresh', () => {
|
|
useTripStore.setState({ trip: buildTrip({ id: 1, start_date: '2025-06-01', end_date: '2025-06-05' }) });
|
|
const { refreshDays, loadReservations, dispatchSpy } = stubRefresh();
|
|
const updatedTrip = buildTrip({ id: 1, start_date: '2025-05-31', end_date: '2025-06-05' });
|
|
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
|
expect(refreshDays).toHaveBeenCalledWith(1);
|
|
expect(loadReservations).toHaveBeenCalledWith(1);
|
|
expect(firedAccommodationsRefresh(dispatchSpy)).toBe(true);
|
|
});
|
|
|
|
it('FE-WSEVT-TRIP-004: a title-only trip:updated does not trigger any refetch', () => {
|
|
useTripStore.setState({ trip: buildTrip({ id: 1, title: 'Old', start_date: '2025-06-01', end_date: '2025-06-05' }) });
|
|
const { refreshDays, loadReservations, dispatchSpy } = stubRefresh();
|
|
const updatedTrip = buildTrip({ id: 1, title: 'New', start_date: '2025-06-01', end_date: '2025-06-05' });
|
|
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
|
expect(refreshDays).not.toHaveBeenCalled();
|
|
expect(loadReservations).not.toHaveBeenCalled();
|
|
expect(firedAccommodationsRefresh(dispatchSpy)).toBe(false);
|
|
});
|
|
|
|
it('FE-WSEVT-TRIP-005: a trip:updated for a different trip id does not trigger a refetch', () => {
|
|
useTripStore.setState({ trip: buildTrip({ id: 1, start_date: '2025-06-01', end_date: '2025-06-05' }) });
|
|
const { refreshDays, loadReservations } = stubRefresh();
|
|
const updatedTrip = buildTrip({ id: 2, start_date: '2025-07-01', end_date: '2025-07-05' });
|
|
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
|
expect(refreshDays).not.toHaveBeenCalled();
|
|
expect(loadReservations).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|