mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
b267771772
Collapsing a day card has hidden that day's places from the map since #216, and it was the only way to reduce a busy map to one day. The phone shell does not render DayPlanSidebar, so when it replaced the desktop page on phones that control disappeared with it: expandedDayIds stayed null and every day's pins sat on top of each other with nothing to do about it (#1962). A day chip in map view now drives the same declutter the desktop chevron drives, rather than introducing a second filter. Entering the map focuses the selected day, tapping another chip moves the focus with it, and leaving the map clears it so the timeline is never looking at a filtered state. Unplanned places stay visible, exactly as the collapse path has always left them.
452 lines
21 KiB
TypeScript
452 lines
21 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { act, fireEvent, render, screen } from '../../../helpers/render'
|
|
import { buildPlanner, buildShell } from '../../../helpers/mobileTrip'
|
|
import type { MTripShellApi, TripPlanner } from '../../../../src/mobile/screens/trip/MTripShell'
|
|
import type { Day, PackingItem, TodoItem } from '../../../../src/types'
|
|
|
|
// FE-MOB-SHELL-001 to FE-MOB-SHELL-034
|
|
|
|
const mocks = vi.hoisted(() => ({ planner: {} as TripPlanner }))
|
|
|
|
vi.mock('../../../../src/pages/tripPlanner/useTripPlanner', () => ({
|
|
useTripPlanner: () => mocks.planner,
|
|
}))
|
|
|
|
// The real slots pull in the map engine and every tab panel; the shell only
|
|
// ever hands them props, so they are replaced by the probes below.
|
|
vi.mock('../../../../src/mobile/screens/trip/plan/MPlanTimeline', () => ({ default: () => null }))
|
|
vi.mock('../../../../src/mobile/screens/trip/map/MMapArea', () => ({ default: () => null }))
|
|
vi.mock('../../../../src/mobile/screens/trip/places/MPlacesBrowser', () => ({ default: () => null }))
|
|
vi.mock('../../../../src/mobile/screens/trip/tabs/MTripTabPanel', () => ({ default: () => null }))
|
|
vi.mock('../../../../src/mobile/screens/trip/sheets/MTripSheets', () => ({ default: () => null }))
|
|
|
|
import MTripShell from '../../../../src/mobile/screens/trip/MTripShell'
|
|
|
|
/** Last shell api handed to a slot — the object every slot shares. */
|
|
let shellApi!: MTripShellApi
|
|
|
|
function slot(testId: string) {
|
|
return function Slot({ shell }: { planner: TripPlanner; shell: MTripShellApi }) {
|
|
shellApi = shell
|
|
return <div data-testid={testId} />
|
|
}
|
|
}
|
|
|
|
function TabSlot({ shell, tab }: { planner: TripPlanner; shell: MTripShellApi; tab: string }) {
|
|
shellApi = shell
|
|
return <div data-testid="tab-panel">{tab}</div>
|
|
}
|
|
|
|
const TRIP_TABS = [
|
|
{ id: 'plan', label: 'Plan' },
|
|
{ id: 'transports', label: 'Transport' },
|
|
{ id: 'buchungen', label: 'Bookings' },
|
|
{ id: 'finanzplan', label: 'Budget' },
|
|
{ id: 'listen', label: 'Lists' },
|
|
{ id: 'dateien', label: 'Files' },
|
|
{ id: 'collab', label: 'Collaboration' },
|
|
]
|
|
|
|
const DAYS = [
|
|
{ id: 11, trip_id: 1, day_number: 1, date: '2026-05-02', title: null },
|
|
{ id: 12, trip_id: 1, day_number: 2, date: '2026-05-03', title: null },
|
|
] as unknown as Day[]
|
|
|
|
function todayIso(): string {
|
|
const now = new Date()
|
|
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
|
}
|
|
|
|
function renderShell(overrides: Partial<TripPlanner> = {}) {
|
|
mocks.planner = buildPlanner({
|
|
days: DAYS,
|
|
selectedDayId: 11,
|
|
TRIP_TABS,
|
|
...overrides,
|
|
} as Partial<TripPlanner>)
|
|
const view = render(
|
|
<MTripShell
|
|
PlanTimeline={slot('plan-timeline')}
|
|
MapArea={slot('map-area')}
|
|
PlacesBrowser={slot('places-browser')}
|
|
TabPanel={TabSlot}
|
|
Sheets={slot('sheets')}
|
|
/>,
|
|
)
|
|
return { ...view, planner: mocks.planner }
|
|
}
|
|
|
|
const spy = (planner: TripPlanner, name: keyof TripPlanner) =>
|
|
vi.mocked(planner[name] as unknown as ReturnType<typeof vi.fn>)
|
|
|
|
describe('MTripShell', () => {
|
|
beforeEach(() => {
|
|
sessionStorage.clear()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-001: shows the loading splash with the trip title while the planner loads', () => {
|
|
renderShell({ isLoading: true } as Partial<TripPlanner>)
|
|
expect(screen.getByText('Japan 2026')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('sheets')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-002: keeps the splash up until the photo warm-up finished', () => {
|
|
renderShell({ splashDone: false } as Partial<TripPlanner>)
|
|
expect(screen.getByText('Japan 2026')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('map-area')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-003: renders nothing once loading finished without a trip', () => {
|
|
const { container } = renderShell({ trip: null } as Partial<TripPlanner>)
|
|
expect(container).toBeEmptyDOMElement()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-003b: the splash falls back to the brand name before the trip arrives', () => {
|
|
renderShell({ isLoading: true, trip: null } as Partial<TripPlanner>)
|
|
expect(screen.getByText('TREK')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-004: seeds the running trip on today rather than on day one', () => {
|
|
const days = [
|
|
{ id: 11, day_number: 1, date: '2026-05-02' },
|
|
{ id: 12, day_number: 2, date: todayIso() },
|
|
] as unknown as Day[]
|
|
const { planner } = renderShell({ days, selectedDayId: null } as Partial<TripPlanner>)
|
|
expect(planner.tripActions.setSelectedDay).toHaveBeenCalledWith(12)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-005: falls back to the first day when the trip is not running', () => {
|
|
const { planner } = renderShell({ selectedDayId: null } as Partial<TripPlanner>)
|
|
expect(planner.tripActions.setSelectedDay).toHaveBeenCalledWith(11)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-006: leaves an existing day selection alone', () => {
|
|
const { planner } = renderShell()
|
|
expect(planner.tripActions.setSelectedDay).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-007: does not seed a day before the days arrive', () => {
|
|
const { planner } = renderShell({ days: [], selectedDayId: null } as Partial<TripPlanner>)
|
|
expect(planner.tripActions.setSelectedDay).not.toHaveBeenCalled()
|
|
expect(screen.queryByRole('button', { name: 'Sat 2' })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-008: the back button leaves for the dashboard', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'common.back' }))
|
|
expect(planner.navigate).toHaveBeenCalledWith('/dashboard')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-009: the plan tab mounts the map under the timeline and no browser', () => {
|
|
renderShell()
|
|
expect(screen.getByTestId('map-area')).toBeInTheDocument()
|
|
expect(screen.getByTestId('plan-timeline')).toBeInTheDocument()
|
|
expect(screen.getByTestId('sheets')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('places-browser')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('tab-panel')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-010: the browse segment swaps the timeline for the places browser', () => {
|
|
renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
expect(screen.getByTestId('places-browser')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('plan-timeline')).not.toBeInTheDocument()
|
|
expect(shellApi.mode).toBe('browse')
|
|
expect(shellApi.view).toBe('plan')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-011: browse entered from edit flags browseFromEdit for the pool filter', () => {
|
|
renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlan' }))
|
|
expect(shellApi.mode).toBe('edit')
|
|
expect(shellApi.browseFromEdit).toBe(false)
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
expect(shellApi.browseFromEdit).toBe(true)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-012: browse entered from travel leaves browseFromEdit off', () => {
|
|
renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
expect(shellApi.browseFromEdit).toBe(false)
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.travel' }))
|
|
expect(shellApi.mode).toBe('go')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-013: switching the travel mode from another tab forces the plan tab', () => {
|
|
const { planner } = renderShell({ activeTab: 'listen' } as Partial<TripPlanner>)
|
|
act(() => { shellApi.setTravelMode('edit') })
|
|
expect(planner.handleTabChange).toHaveBeenCalledWith('plan')
|
|
expect(shellApi.mode).toBe('edit')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-014: the map toggle draws the route and refits the selected day', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
|
expect(planner.handleSelectDay).toHaveBeenCalledWith(11, false)
|
|
expect(shellApi.view).toBe('map')
|
|
// The timeline unmounts, the map layer stays warm underneath.
|
|
expect(screen.queryByTestId('plan-timeline')).not.toBeInTheDocument()
|
|
expect(screen.getByTestId('map-area')).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-015: leaving the map restores the timeline without touching the route', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.listView' }))
|
|
expect(shellApi.view).toBe('plan')
|
|
expect(screen.getByTestId('plan-timeline')).toBeInTheDocument()
|
|
expect(spy(planner, 'setRouteShown')).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-016: the map toggle skips the refit while no day is selected', () => {
|
|
const { planner } = renderShell({ selectedDayId: null } as Partial<TripPlanner>)
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
|
expect(planner.handleSelectDay).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-017: the map toggle drops out of browse mode', () => {
|
|
renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
expect(shellApi.mode).toBe('go')
|
|
expect(screen.queryByTestId('places-browser')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-018: day chips render weekday + date and mark the active one', () => {
|
|
renderShell()
|
|
const chip = screen.getByRole('button', { name: 'Sat 2' })
|
|
expect(chip).toHaveAttribute('aria-current', 'true')
|
|
expect(screen.getByRole('button', { name: 'Sun 3' })).not.toHaveAttribute('aria-current')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-019: a chip without a usable date falls back to the day number', () => {
|
|
const days = [
|
|
{ id: 11, day_number: 1, date: null },
|
|
{ id: 12, day_number: 2, date: 'nodatehere' },
|
|
{ id: 13, date: null },
|
|
] as unknown as Day[]
|
|
renderShell({ days } as Partial<TripPlanner>)
|
|
expect(screen.getByRole('button', { name: 'planner.dayN:1' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'planner.dayN:2' })).toBeInTheDocument()
|
|
// A day the server sent without a number falls back to its position.
|
|
expect(screen.getByRole('button', { name: 'planner.dayN:3' })).toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-020: tapping another day selects it and fits the list view', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
|
expect(planner.handleSelectDay).toHaveBeenCalledWith(12, true)
|
|
expect(planner.setRouteShown).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-021: tapping the active day opens the day sheet', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Sat 2' }))
|
|
expect(shellApi.sheet).toEqual({ id: 'day', payload: { dayId: 11 } })
|
|
expect(planner.handleSelectDay).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-022: a day tap in map mode fits without the list flag and draws the route', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
vi.mocked(planner.setRouteShown).mockClear()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
|
expect(planner.handleSelectDay).toHaveBeenLastCalledWith(12, false)
|
|
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
|
})
|
|
|
|
// #1962 — the phone lost the desktop sidebar's collapse chevron, so the map showed
|
|
// every day's pins at once with no way to narrow it down.
|
|
it('FE-MOB-SHELL-040: entering the map focuses it on the selected day', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
expect(planner.setExpandedDayIds).toHaveBeenCalledWith(new Set([11]))
|
|
})
|
|
|
|
it('FE-MOB-SHELL-041: a day tap in map mode moves the focus with it', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
vi.mocked(planner.setExpandedDayIds).mockClear()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
|
expect(planner.setExpandedDayIds).toHaveBeenCalledWith(new Set([12]))
|
|
})
|
|
|
|
it('FE-MOB-SHELL-042: a day tap in list mode leaves the map unfiltered', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
|
expect(planner.setExpandedDayIds).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-043: leaving the map clears the focus again', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
|
vi.mocked(planner.setExpandedDayIds).mockClear()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.listView' }))
|
|
expect(planner.setExpandedDayIds).toHaveBeenCalledWith(null)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-023: the dock only shows enabled tabs and marks the active one', () => {
|
|
const { planner } = renderShell({
|
|
TRIP_TABS: [
|
|
{ id: 'plan', label: 'Plan' },
|
|
{ id: 'transports', label: 'Transport' },
|
|
// A plugin tab that arrived without a name still needs a label.
|
|
{ id: 'buchungen' },
|
|
],
|
|
} as Partial<TripPlanner>)
|
|
expect(screen.getByRole('button', { name: 'Plan' })).toHaveAttribute('aria-current', 'page')
|
|
expect(screen.getByRole('button', { name: 'buchungen' })).toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'Lists' })).not.toBeInTheDocument()
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Transport' }))
|
|
expect(planner.handleTabChange).toHaveBeenCalledWith('transports')
|
|
expect(shellApi.mode).toBe('go')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-024: a dock tap changes the tab and drops out of browse mode', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
expect(shellApi.mode).toBe('browse')
|
|
fireEvent.click(screen.getByRole('button', { name: 'Lists' }))
|
|
expect(planner.handleTabChange).toHaveBeenCalledWith('listen')
|
|
expect(shellApi.mode).toBe('go')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-025: the Mehr button opens the sheet and closeSheet clears it', () => {
|
|
renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.more' }))
|
|
expect(shellApi.sheet).toEqual({ id: 'mehr', payload: undefined })
|
|
act(() => { shellApi.closeSheet() })
|
|
expect(shellApi.sheet).toBeNull()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-026: a non-plan tab replaces the plan chrome with its panel', () => {
|
|
renderShell({ activeTab: 'dateien' } as Partial<TripPlanner>)
|
|
expect(screen.getByTestId('tab-panel')).toHaveTextContent('dateien')
|
|
expect(screen.queryByTestId('map-area')).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'mobileTrip.travel' })).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'mobileTrip.mapView' })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-027: the transports header opens a blank transport modal', () => {
|
|
const { planner } = renderShell({ activeTab: 'transports' } as Partial<TripPlanner>)
|
|
fireEvent.click(screen.getByRole('button', { name: 'transport.addTransport' }))
|
|
expect(planner.setEditingTransport).toHaveBeenCalledWith(null)
|
|
expect(planner.setTransitPrefill).toHaveBeenCalledWith(null)
|
|
expect(planner.setTransportModalAutomated).toHaveBeenCalledWith(false)
|
|
expect(planner.setShowTransportModal).toHaveBeenCalledWith(true)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-028: the transports header offers both importers when they are available', () => {
|
|
const { planner } = renderShell({ activeTab: 'transports', airTrailAvailable: true } as Partial<TripPlanner>)
|
|
fireEvent.click(screen.getByRole('button', { name: 'reservations.import.title' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'reservations.airtrail.title' }))
|
|
expect(planner.setShowBookingImport).toHaveBeenCalledWith(true)
|
|
expect(planner.setShowAirTrailImport).toHaveBeenCalledWith(true)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-029: the transports header hides the importers that are switched off', () => {
|
|
renderShell({
|
|
activeTab: 'transports', bookingImportAvailable: false, airTrailAvailable: false,
|
|
} as Partial<TripPlanner>)
|
|
expect(screen.queryByRole('button', { name: 'reservations.import.title' })).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'reservations.airtrail.title' })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('FE-MOB-SHELL-030: the transports compact toggle flips the shell flag', () => {
|
|
renderShell({ activeTab: 'transports' } as Partial<TripPlanner>)
|
|
const toggle = screen.getByRole('button', { name: 'mobileTrip.compactView' })
|
|
expect(toggle).toHaveAttribute('aria-pressed', 'false')
|
|
fireEvent.click(toggle)
|
|
expect(toggle).toHaveAttribute('aria-pressed', 'true')
|
|
expect(shellApi.transportsCompact).toBe(true)
|
|
expect(shellApi.bookingsCompact).toBe(false)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-031: the bookings header opens a blank reservation and toggles its own density', () => {
|
|
const { planner } = renderShell({ activeTab: 'buchungen' } as Partial<TripPlanner>)
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.newReservation' }))
|
|
expect(planner.setEditingReservation).toHaveBeenCalledWith(null)
|
|
expect(planner.setShowReservationModal).toHaveBeenCalledWith(true)
|
|
fireEvent.click(screen.getByRole('button', { name: 'reservations.import.title' }))
|
|
expect(planner.setShowBookingImport).toHaveBeenCalledWith(true)
|
|
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.compactView' }))
|
|
expect(shellApi.bookingsCompact).toBe(true)
|
|
expect(shellApi.transportsCompact).toBe(false)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-032: the costs header raises the add-expense and CSV signals', () => {
|
|
renderShell({ activeTab: 'finanzplan' } as Partial<TripPlanner>)
|
|
expect(shellApi.addExpenseSignal).toBe(0)
|
|
fireEvent.click(screen.getByRole('button', { name: 'costs.addExpense' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'budget.exportCsv' }))
|
|
expect(shellApi.addExpenseSignal).toBe(1)
|
|
expect(shellApi.exportCostsCsvSignal).toBe(1)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-033: the files header raises the upload and trash signals', () => {
|
|
renderShell({ activeTab: 'dateien' } as Partial<TripPlanner>)
|
|
fireEvent.click(screen.getByRole('button', { name: 'common.upload' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'files.trash' }))
|
|
fireEvent.click(screen.getByRole('button', { name: 'common.upload' }))
|
|
expect(shellApi.uploadFilesSignal).toBe(2)
|
|
expect(shellApi.openFilesTrashSignal).toBe(1)
|
|
})
|
|
|
|
it('FE-MOB-SHELL-034: the lists header shows packed/open counts and persists the sub-tab', () => {
|
|
const packingItems = [
|
|
{ id: 1, checked: true }, { id: 2, checked: false }, { id: 3, checked: true },
|
|
] as unknown as PackingItem[]
|
|
const todoItems = [{ id: 1, checked: false }, { id: 2, checked: false }] as unknown as TodoItem[]
|
|
renderShell({ activeTab: 'listen', packingItems, todoItems } as Partial<TripPlanner>)
|
|
expect(screen.getByRole('button', { name: /todo\.subtab\.packing/ })).toHaveTextContent('2/3')
|
|
const todoTab = screen.getByRole('button', { name: /todo\.subtab\.todo/ })
|
|
expect(todoTab).toHaveTextContent('mobileTrip.todoOpenCount:2')
|
|
|
|
fireEvent.click(todoTab)
|
|
expect(shellApi.listsTab).toBe('todo')
|
|
expect(sessionStorage.getItem('trip-lists-subtab-1')).toBe('todo')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-035: the lists sub-tab is restored from the session', () => {
|
|
sessionStorage.setItem('trip-lists-subtab-1', 'todo')
|
|
renderShell({ activeTab: 'listen' } as Partial<TripPlanner>)
|
|
expect(shellApi.listsTab).toBe('todo')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-036: the collab header switches the sub-tab', () => {
|
|
renderShell({ activeTab: 'collab' } as Partial<TripPlanner>)
|
|
expect(shellApi.collabTab).toBe('chat')
|
|
fireEvent.click(screen.getByRole('button', { name: 'collab.tabs.polls' }))
|
|
expect(shellApi.collabTab).toBe('polls')
|
|
fireEvent.click(screen.getByRole('button', { name: 'collab.tabs.notes' }))
|
|
expect(shellApi.collabTab).toBe('notes')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-037: setTrTab from a slot routes through the planner and resets browse', () => {
|
|
const { planner } = renderShell()
|
|
fireEvent.click(screen.getByRole('button', { name: 'trip.mobilePlaces' }))
|
|
act(() => { shellApi.setTrTab('collab') })
|
|
expect(planner.handleTabChange).toHaveBeenCalledWith('collab')
|
|
expect(shellApi.mode).toBe('go')
|
|
})
|
|
|
|
it('FE-MOB-SHELL-038: openSheet carries the payload through to the slots', () => {
|
|
renderShell()
|
|
act(() => { shellApi.openSheet('note', { dayId: 12 }) })
|
|
expect(shellApi.sheet).toEqual({ id: 'note', payload: { dayId: 12 } })
|
|
})
|
|
})
|
|
|
|
describe('buildShell fixture', () => {
|
|
it('FE-MOB-SHELL-039: defaults to the plan/go state and takes overrides', () => {
|
|
const shell = buildShell({ mode: 'browse', browseFromEdit: true })
|
|
expect(shell.view).toBe('plan')
|
|
expect(shell.trTab).toBe('plan')
|
|
expect(shell.mode).toBe('browse')
|
|
expect(shell.browseFromEdit).toBe(true)
|
|
})
|
|
})
|