mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(mobile): offer public transit in the day plan's leg menu, as on the desktop
The connector menu on the phone listed only the road profiles, so the transit search could not be opened for a single leg there. It now carries the Public transit entry whenever the trip has dates, seeded with the leg's two ends and the departure of the stop it leaves, and the transport sheet passes that departure on to the search. The prefill is built by one module the desktop day plan now uses as well. Closes #2398
This commit is contained in:
@@ -9,7 +9,7 @@ import type { MergedItem } from '../../../../src/utils/dayMerge'
|
||||
import type { Assignment, Day, DayNote, Place, RouteSegment } from '../../../../src/types'
|
||||
import MPlanTimeline from '../../../../src/mobile/screens/trip/plan/MPlanTimeline'
|
||||
|
||||
// FE-MOB-PLTL-001 to FE-MOB-PLTL-045
|
||||
// FE-MOB-PLTL-001 to FE-MOB-PLTL-047
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
tl: {} as Record<string, unknown>,
|
||||
@@ -119,6 +119,8 @@ function buildTl(over: Record<string, unknown> = {}): MPlanTimelineController {
|
||||
{ key: 'plugin:ev/fastest', label: 'EV fastest' },
|
||||
],
|
||||
setLegMode: vi.fn(),
|
||||
transitLegFor: vi.fn(() => null),
|
||||
planTransitLeg: vi.fn(),
|
||||
...over,
|
||||
} as unknown as MPlanTimelineController
|
||||
}
|
||||
@@ -388,6 +390,26 @@ describe('MPlanTimeline', () => {
|
||||
expect(mocks.tl.setLegMode).toHaveBeenCalledWith(11, null)
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-046: a leg with a transit search behind it offers Public transit and opens it (#2398)', () => {
|
||||
const leg = { from: { name: 'Museum', lat: 35.71, lng: 139.79 }, to: { name: 'Ueno Park', lat: 35.72, lng: 139.77 }, time: '09:30' }
|
||||
renderTimeline({ transitLegFor: vi.fn(() => leg) }, {}, { mode: 'edit' })
|
||||
fireEvent.click(connector())
|
||||
|
||||
expect(mocks.tl.transitLegFor).toHaveBeenCalledWith(SEG)
|
||||
fireEvent.click(screen.getByText('transit.title'))
|
||||
|
||||
expect(mocks.tl.planTransitLeg).toHaveBeenCalledWith(leg)
|
||||
expect(mocks.tl.setLegMode).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-047: without a transit search for the leg the menu keeps to the road profiles', () => {
|
||||
renderEditing()
|
||||
fireEvent.click(connector())
|
||||
|
||||
expect(screen.getByText('Driving')).toBeInTheDocument()
|
||||
expect(screen.queryByText('transit.title')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-020: read-only members get no tappable connectors', () => {
|
||||
renderTimeline({}, { can: vi.fn(() => false) }, { mode: 'edit' })
|
||||
|
||||
|
||||
@@ -73,16 +73,18 @@ vi.mock('../../../../src/components/Planner/LocationSelect', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../../../../src/components/Planner/TransitSearchPanel', () => ({
|
||||
default: ({ day, places, onAdd, initialFrom }: {
|
||||
default: ({ day, places, onAdd, initialFrom, initialTime }: {
|
||||
day: { id: number }
|
||||
places: { name: string }[]
|
||||
onAdd: (p: Record<string, unknown>) => void
|
||||
initialFrom: { name: string } | null
|
||||
initialTime: string | null
|
||||
}) => (
|
||||
<div>
|
||||
<span data-testid="transit-day">{day.id}</span>
|
||||
<span data-testid="transit-places">{places.map(p => p.name).join(',')}</span>
|
||||
<span data-testid="transit-from">{initialFrom?.name ?? ''}</span>
|
||||
<span data-testid="transit-time">{initialTime ?? ''}</span>
|
||||
<button type="button" onClick={() => onAdd({ title: 'U4 to Prater', type: 'transit' })}>add-itinerary</button>
|
||||
</div>
|
||||
),
|
||||
@@ -921,7 +923,7 @@ describe('MTransportFormSheet', () => {
|
||||
const planner = makePlanner({
|
||||
transportModalAutomated: true,
|
||||
transportModalDayId: 12,
|
||||
transitPrefill: { from: { name: 'Hotel', lat: 34.7, lng: 135.5 }, to: null },
|
||||
transitPrefill: { from: { name: 'Hotel', lat: 34.7, lng: 135.5 }, to: null, time: '08:15' },
|
||||
places: [{ id: 1, name: 'Fushimi Inari' }, { id: 2, name: 'Nishiki Market' }],
|
||||
assignments: { '12': [{ id: 9, place_id: 2, order_index: 1 }, { id: 8, place_id: 1, order_index: 0 }] },
|
||||
})
|
||||
@@ -929,6 +931,8 @@ describe('MTransportFormSheet', () => {
|
||||
expect(screen.getByTestId('transit-day')).toHaveTextContent('12')
|
||||
expect(screen.getByTestId('transit-places')).toHaveTextContent('Fushimi Inari,Nishiki Market')
|
||||
expect(screen.getByTestId('transit-from')).toHaveTextContent('Hotel')
|
||||
// A leg picked from the timeline's connector menu carries its departure (#2398).
|
||||
expect(screen.getByTestId('transit-time')).toHaveTextContent('08:15')
|
||||
})
|
||||
|
||||
it('FE-MOB-TRFRM-040: the transit panel saves its itinerary through the shared save path', async () => {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { act, renderHook } from '../../../helpers/render'
|
||||
import { buildPlanner } from '../../../helpers/mobileTrip'
|
||||
import { buildAssignment, buildDay, buildPlace, buildTrip } from '../../../helpers/factories'
|
||||
import { useMPlanTimeline } from '../../../../src/mobile/screens/trip/plan/useMPlanTimeline'
|
||||
import type { RouteSegment } from '../../../../src/types'
|
||||
|
||||
// FE-MOB-PLTL-HOOK-001 to FE-MOB-PLTL-HOOK-002: public transit for one leg (#2398)
|
||||
|
||||
vi.mock('../../../../src/hooks/useRouteCalculation', () => ({
|
||||
useRouteCalculation: () => ({ routeSegments: [] }),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../src/api/client', async importOriginal => {
|
||||
const actual = await importOriginal<typeof import('../../../../src/api/client')>()
|
||||
return { ...actual, weatherApi: { ...actual.weatherApi, get: vi.fn(() => new Promise(() => {})) } }
|
||||
})
|
||||
|
||||
const DAY = buildDay({ id: 5, trip_id: 1, day_number: 1, date: '2026-05-02' })
|
||||
const assignments = {
|
||||
'5': [
|
||||
buildAssignment({ id: 11, day_id: 5, order_index: 0, place: buildPlace({ id: 1, name: 'Museum', place_time: '09:30', lat: 35.71, lng: 139.79 }) }),
|
||||
buildAssignment({ id: 12, day_id: 5, order_index: 1, place: buildPlace({ id: 2, name: 'Ueno Park', lat: 35.72, lng: 139.77 }) }),
|
||||
],
|
||||
}
|
||||
const SEG = { from: [35.71, 139.79], to: [35.72, 139.77] } as unknown as RouteSegment
|
||||
|
||||
function planner(trip = buildTrip({ id: 1, start_date: '2026-05-01', end_date: '2026-05-05' })) {
|
||||
return buildPlanner({ trip, days: [DAY], selectedDayId: DAY.id, assignments } as never)
|
||||
}
|
||||
|
||||
describe('useMPlanTimeline, public transit for one leg', () => {
|
||||
it('FE-MOB-PLTL-HOOK-001: seeds the automated search with the leg and opens it for the day', () => {
|
||||
const p = planner()
|
||||
const { result } = renderHook(() => useMPlanTimeline(p))
|
||||
|
||||
const leg = result.current.transitLegFor(SEG)
|
||||
expect(leg).toEqual({
|
||||
from: { name: 'Museum', lat: 35.71, lng: 139.79 },
|
||||
to: { name: 'Ueno Park', lat: 35.72, lng: 139.77 },
|
||||
time: '09:30',
|
||||
})
|
||||
|
||||
act(() => result.current.planTransitLeg(leg!))
|
||||
|
||||
expect(p.setTransportModalDayId).toHaveBeenCalledWith(5)
|
||||
expect(p.setEditingTransport).toHaveBeenCalledWith(null)
|
||||
expect(p.setTransitPrefill).toHaveBeenCalledWith(leg)
|
||||
expect(p.setTransportModalAutomated).toHaveBeenCalledWith(true)
|
||||
expect(p.setShowTransportModal).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('FE-MOB-PLTL-HOOK-002: offers nothing on a trip without dates, like the desktop and the day sheet', () => {
|
||||
const p = planner(buildTrip({ id: 1, start_date: null, end_date: null }))
|
||||
const { result } = renderHook(() => useMPlanTimeline(p))
|
||||
|
||||
expect(result.current.transitLegFor(SEG)).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user