mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
4d6820a4d4
fix(planner): route a layover flight per leg, and count linked places as planned Two v4.0.0 reports, both a surface answering a question with less than it had. The map built its own waypoint order. It read the BOOKING position and never expanded metadata.legs, while a multi-leg booking stores its position per leg — so a Frankfurt to Vancouver flight via Reykjavik was dropped from the list and the airport before it was joined to the airport after it in one driving run across the Atlantic. It now takes the order from getMergedItems, the same one the day plan renders, which brings the span filter and the leg expansion with it. getMergedItems in turn had never read day_plan_position, the one slot the map did read, which is how the two could disagree at all. getTransportRouteEndpoints handed back the booking outermost airports for every leg. A stop between two legs was therefore routed from the arrival airport back to the departure one — a drive from Vancouver to Frankfurt with a cafe in Reykjavik on the way. A leg now spans its own pair, when the endpoints line up one per stop with the legs; anything ambiguous keeps the role lookup. Being planned meant one thing: a day_assignments row. A hotel is linked through its stay and a venue through its booking, and neither is ever dragged onto a day, so both sat under Unplanned while the day header printed their names. The three places that asked the question — desktop sidebar, map markers, phone browser — now ask one helper that also counts a stay and a day-anchored booking. A booking that names no day still counts as unplanned, because nothing says when it happens, and the day plan renders nothing for it either. @
104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { plannedPlaceIds, plannedPlaceIdsForDay } from './plannedPlaces'
|
|
import type { AssignmentsMap, Day } from '../types'
|
|
|
|
// FE-UTIL-PLANNED-001 to FE-UTIL-PLANNED-010
|
|
//
|
|
// #2072 — a hotel linked to a booking sat under "Unplanned" while its name was
|
|
// printed in the day header, because being planned meant one thing only: a
|
|
// day_assignments row.
|
|
|
|
const assignments = (map: Record<string, number[]>): AssignmentsMap =>
|
|
Object.fromEntries(
|
|
Object.entries(map).map(([dayId, ids]) => [dayId, ids.map((id, i) => ({
|
|
id: 900 + id, day_id: Number(dayId), order_index: i, place: { id },
|
|
}))]),
|
|
) as unknown as AssignmentsMap
|
|
|
|
const DAYS = [
|
|
{ id: 10, day_number: 1 }, { id: 11, day_number: 2 }, { id: 12, day_number: 3 }, { id: 13, day_number: 4 },
|
|
] as unknown as Day[]
|
|
|
|
describe('plannedPlaceIds', () => {
|
|
it('FE-UTIL-PLANNED-001: an assigned place is planned, as it always was', () => {
|
|
expect([...plannedPlaceIds({ assignments: assignments({ 10: [1, 2] }) })]).toEqual([1, 2])
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-002: a stay plans its place without any assignment', () => {
|
|
const ids = plannedPlaceIds({
|
|
assignments: assignments({}),
|
|
accommodations: [{ place_id: 7, start_day_id: 10, end_day_id: 12 }],
|
|
})
|
|
expect(ids.has(7)).toBe(true)
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-003: a stay whose place was deleted contributes nothing', () => {
|
|
const ids = plannedPlaceIds({
|
|
assignments: assignments({}),
|
|
accommodations: [{ place_id: null, start_day_id: 10, end_day_id: 12 }],
|
|
})
|
|
expect(ids.size).toBe(0)
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-004: a booking on a day plans the place it points at', () => {
|
|
const ids = plannedPlaceIds({
|
|
assignments: assignments({}),
|
|
reservations: [{ place_id: 5, day_id: 10 }],
|
|
})
|
|
expect(ids.has(5)).toBe(true)
|
|
})
|
|
|
|
// The boundary the issue itself calls out: a booking that names no day is not
|
|
// on the plan, and the day view renders nothing for it either.
|
|
it('FE-UTIL-PLANNED-005: a booking with no day plans nothing', () => {
|
|
const ids = plannedPlaceIds({
|
|
assignments: assignments({}),
|
|
reservations: [{ place_id: 5, day_id: null }],
|
|
})
|
|
expect(ids.size).toBe(0)
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-006: a place reachable two ways is counted once', () => {
|
|
const ids = plannedPlaceIds({
|
|
assignments: assignments({ 10: [7] }),
|
|
accommodations: [{ place_id: 7, start_day_id: 10, end_day_id: 12 }],
|
|
reservations: [{ place_id: 7, day_id: 10 }],
|
|
})
|
|
expect([...ids]).toEqual([7])
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-007: no sources at all is an empty set, not a throw', () => {
|
|
expect(plannedPlaceIds({ assignments: {} as AssignmentsMap }).size).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('plannedPlaceIdsForDay', () => {
|
|
it('FE-UTIL-PLANNED-008: a stay covers the nights in between, not just its two ends', () => {
|
|
const sources = {
|
|
assignments: assignments({}),
|
|
accommodations: [{ place_id: 7, start_day_id: 10, end_day_id: 12 }],
|
|
}
|
|
expect(plannedPlaceIdsForDay(10, DAYS, sources).has(7)).toBe(true)
|
|
expect(plannedPlaceIdsForDay(11, DAYS, sources).has(7)).toBe(true)
|
|
expect(plannedPlaceIdsForDay(12, DAYS, sources).has(7)).toBe(true)
|
|
expect(plannedPlaceIdsForDay(13, DAYS, sources).has(7)).toBe(false)
|
|
})
|
|
|
|
// The day rows are the only thing that can order a stay. Without them the
|
|
// planner falls back to the id range rather than dropping the middle nights.
|
|
it('FE-UTIL-PLANNED-009: an unloaded day list still keeps the middle of a stay', () => {
|
|
const sources = {
|
|
assignments: assignments({}),
|
|
accommodations: [{ place_id: 7, start_day_id: 10, end_day_id: 12 }],
|
|
}
|
|
expect(plannedPlaceIdsForDay(11, [], sources).has(7)).toBe(true)
|
|
expect(plannedPlaceIdsForDay(13, [], sources).has(7)).toBe(false)
|
|
})
|
|
|
|
it('FE-UTIL-PLANNED-010: a booking counts only on the day it names', () => {
|
|
const sources = { assignments: assignments({}), reservations: [{ place_id: 5, day_id: 11 }] }
|
|
expect(plannedPlaceIdsForDay(11, DAYS, sources).has(5)).toBe(true)
|
|
expect(plannedPlaceIdsForDay(10, DAYS, sources).has(5)).toBe(false)
|
|
})
|
|
})
|