mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
3e8d192305
Spreading a multi-day tour wrote through the store directly and skipped the via re-anchoring every other write on the plan does. A tour with a start counts as a road-trip stop, so taking the whole tour, a stale stage or a moved stage off the middle of a day shifted every later stop, and the vias anchored by position shaped the wrong leg. The undo had the same gap when it put the whole tour back mid-day. Each stop a spread or its undo takes off a day now carries the day's vias along (reanchorAfterRemove), and the whole tour put back carries them the other way (reanchorAfterInsert). The stops are read from the store as each write goes out and the vias are kept as the last correction left them, since one run can touch a day twice before the plan renders. The phone's place sheet greyed out a day that holds a multi-day tour, so its stages could not be spread from there. It now checks the day the way the desktop menus do and leaves the choice to the plan, which opens the card with only the stages offered. The second step of the day menu names the day picked, and a greyed-out "Whole tour on this day" says why right under it. The toast for a tour that sat whole on several days now says "days" instead of "day", in all 27 languages.
133 lines
5.3 KiB
TypeScript
133 lines
5.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { TripPlanner } from '../../../../src/mobile/screens/trip/MTripShell';
|
|
import MBrowseActionsSheet from '../../../../src/mobile/screens/trip/sheets/MBrowseActionsSheet';
|
|
import { useTripStore } from '../../../../src/store/tripStore';
|
|
import type { Place } from '../../../../src/types';
|
|
import { buildPlanner, buildShell } from '../../../helpers/mobileTrip';
|
|
import { act, fireEvent, render, screen, waitFor } from '../../../helpers/render';
|
|
import { resetAllStores, seedStore } from '../../../helpers/store';
|
|
|
|
function tourPlace(): Place {
|
|
return {
|
|
id: 42,
|
|
trip_id: 1,
|
|
name: 'Ridge walk',
|
|
address: null,
|
|
description: null,
|
|
category_id: null,
|
|
lat: 48,
|
|
lng: 11,
|
|
image_url: null,
|
|
google_place_id: null,
|
|
osm_id: null,
|
|
route_geometry: '[[48,11],[49,12]]',
|
|
route_color: null,
|
|
tour_place_id: 42,
|
|
} as unknown as Place;
|
|
}
|
|
|
|
const days = [
|
|
{ id: 7, trip_id: 1, day_number: 1, title: 'Day one', date: null },
|
|
{ id: 8, trip_id: 1, day_number: 2, title: 'Day two', date: null },
|
|
{ id: 9, trip_id: 1, day_number: 3, title: 'Day three', date: null },
|
|
] as never;
|
|
|
|
function mobilePlanner(overrides: Partial<TripPlanner> = {}) {
|
|
const place = tourPlace();
|
|
return buildPlanner({
|
|
places: [place],
|
|
days,
|
|
assignments: { '7': [{ id: 70, day_id: 7, place_id: place.id }] } as never,
|
|
isTourPlace: vi.fn((placeId: number) => placeId === place.id),
|
|
...overrides,
|
|
} as Partial<TripPlanner>);
|
|
}
|
|
|
|
function renderDayPicker(planner = mobilePlanner()) {
|
|
const shell = buildShell({ sheet: { id: 'bract', payload: { placeId: 42, dayPicker: true } } });
|
|
const view = render(<MBrowseActionsSheet planner={planner} shell={shell} />);
|
|
return { ...view, planner, shell };
|
|
}
|
|
|
|
describe('MBrowseActionsSheet Tour assignment picker', () => {
|
|
it('disables assigned Tour days, blocks repeat clicks, and refreshes on reopen', async () => {
|
|
resetAllStores();
|
|
// The days a tour is on are read from the trip store, the one place every tour view reads them from.
|
|
seedStore(useTripStore, { assignments: { '7': [{ id: 70, day_id: 7, place_id: 42 }] } as never });
|
|
let finishAssignment!: (assigned: boolean) => void;
|
|
const handleAssignToDay = vi.fn(
|
|
() =>
|
|
new Promise<boolean>((resolve) => {
|
|
finishAssignment = resolve;
|
|
})
|
|
);
|
|
const planner = mobilePlanner({ handleAssignToDay });
|
|
const { rerender, shell } = renderDayPicker(planner);
|
|
|
|
expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument();
|
|
|
|
const dayOne = screen.getByRole('button', { name: /Day one/ });
|
|
const dayTwo = screen.getByRole('button', { name: /Day two/ });
|
|
expect(dayOne).toBeDisabled();
|
|
expect(dayTwo).toBeEnabled();
|
|
fireEvent.click(dayOne);
|
|
expect(handleAssignToDay).not.toHaveBeenCalled();
|
|
|
|
fireEvent.click(dayTwo);
|
|
fireEvent.click(dayTwo);
|
|
expect(handleAssignToDay).toHaveBeenCalledTimes(1);
|
|
expect(handleAssignToDay).toHaveBeenCalledWith(42, 8);
|
|
|
|
await act(async () => {
|
|
finishAssignment(true);
|
|
});
|
|
await waitFor(() => expect(shell.closeSheet).toHaveBeenCalledOnce());
|
|
|
|
const assignedAfter = {
|
|
'7': [{ id: 70, day_id: 7, place_id: 42 }],
|
|
'8': [{ id: 80, day_id: 8, place_id: 42 }],
|
|
} as never;
|
|
act(() => seedStore(useTripStore, { assignments: assignedAfter }));
|
|
const reopened = mobilePlanner({ assignments: assignedAfter });
|
|
rerender(<MBrowseActionsSheet planner={reopened} shell={shell} />);
|
|
expect(screen.getByRole('button', { name: /Day one/ })).toBeDisabled();
|
|
expect(screen.getByRole('button', { name: /Day two/ })).toBeDisabled();
|
|
expect(screen.getByRole('button', { name: /Day three/ })).toBeEnabled();
|
|
});
|
|
|
|
it('FE-TOUR-STAGE-SPREAD-027: a multi-day tour still goes from a day that holds it, and the plan asks which way', async () => {
|
|
resetAllStores();
|
|
seedStore(useTripStore, { assignments: { '7': [{ id: 70, day_id: 7, place_id: 42 }] } as never });
|
|
const handleAssignToDay = vi.fn().mockResolvedValue(true);
|
|
const stageStops = ['stop-a', 'stop-b', 'stop-c'].map((key) => ({ key, lat: 48, lng: 11 }));
|
|
const planner = mobilePlanner({
|
|
handleAssignToDay,
|
|
tours: [{ place_id: 42, name: 'Ridge walk', stage_stops: stageStops }] as never,
|
|
});
|
|
const { shell } = renderDayPicker(planner);
|
|
const dayOne = screen.getByRole('button', { name: /Day one/ });
|
|
expect(dayOne).toBeEnabled();
|
|
fireEvent.click(dayOne);
|
|
// No way is passed, so the plan opens the choice; on this day only the stages are left.
|
|
expect(handleAssignToDay).toHaveBeenCalledWith(42, 7);
|
|
await waitFor(() => expect(shell.closeSheet).toHaveBeenCalledOnce());
|
|
});
|
|
|
|
it('leaves ordinary Place day choices enabled and preserves their assignment write behavior', () => {
|
|
resetAllStores();
|
|
const place = { ...tourPlace(), tour_place_id: undefined };
|
|
const handleAssignToDay = vi.fn().mockResolvedValue(true);
|
|
const planner = mobilePlanner({
|
|
places: [place],
|
|
isTourPlace: vi.fn(() => false),
|
|
assignments: { '7': [{ id: 70, day_id: 7, place_id: place.id }] } as never,
|
|
handleAssignToDay,
|
|
});
|
|
renderDayPicker(planner);
|
|
const dayOne = screen.getByRole('button', { name: /Day one/ });
|
|
expect(dayOne).toBeEnabled();
|
|
fireEvent.click(dayOne);
|
|
expect(handleAssignToDay).toHaveBeenCalledWith(place.id, 7);
|
|
});
|
|
});
|