Files
TREK/client/tests/helpers/mobileTrip.ts
T
Maurice 5b4a7e82ff feat(client): show Dawarich stays, routes and marks where they belong
The trip planner and the journal get a "From Dawarich" list: the stays
recorded for those dates, each one a row that can be dismissed or turned
into a place or an entry after a review step that shows time, name, note
and day. A place that came from a recording carries the Dawarich mark
next to its name so its origin stays visible afterwards.

The recorded route lies over the trip and journal maps as an overlay,
coloured per day and cut along the reader's own timezone rather than
UTC. It is fetched for the window on screen and never stored.

The Atlas gets its own panel beside the map for the countries and cities
the recordings prove, and the wish list is ticked off there, on the day
of the visit, undoably. The phone has its own take on all of it, in the
mobile shell's colours and with native sheets.
2026-09-12 23:37:05 +02:00

270 lines
8.6 KiB
TypeScript

import { vi } from 'vitest';
import type { TripPlanner, MTripShellApi } from '../../src/mobile/screens/trip/MTripShell';
import { buildTrip } from './factories';
/**
* Fixtures for the mobile trip screens. Every tab panel, sheet and overlay
* under src/mobile/screens/trip receives the same two props — the planner
* context from useTripPlanner() and the shell api from MTripShell — so tests
* render them directly with these builders instead of mounting the whole shell.
*
* Defaults are the empty-but-valid state: a trip exists, every list is empty,
* every callback is a spy. Pass overrides for the slice under test.
*/
/** Mirrors useTripStore's action surface as spies. */
export function buildTripActions(): Record<string, ReturnType<typeof vi.fn>> {
const names = [
'addCategory', 'addDayNote', 'addFile', 'addPackingContributor', 'addPackingItem',
'addPlace', 'addReservation', 'addTodoItem', 'assignPlaceToDay', 'clonePackingItem',
'deleteBudgetItem', 'deleteDayNote', 'deleteFile', 'deletePackingItem', 'deletePlace',
'deletePlacesMany', 'deleteReservation', 'deleteTodoItem', 'insertDay', 'loadBudgetItems',
'loadFiles', 'loadReservations', 'loadTrip', 'moveAssignment', 'moveDayNote', 'ratePlace',
'refreshDays', 'removeAssignment', 'removePackingContributor', 'reorderAssignments',
'reorderDays', 'setAssignments', 'setPackingItemSharing', 'setSelectedDay',
'toggleBudgetMemberPaid', 'togglePackingItem', 'toggleReservationStatus', 'toggleTodoItem',
'updateDayNote', 'updateDayTitle', 'updatePackingItem', 'updatePlace', 'updatePlacesMany',
'updateReservation', 'updateTodoItem', 'updateTrip', 'uploadPlaceImage',
];
const actions: Record<string, ReturnType<typeof vi.fn>> = {};
for (const name of names) actions[name] = vi.fn(async () => undefined);
return actions;
}
export function buildToast(): Record<string, ReturnType<typeof vi.fn>> {
return { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() };
}
/**
* `t` echoes the key so assertions stay independent of the English copy.
* Pass a real translation fn through overrides when a test needs visible text.
*/
const echoT = (key: string, params?: Record<string, string | number>): string =>
params ? `${key}:${Object.values(params).join(',')}` : key;
export function buildPlanner(overrides: Partial<TripPlanner> = {}): TripPlanner {
const trip = buildTrip({ id: 1, title: 'Japan 2026', currency: 'EUR' });
const base = {
tripId: 1,
navigate: vi.fn(),
toast: buildToast(),
t: echoT,
language: 'en',
settings: { time_format: '24h', date_format: 'DD.MM.YYYY', default_currency: 'EUR', distance_unit: 'km' },
placesPhotosEnabled: false,
trip,
days: [],
places: [],
assignments: [],
packingItems: [],
todoItems: [],
categories: [],
reservations: [],
budgetItems: [],
files: [],
selectedDayId: null,
isLoading: false,
tripActions: buildTripActions(),
can: vi.fn(() => true),
canUploadFiles: true,
pushUndo: vi.fn(),
undo: vi.fn(),
canUndo: false,
lastActionLabel: null,
handleUndo: vi.fn(),
enabledAddons: { packing: true, budget: true, documents: true, collab: true, vacay: true, atlas: true },
collabFeatures: { chat: true, notes: true, polls: true },
tripAccommodations: [],
setTripAccommodations: vi.fn(),
allowedFileTypes: ['image/jpeg', 'image/png', 'application/pdf'],
tripMembers: [],
setTripMembers: vi.fn(),
refreshMembers: vi.fn(),
loadAccommodations: vi.fn(),
TRANSPORT_TYPES: new Set(['flight', 'train', 'bus', 'car', 'taxi', 'bicycle', 'cruise', 'ferry', 'transit', 'transport_other']),
TRIP_TABS: [],
activeTab: 'plan',
setActiveTab: vi.fn(),
handleTabChange: vi.fn(),
leftWidth: 320,
rightWidth: 320,
leftCollapsed: false,
rightCollapsed: false,
setLeftCollapsed: vi.fn(),
setRightCollapsed: vi.fn(),
startResizeLeft: vi.fn(),
startResizeRight: vi.fn(),
selectedPlaceId: null,
selectedAssignmentId: null,
setSelectedPlaceId: vi.fn(),
selectAssignment: vi.fn(),
showDayDetail: false,
setShowDayDetail: vi.fn(),
dayDetailCollapsed: false,
setDayDetailCollapsed: vi.fn(),
showPlaceForm: false,
setShowPlaceForm: vi.fn(),
editingPlace: null,
setEditingPlace: vi.fn(),
prefillCoords: null,
setPrefillCoords: vi.fn(),
placeFormDayId: null,
setPlaceFormDayId: vi.fn(),
reservationModalDayId: null,
setReservationModalDayId: vi.fn(),
editingAssignmentId: null,
setEditingAssignmentId: vi.fn(),
showTripForm: false,
setShowTripForm: vi.fn(),
showMembersModal: false,
setShowMembersModal: vi.fn(),
showReservationModal: false,
setShowReservationModal: vi.fn(),
editingReservation: null,
setEditingReservation: vi.fn(),
showBookingImport: false,
setShowBookingImport: vi.fn(),
bookingImportKind: 'bookings' as const,
setBookingImportKind: vi.fn(),
bookingImportAvailable: true,
airTrailAvailable: false,
showAirTrailImport: false,
setShowAirTrailImport: vi.fn(),
bookingForAssignmentId: null,
setBookingForAssignmentId: vi.fn(),
showTransportModal: false,
setShowTransportModal: vi.fn(),
editingTransport: null,
setEditingTransport: vi.fn(),
transportModalDayId: null,
setTransportModalDayId: vi.fn(),
transportModalAutomated: false,
setTransportModalAutomated: vi.fn(),
transitPrefill: null,
setTransitPrefill: vi.fn(),
transitJourney: null,
setTransitJourney: vi.fn(),
reservationPrefill: null,
transportPrefill: null,
importReviewActive: false,
startImportReview: vi.fn(),
advanceImportReview: vi.fn(),
routeShown: false,
setRouteShown: vi.fn(),
autoShowRoute: vi.fn(),
routeProfile: 'driving',
setRouteProfile: vi.fn(),
routeVias: [],
fitKey: 0,
setFitKey: vi.fn(),
mobileSidebarOpen: false,
setMobileSidebarOpen: vi.fn(),
mobilePlanScrollTopRef: { current: 0 },
mobilePlacesScrollTopRef: { current: 0 },
deletePlaceId: null,
setDeletePlaceId: vi.fn(),
deletePlaceIds: null,
setDeletePlaceIds: vi.fn(),
// resolveVisibleConnectionIds returns an array, and consumers call .includes().
visibleConnections: [] as number[],
toggleConnection: vi.fn(),
allConnectionsShown: false,
toggleAllConnections: vi.fn(),
mapTransportDetail: null,
setMapTransportDetail: vi.fn(),
isMobile: true,
isTouch: true,
expandedDayIds: new Set<number>(),
setExpandedDayIds: vi.fn(),
mapPlaces: [],
// The recorded-route overlay (#2279): off and empty. MMapArea reads
// `dawarichTrail.track` unconditionally, so the shape has to be here even
// when the addon is not in play.
dawarichEnabled: false,
dawarichTrailShown: false,
toggleDawarichTrail: vi.fn(),
dawarichTrail: { track: null, status: 'idle', reload: vi.fn() },
route: null,
routeSegments: [],
routeInfo: null,
setRoute: vi.fn(),
setRouteInfo: vi.fn(),
updateRouteForDay: vi.fn(),
handleSelectDay: vi.fn(),
handlePlaceClick: vi.fn(),
handleMarkerClick: vi.fn(),
handleMapClick: vi.fn(),
handleMapContextMenu: vi.fn(),
openAddPlaceFromPoi: vi.fn(),
handleSavePlace: vi.fn(),
openPlaceEditor: vi.fn(),
handleDeletePlace: vi.fn(),
confirmDeletePlace: vi.fn(),
confirmDeletePlaces: vi.fn(),
confirmChangeCategory: vi.fn(),
handleAssignToDay: vi.fn(),
handleRemoveAssignment: vi.fn(),
handleReorder: vi.fn(),
handleReorderDays: vi.fn(),
handleAddDay: vi.fn(),
handleUpdateDayTitle: vi.fn(),
handleSaveReservation: vi.fn(),
handleSaveTransport: vi.fn(),
handleDeleteReservation: vi.fn(),
selectedPlace: null,
dayOrderMap: new Map<number, number>(),
dayPlaces: [],
mapTileUrl: 'https://tile.example/{z}/{x}/{y}.png',
fontStyle: {},
splashDone: true,
};
return { ...base, ...overrides } as unknown as TripPlanner;
}
export function buildShell(overrides: Partial<MTripShellApi> = {}): MTripShellApi {
const base: MTripShellApi = {
view: 'plan',
mode: 'go',
trTab: 'plan',
setTrTab: vi.fn(),
setTravelMode: vi.fn(),
toggleView: vi.fn(),
browseFromEdit: false,
sheet: null,
openSheet: vi.fn(),
closeSheet: vi.fn(),
listsTab: 'packing',
setListsTab: vi.fn(),
collabTab: 'chat',
setCollabTab: vi.fn(),
transportsCompact: false,
bookingsCompact: false,
addExpenseSignal: 0,
exportCostsCsvSignal: 0,
uploadFilesSignal: 0,
openFilesTrashSignal: 0,
};
return { ...base, ...overrides };
}