mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
fix(mobile): drop stale up-next cards and open the plan on the next day (#2057)
The UP NEXT card kept pointing at this morning's first stop hours after it had started, and at day one of a trip that ended last month. A header promising what comes next, sitting above something that is over. It now stays away on a day that has passed and once today's timed plan has run out — a day whose stops carry no times at all still shows its first stop, there is nothing stale about that. The timeline card already collapses the reserved space when there is no up-next, so the layout closes cleanly. Seeding the plan day also lands on the next day still ahead when today falls into a gap in the dates, instead of dropping back to day one. A trip that is already over keeps opening on its first day. And a day that was selected before the shell mounted now counts as seeded, so clearing the selection is left alone instead of being re-seeded on the next render — which is what the guard was there for in the first place. Based on #2055.
This commit is contained in:
@@ -64,16 +64,17 @@ function renderShell(overrides: Partial<TripPlanner> = {}) {
|
||||
TRIP_TABS,
|
||||
...overrides,
|
||||
} as Partial<TripPlanner>)
|
||||
const view = render(
|
||||
const Shell = () => (
|
||||
<MTripShell
|
||||
PlanTimeline={slot('plan-timeline')}
|
||||
MapArea={slot('map-area')}
|
||||
PlacesBrowser={slot('places-browser')}
|
||||
TabPanel={TabSlot}
|
||||
Sheets={slot('sheets')}
|
||||
/>,
|
||||
/>
|
||||
)
|
||||
return { ...view, planner: mocks.planner }
|
||||
const view = render(<Shell />)
|
||||
return { ...view, planner: mocks.planner, rerenderShell: () => view.rerender(<Shell />) }
|
||||
}
|
||||
|
||||
const spy = (planner: TripPlanner, name: keyof TripPlanner) =>
|
||||
@@ -120,9 +121,29 @@ describe('MTripShell', () => {
|
||||
expect(planner.tripActions.setSelectedDay).toHaveBeenCalledWith(11)
|
||||
})
|
||||
|
||||
it('FE-MOB-SHELL-006: leaves an existing day selection alone', () => {
|
||||
const { planner } = renderShell()
|
||||
it('FE-MOB-SHELL-006: leaves an existing day selection alone, and a later deselect too', () => {
|
||||
const { planner, rerenderShell } = renderShell()
|
||||
expect(planner.tripActions.setSelectedDay).not.toHaveBeenCalled()
|
||||
|
||||
// Clearing the day is the user's doing; seeding it again here would be the
|
||||
// shell fighting them for it.
|
||||
planner.selectedDayId = null
|
||||
rerenderShell()
|
||||
expect(planner.tripActions.setSelectedDay).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-MOB-SHELL-006b: seeds the next dated day when today falls in a gap', () => {
|
||||
const now = new Date()
|
||||
const iso = (offset: number) => {
|
||||
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset)
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
const days = [
|
||||
{ id: 11, day_number: 1, date: iso(-2) },
|
||||
{ id: 12, day_number: 2, date: iso(2) },
|
||||
] as unknown as Day[]
|
||||
const { planner } = renderShell({ days, selectedDayId: null } as Partial<TripPlanner>)
|
||||
expect(planner.tripActions.setSelectedDay).toHaveBeenCalledWith(12)
|
||||
})
|
||||
|
||||
it('FE-MOB-SHELL-007: does not seed a day before the days arrive', () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
Accommodation, Assignment, Day, DayNote, Reservation, RouteSegment, TranslationFn,
|
||||
} from '../../../../src/types'
|
||||
|
||||
// FE-MOB-PTLM-001 to FE-MOB-PTLM-043
|
||||
// FE-MOB-PTLM-001 to FE-MOB-PTLM-044
|
||||
|
||||
const DAYS = [
|
||||
{ id: 1, trip_id: 1, day_number: 1, date: '2026-05-01', title: null },
|
||||
@@ -345,10 +345,12 @@ describe('planTimelineModel — findUpNext', () => {
|
||||
expect(up?.minutesUntil).toBe(45)
|
||||
})
|
||||
|
||||
it('FE-MOB-PTLM-033: falls back to the first timed stop once the day is over', () => {
|
||||
const up = findUpNext(DAY2, [early, noon, late], new Date(2026, 4, 2, 23, 30))
|
||||
expect(up?.assignment.id).toBe(11)
|
||||
expect(up?.minutesUntil).toBeNull()
|
||||
it('FE-MOB-PTLM-033: shows nothing once the timed plan for today has run out', () => {
|
||||
expect(findUpNext(DAY2, [early, noon, late], new Date(2026, 4, 2, 23, 30))).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MOB-PTLM-044: shows nothing for a day that is already behind us', () => {
|
||||
expect(findUpNext(DAY2, [late, noon, early], new Date(2026, 4, 3, 8, 0))).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MOB-PTLM-034: never counts down on a day that is not today', () => {
|
||||
|
||||
@@ -239,7 +239,7 @@ describe('useMPlanTimeline', () => {
|
||||
expect(result.current.openTransitKeys.has('tr-61')).toBe(false)
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-014: counts down on today and never on any other day', () => {
|
||||
it('FE-MOB-PLTL-014: counts down on today and shows nothing for a day gone by', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date(2026, 4, 2, 10, 15))
|
||||
const today = makePlanner({ assignments: { '2': [TIMED_EARLY, TIMED_LATE] } })
|
||||
@@ -251,7 +251,7 @@ describe('useMPlanTimeline', () => {
|
||||
days: DAYS.map(d => (d.id === 2 ? { ...d, date: '2026-04-30' } : d)),
|
||||
})
|
||||
const other = renderHook(() => useMPlanTimeline(past))
|
||||
expect(other.result.current.upNext).toEqual({ assignment: TIMED_EARLY, minutesUntil: null })
|
||||
expect(other.result.current.upNext).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-015: re-evaluates the countdown on the half-minute tick', () => {
|
||||
@@ -263,7 +263,7 @@ describe('useMPlanTimeline', () => {
|
||||
|
||||
vi.setSystemTime(new Date(2026, 4, 2, 11, 30))
|
||||
act(() => { vi.advanceTimersByTime(30_000) })
|
||||
expect(result.current.upNext).toEqual({ assignment: TIMED_EARLY, minutesUntil: null })
|
||||
expect(result.current.upNext).toBeNull()
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-016: reorders the day when a stop is moved down', async () => {
|
||||
|
||||
Reference in New Issue
Block a user