diff --git a/client/src/components/Map/LocationButton.tsx b/client/src/components/Map/LocationButton.tsx index f2591b192..d5fe5f4e2 100644 --- a/client/src/components/Map/LocationButton.tsx +++ b/client/src/components/Map/LocationButton.tsx @@ -69,14 +69,19 @@ export default function LocationButton({ mode, error, errorCode, onClick, bottom style={{ position: 'absolute', bottom: bottomOffset, - right: 12, + // The map's base-layer switcher sits 20px off the left edge; this mirrors it. + right: 20, zIndex: 1000, width: 42, height: 42, borderRadius: '50%', border: 'none', cursor: 'pointer', - background: isActive ? '#3b82f6' : 'var(--bg-card, white)', + // Off, it wears the same frosted glass as the map's other round controls; + // on, the location blue says it is tracking. + background: isActive ? '#3b82f6' : 'var(--sidebar-bg)', + backdropFilter: isActive ? undefined : 'blur(20px) saturate(180%)', + WebkitBackdropFilter: isActive ? undefined : 'blur(20px) saturate(180%)', color: isActive ? 'white' : (error ? '#ef4444' : 'var(--text-muted, #6b7280)'), boxShadow: MAP_CONTROL_SHADOW, display: 'flex', diff --git a/client/src/components/Map/MapCompassPill.test.tsx b/client/src/components/Map/MapCompassPill.test.tsx index 23128f0da..9a77479a7 100644 --- a/client/src/components/Map/MapCompassPill.test.tsx +++ b/client/src/components/Map/MapCompassPill.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-MAPCOMPASS-001 to FE-COMP-MAPCOMPASS-006 +// FE-COMP-MAPCOMPASS-001 to FE-COMP-MAPCOMPASS-007 import React from 'react' import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent, act } from '../../../tests/helpers/render' @@ -86,3 +86,17 @@ describe('MapCompassPill', () => { expect(button.style.background).toBe('transparent') }) }) + +describe('MapCompassPill on the phone map', () => { + it('FE-COMP-MAPCOMPASS-007: appears only while the map is turned off north', () => { + const { map, rotateTo } = fakeCompassMap(0) + render() + expect(screen.queryByRole('button', { name: 'Reset north' })).not.toBeInTheDocument() + + rotateTo(30) + expect(screen.getByRole('button', { name: 'Reset north' })).toBeInTheDocument() + + rotateTo(0) + expect(screen.queryByRole('button', { name: 'Reset north' })).not.toBeInTheDocument() + }) +}) diff --git a/client/src/components/Map/MapCompassPill.tsx b/client/src/components/Map/MapCompassPill.tsx index 056018676..d83699cd8 100644 --- a/client/src/components/Map/MapCompassPill.tsx +++ b/client/src/components/Map/MapCompassPill.tsx @@ -15,7 +15,12 @@ export interface CompassMap { * (GL only) and built as the SAME frosted shell (padding 4 around a 34px button) * so its height and transparency match the POI pill exactly. */ -export function MapCompassPill({ map }: { map: CompassMap }) { +export function MapCompassPill({ map, onlyWhenRotated = false }: { + map: CompassMap + /** Shown only while the map is turned off north, the way a native map's compass + * appears when it has something to undo (the phone map, where room is short). */ + onlyWhenRotated?: boolean +}) { const [bearing, setBearing] = useState(() => map.getBearing()) useEffect(() => { @@ -25,6 +30,8 @@ export function MapCompassPill({ map }: { map: CompassMap }) { return () => { map.off('rotate', update) } }, [map]) + if (onlyWhenRotated && Math.abs(bearing) < 0.5) return null + return (
({ const trackRef = useRef(null) const pill = useActivePill(trackRef, '[data-active="true"]', [value, options.length]) - return () => ro.disconnect() - }, [value, options.length]) - return (
mobileTabs.find(tab => tab.id === id)?.label ?? id - // While the trip runs, today's chip carries a ring and a button jumps back to it - // from wherever the rail was scrolled (#2392). Null outside the trip's dates. + // While the trip runs, today's chip carries a ring and a dot (#2392). That mark + // is the whole of it: a second button beside the rail only crowded the chrome. + // Null outside the trip's dates. const todayDayId = findTodayDayId(days) - const jumpToToday = () => { - if (todayDayId == null || todayDayId === planner.selectedDayId) return - if (view === 'map') selectDayOnMap(todayDayId) - else planner.handleSelectDay(todayDayId, true) - } const onDayChipTap = (dayId: number) => { if (dayId === planner.selectedDayId) openSheet('day', { dayId }) @@ -542,20 +538,6 @@ export default function MTripShell({ ) })}
- {/* Back to today from anywhere on the rail (#2392). Mounted for the whole - running trip and dimmed while today is already open, so it never - appears under the thumb and shoves the rail aside. */} - {todayDayId != null && ( - - )} {/* Drop the day filter and show the whole trip (#2257). Map only: the plan timeline and the road trip chain are both single-day, so there is nothing to widen there. Stays mounted while a day is active rather than diff --git a/client/src/mobile/screens/trip/map/MMapArea.tsx b/client/src/mobile/screens/trip/map/MMapArea.tsx index 299b5f479..0e973ba91 100644 --- a/client/src/mobile/screens/trip/map/MMapArea.tsx +++ b/client/src/mobile/screens/trip/map/MMapArea.tsx @@ -4,7 +4,6 @@ import { MapCompassPill, type CompassMap } from '../../../../components/Map/MapC import { MAP_LAYER_SWITCHER_INSET, MAP_ROUND_CONTROL_SIZE } from '../../../../components/Map/MapLayerSwitcher' import { TripRouteOverviewPill, TripRouteOverviewPanel } from '../../../../components/Map/TripRouteOverview' import { DawarichTrailPill } from '../../../../components/Map/DawarichTrailPill' -import { MPlacesFilterPill } from './MPlacesFilterPill' import PoiCategoryPill from '../../../../components/Map/PoiCategoryPill' import { usePoiExplore } from '../../../../components/Map/usePoiExplore' import { useMergedMapPois } from '../../../../components/Map/useMergedMapPois' @@ -453,27 +452,38 @@ export default function MMapArea({ planner, shell }: MMapAreaProps) {
)} - {/* Compass, GL maps only (Leaflet cannot rotate). Both engines draw the base-layer - switcher in the bottom left corner, so the compass stands on top of it, on the - same left edge: beside it the two read as one crowded row next to the dock. - The offsets are inline because they are computed from the switcher's own - numbers. */} - {mapActive && glMap && ( -
- + {/* The left column, over the base-layer switcher both engines draw in the bottom + left corner: the recorded Dawarich route, and the compass, which only shows + while the map is turned off north. Two round controls a side at most, one + frosted style and one 8px gap, so neither corner crowds the dock. The offsets + are inline because they are computed from the switcher's own numbers. */} + {mapActive && (glMap || planner.dawarichEnabled) && ( +
+ {/* Kept in road-trip mode: the recorded route beside the planned one is + exactly the comparison a drive invites. */} + {!onStage && planner.dawarichEnabled && ( + + )} + {glMap && }
)} - {/* Whole-trip overview (#1736): the stages stack above their toggle on the right, - clear of the round-controls band below it and of the base-layer switcher and - the compass, which both sit bottom left. The offset is Tailwind rather than - inline because the compass band is identified by being the one element with - an inline --bottom-nav-h, and a second would make that ambiguous. The places - filter closes the stack, under the toggles, so the overview card stays - right above its own toggle: it decides which pins the plan map shows, so it is - there whatever else is; the stage draws its own pins and has no use for it. */} + {/* The right column: the whole-trip overview (#1736) over the locate button, its + stages stacking above their toggle. 20px off the edge and 8px over the locate + button, mirroring the left column. The places filter has no button here: it + is set in the places tab, whose filter the map's pins follow. The offset is + Tailwind rather than inline because the compass band is identified by being + the one element with an inline --bottom-nav-h, and a second would make that + ambiguous. */} {mapActive && !onStage && ( -
+
{!planner.roadtripActive && planner.overviewActive && ( )} - {/* Kept in road-trip mode: the recorded route beside the planned one is - exactly the comparison a drive invites. */} - {planner.dawarichEnabled && ( - - )} - shell.openSheet('placesFilter')} />
)}
diff --git a/client/src/mobile/screens/trip/map/MPlacesFilterPill.tsx b/client/src/mobile/screens/trip/map/MPlacesFilterPill.tsx deleted file mode 100644 index 4a57109dd..000000000 --- a/client/src/mobile/screens/trip/map/MPlacesFilterPill.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { SlidersHorizontal } from 'lucide-react' -import { MapTogglePill } from '../../../../components/Map/MapTogglePill' -import { useTripStore } from '../../../../store/tripStore' -import { countActivePlacesFilters } from '../../../../utils/placesFilter' -import { useTranslation } from '../../../../i18n' - -/** - * The phone map's way into the places filter: a round control in the right-hand - * stack, lit and badged with how many filters are narrowing the markers. The sheet - * it opens is the shell's 'placesFilter' sheet, so the button announces a dialog - * rather than a pressed state. - */ -export function MPlacesFilterPill({ onOpen }: { onOpen: () => void }) { - const { t } = useTranslation() - const filter = useTripStore(s => s.placesFilter) - const categoryFilters = useTripStore(s => s.placesCategoryFilter) - const ratingFilter = useTripStore(s => s.placesRatingFilter) - const count = countActivePlacesFilters({ filter, categoryFilters, ratingFilter }) - const label = count > 0 ? `${t('places.filters')} (${count})` : t('places.filters') - return ( - 0} - onToggle={onOpen} - label={label} - badge={count} - testId="places-filter-pill" - opensDialog - icon={} - /> - ) -} diff --git a/client/src/mobile/screens/trip/places/MPlacesFilterSheet.tsx b/client/src/mobile/screens/trip/places/MPlacesFilterSheet.tsx deleted file mode 100644 index c25998d23..000000000 --- a/client/src/mobile/screens/trip/places/MPlacesFilterSheet.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { RotateCcw, X } from 'lucide-react' -import MSheet from '../../../components/MSheet' -import MIconBtn from '../../../components/MIconBtn' -import MChip from '../../../components/MChip' -import { useTripStore } from '../../../../store/tripStore' -import { countActivePlacesFilters } from '../../../../utils/placesFilter' -import { useTranslation } from '../../../../i18n' -import type { Category, Place } from '../../../../types' -import { MCategoryFilterList, MRatingFloorChips } from './MPlacesFilterControls' -import { placesFilterTabs, poolHasTracks } from '../../../../components/Planner/usePlacesPool' - -interface MPlacesFilterSheetProps { - open: boolean - onClose: () => void - places: Place[] - categories: Category[] - toursEnabled: boolean -} - -/** Uppercase eyebrow over each group of the sheet. */ -function SectionTitle({ children }: { children: string }) { - return ( -
{children}
- ) -} - -/** - * The places filter, opened from the phone map: which pool to show, a minimum - * rating and the trip's categories, with one button that lifts them all. Every - * choice lands in the trip store at once, so the markers behind the sheet and the - * places list change with it (#1541). Routed as shell sheet 'placesFilter'. - */ -export default function MPlacesFilterSheet({ open, onClose, places, categories, toursEnabled }: MPlacesFilterSheetProps) { - const { t } = useTranslation() - const filter = useTripStore(s => s.placesFilter) - const categoryFilters = useTripStore(s => s.placesCategoryFilter) - const ratingFilter = useTripStore(s => s.placesRatingFilter) - const setFilter = useTripStore(s => s.setPlacesFilter) - const resetPlacesFilters = useTripStore(s => s.resetPlacesFilters) - const activeCount = countActivePlacesFilters({ filter, categoryFilters, ratingFilter }) - - const pools = placesFilterTabs(t, poolHasTracks(places, toursEnabled)) - const hasCategoryChoice = categories.length > 0 || places.some(p => p.category_id == null) - - return ( - // Opaque: the sheet opens over the map, and the default bar glass let the map's - // place names show through the chips and category rows (seen at 390px wide). - -
-
{t('places.filters')}
- - - -
- -
-
- {t('places.filterShow')} -
- {pools.map(pool => ( - setFilter(pool.id)}> - {pool.label} - - ))} -
-
- -
- {t('places.filterByRating')} - -
- - {hasCategoryChoice && ( -
- {t('categories.title')} -
- -
-
- )} -
- -
- -
-
- ) -} diff --git a/client/src/mobile/screens/trip/sheets/MTripSheets.tsx b/client/src/mobile/screens/trip/sheets/MTripSheets.tsx index fdfcfdcbf..a67bc01ee 100644 --- a/client/src/mobile/screens/trip/sheets/MTripSheets.tsx +++ b/client/src/mobile/screens/trip/sheets/MTripSheets.tsx @@ -27,7 +27,6 @@ import MMehrSheet from './MMehrSheet' import MTripSettingsSheet from './MTripSettingsSheet' import MNewTripSheet from '../../dashboard/MNewTripSheet' import MAssistantScreen from '../assistant/MAssistantScreen' -import MPlacesFilterSheet from '../places/MPlacesFilterSheet' import MRtStopSheet from '../roadtrip/MRtStopSheet' import MRtStaySheet from '../roadtrip/MRtStaySheet' import MRtKindSheet from '../roadtrip/MRtKindSheet' @@ -145,13 +144,6 @@ export default function MTripSheets({ planner, shell }: MTripSheetsProps) { onClose={shell.closeSheet} /> - {/* ── Planner-flag editors (also serve ?create= and the import review) ── */} diff --git a/client/tests/unit/mobile/trip/MMapArea.test.tsx b/client/tests/unit/mobile/trip/MMapArea.test.tsx index 07de56a65..4b1400a2f 100644 --- a/client/tests/unit/mobile/trip/MMapArea.test.tsx +++ b/client/tests/unit/mobile/trip/MMapArea.test.tsx @@ -954,39 +954,9 @@ describe('MMapArea and a booked night at the edge of the stage', () => { }) describe('MMapArea — places filter', () => { - it('FE-MOB-MAPAREA-046: the plan map offers the places filter, and a tap opens its sheet', () => { - const { shell } = renderArea({ trTab: 'plan' }) - const pill = screen.getByTestId('places-filter-pill') - expect(pill).toHaveAccessibleName('Filters') - // It opens a sheet: a dialog to announce, not an on/off state. - expect(pill).toHaveAttribute('aria-haspopup', 'dialog') - expect(pill).not.toHaveAttribute('aria-pressed') - act(() => { pill.click() }) - expect(shell.openSheet).toHaveBeenCalledWith('placesFilter') - }) - - it('FE-MOB-MAPAREA-047: the pill lights up and counts the filters narrowing the pins', () => { + it('FE-MOB-MAPAREA-046: the map has no filter button of its own; the places tab sets the filter its pins follow', () => { seedStore(useTripStore, { placesFilter: 'unplanned', placesRatingFilter: 4 }) renderArea({ trTab: 'plan' }) - const pill = screen.getByTestId('places-filter-pill') - expect(pill).toHaveClass('text-accent') - expect(pill).not.toHaveAttribute('aria-pressed') - expect(pill).toHaveAttribute('aria-haspopup', 'dialog') - expect(pill).toHaveAccessibleName('Filters (2)') - expect(screen.getByText('2')).toHaveAttribute('aria-hidden', 'true') - }) - - it('FE-MOB-MAPAREA-049: a road trip keeps the places filter on its plan map, Dawarich or not', () => { - // The stack used to stand down in road-trip mode unless Dawarich had a pill in - // it; the plan map still shows the trip's places there, so its filter stays. - renderArea({ trTab: 'plan' }, { roadtripActive: true, dawarichEnabled: false }) - expect(screen.getByTestId('places-filter-pill')).toBeInTheDocument() - }) - - it('FE-MOB-MAPAREA-048: the stage draws its own pins, so it has no places filter, and neither has a covered map', () => { - renderArea({ trTab: 'roadtrip', mapFront: true }) - expect(screen.queryByTestId('places-filter-pill')).not.toBeInTheDocument() - renderArea({ view: 'plan', mapFront: false }) expect(screen.queryByTestId('places-filter-pill')).not.toBeInTheDocument() }) }) diff --git a/client/tests/unit/mobile/trip/MPlacesFilterSheet.test.tsx b/client/tests/unit/mobile/trip/MPlacesFilterSheet.test.tsx deleted file mode 100644 index cc237db43..000000000 --- a/client/tests/unit/mobile/trip/MPlacesFilterSheet.test.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { fireEvent, render, screen, within } from '../../../helpers/render' -import MPlacesFilterSheet from '../../../../src/mobile/screens/trip/places/MPlacesFilterSheet' -import { useTripStore } from '../../../../src/store/tripStore' -import { resetAllStores, seedStore } from '../../../helpers/store' -import { buildPlace } from '../../../helpers/factories' -import type { Category, Place } from '../../../../src/types' - -// FE-MOB-PFSHEET-001 to FE-MOB-PFSHEET-009 - -const CATEGORIES = [ - { id: 1, name: 'Sights', color: '#123456', icon: 'landmark' }, - { id: 2, name: 'Food', color: '', icon: 'utensils' }, -] as unknown as Category[] - -const TAGGED = buildPlace({ id: 1, category_id: 1 }) -const BARE = buildPlace({ id: 2, category_id: null }) -const TRACK = buildPlace({ id: 3, category_id: 2, route_geometry: '[[1,2],[3,4]]' }) - -function renderSheet({ places = [TAGGED, BARE], categories = CATEGORIES, open = true, toursEnabled = false }: { - places?: Place[] - categories?: Category[] - open?: boolean - toursEnabled?: boolean -} = {}) { - const onClose = vi.fn() - render() - return { onClose } -} - -const group = (name: string) => screen.getByRole('group', { name }) - -describe('MPlacesFilterSheet', () => { - beforeEach(() => { - resetAllStores() - }) - - it('FE-MOB-PFSHEET-001: shows the pool, the rating floors and the categories under one title', async () => { - renderSheet() - const sheet = await screen.findByRole('dialog', { name: 'Filters' }) - expect(within(sheet).getByText('Show')).toBeInTheDocument() - expect(within(group('Show')).getAllByRole('button').map(b => b.textContent)).toEqual(['All', 'Unplanned', 'Planned']) - expect(within(group('Filter by rating')).getAllByRole('button').map(b => b.textContent)) - .toEqual(['All', '5+', '4+', '3+', '2+', '1+']) - expect(screen.getByRole('checkbox', { name: 'Sights' })).toBeInTheDocument() - expect(screen.getByRole('checkbox', { name: 'Food' })).toBeInTheDocument() - expect(screen.getByRole('checkbox', { name: 'No Category' })).toBeInTheDocument() - }) - - it('FE-MOB-PFSHEET-002: the tracks pool only shows once a place carries a track', async () => { - renderSheet({ places: [TAGGED, TRACK] }) - await screen.findByRole('dialog', { name: 'Filters' }) - expect(within(group('Show')).getByRole('button', { name: 'Tracks' })).toBeInTheDocument() - expect(screen.queryByRole('checkbox', { name: 'No Category' })).not.toBeInTheDocument() - }) - - it('hides Tracks from the map filter while Tours is enabled', async () => { - const tourTrack = buildPlace({ id: 4, route_geometry: '[[5,6],[7,8]]' }) - renderSheet({ places: [TAGGED, TRACK, tourTrack], toursEnabled: true }) - await screen.findByRole('dialog', { name: 'Filters' }) - expect(within(group('Show')).queryByRole('button', { name: 'Tracks' })).not.toBeInTheDocument() - }) - - it('FE-MOB-PFSHEET-003: picking a pool writes it to the trip store', async () => { - renderSheet() - await screen.findByRole('dialog', { name: 'Filters' }) - const show = group('Show') - expect(within(show).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'true') - fireEvent.click(within(show).getByRole('button', { name: 'Unplanned' })) - expect(useTripStore.getState().placesFilter).toBe('unplanned') - // The chosen chip says so to a screen reader, and only that one. - expect(within(show).getByRole('button', { name: 'Unplanned' })).toHaveAttribute('aria-pressed', 'true') - expect(within(show).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'false') - }) - - it('FE-MOB-PFSHEET-004: picking a floor writes the rating filter to the trip store', async () => { - renderSheet() - await screen.findByRole('dialog', { name: 'Filters' }) - const floors = group('Filter by rating') - fireEvent.click(within(floors).getByRole('button', { name: '3+' })) - expect(useTripStore.getState().placesRatingFilter).toBe(3) - expect(within(floors).getByRole('button', { name: '3+' })).toHaveAttribute('aria-pressed', 'true') - expect(within(floors).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'false') - }) - - it('FE-MOB-PFSHEET-005: categories toggle in and out of the shared set, "no category" included', async () => { - renderSheet() - await screen.findByRole('dialog', { name: 'Filters' }) - fireEvent.click(screen.getByRole('checkbox', { name: 'Food' })) - fireEvent.click(screen.getByRole('checkbox', { name: 'No Category' })) - expect([...useTripStore.getState().placesCategoryFilter]).toEqual(['2', 'uncategorized']) - expect(screen.getByRole('checkbox', { name: 'Food' })).toHaveAttribute('aria-checked', 'true') - fireEvent.click(screen.getByRole('checkbox', { name: 'Food' })) - expect([...useTripStore.getState().placesCategoryFilter]).toEqual(['uncategorized']) - }) - - it('FE-MOB-PFSHEET-006: reset lifts every filter, and stays off while nothing filters', async () => { - seedStore(useTripStore, { placesFilter: 'planned', placesCategoryFilter: new Set(['1']), placesRatingFilter: 5 }) - renderSheet() - await screen.findByRole('dialog', { name: 'Filters' }) - const reset = screen.getByRole('button', { name: 'Reset' }) - expect(reset).toBeEnabled() - fireEvent.click(reset) - const s = useTripStore.getState() - expect(s.placesFilter).toBe('all') - expect(s.placesCategoryFilter.size).toBe(0) - expect(s.placesRatingFilter).toBe('all') - expect(screen.getByRole('button', { name: 'Reset' })).toBeDisabled() - }) - - it('FE-MOB-PFSHEET-007: without any category to choose, the categories section is left out', async () => { - renderSheet({ places: [TAGGED], categories: [] }) - const sheet = await screen.findByRole('dialog', { name: 'Filters' }) - expect(within(sheet).queryByText('Categories')).not.toBeInTheDocument() - expect(screen.queryByRole('checkbox')).not.toBeInTheDocument() - }) - - it('FE-MOB-PFSHEET-008: the close button hands back to the caller', async () => { - const { onClose } = renderSheet() - fireEvent.click(within(await screen.findByRole('dialog', { name: 'Filters' })).getByRole('button', { name: 'Close' })) - expect(onClose).toHaveBeenCalledTimes(1) - }) - - it('FE-MOB-PFSHEET-009: nothing is rendered while closed', () => { - renderSheet({ open: false }) - expect(screen.queryByRole('dialog', { name: 'Filters' })).not.toBeInTheDocument() - }) -}) diff --git a/client/tests/unit/mobile/trip/MTripSheets.test.tsx b/client/tests/unit/mobile/trip/MTripSheets.test.tsx index 2c939d709..d5d3edb14 100644 --- a/client/tests/unit/mobile/trip/MTripSheets.test.tsx +++ b/client/tests/unit/mobile/trip/MTripSheets.test.tsx @@ -11,7 +11,7 @@ import { useSettingsStore } from '../../../../src/store/settingsStore' import { useTripStore } from '../../../../src/store/tripStore' import { buildPlanner, buildShell } from '../../../helpers/mobileTrip' import { resetAllStores, seedStore } from '../../../helpers/store' -import { fireEvent, render, screen, waitFor, within } from '../../../helpers/render' +import { fireEvent, render, screen, waitFor } from '../../../helpers/render' // FE-MOB-SHOST-001 to FE-MOB-SHOST-029 and FE-MOB-SHOST-032 to FE-MOB-SHOST-034 (030 and 031 live in MTripSheets.members.test.tsx) // @@ -240,15 +240,8 @@ describe('MTripSheets', () => { } }) - it('FE-MOB-SHOST-033: the map\'s places filter opens for its own id and closes through the shell', async () => { - const { shell } = renderHost({}, { sheet: { id: 'placesFilter' } }) - const sheet = await screen.findByRole('dialog', { name: 'Filters' }) - fireEvent.click(within(sheet).getByRole('button', { name: 'Close' })) - expect(shell.closeSheet).toHaveBeenCalledTimes(1) - }) - - it('FE-MOB-SHOST-033b: no places filter while another sheet is open', () => { - renderHost({}, { sheet: { id: 'day' } }) + it('FE-MOB-SHOST-033: hosts no places filter of its own any more; the places tab carries it', () => { + renderHost({}, { sheet: { id: 'placesFilter' } }) expect(screen.queryByRole('dialog', { name: 'Filters' })).not.toBeInTheDocument() }) diff --git a/client/tests/unit/mobile/trip/MTripShell.test.tsx b/client/tests/unit/mobile/trip/MTripShell.test.tsx index 085e79b57..36275a99f 100644 --- a/client/tests/unit/mobile/trip/MTripShell.test.tsx +++ b/client/tests/unit/mobile/trip/MTripShell.test.tsx @@ -668,31 +668,25 @@ describe('MTripShell', () => { { id: 22, day_number: 2, date: todayIso() }, { id: 23, day_number: 3, date: shift(1) }, ] as unknown as Day[] - const jump = () => screen.getByRole('button', { name: 'mobileTrip.jumpToToday' }) + const jump = () => screen.queryByRole('button', { name: 'mobileTrip.jumpToToday' }) - it('FE-MOB-SHELL-090: marks today and jumps back to it from another day', () => { - const { planner, container } = renderShell({ days: running(), selectedDayId: 21 } as Partial) + it('FE-MOB-SHELL-090: marks today on its chip, and puts no extra button beside the rail', () => { + const { container } = renderShell({ days: running(), selectedDayId: 21 } as Partial) const marked = container.querySelectorAll('[data-today]') expect(marked).toHaveLength(1) expect(marked[0].className).toContain('ring-inset') expect(marked[0]).toHaveTextContent('mobileTrip.today') - - expect(jump()).toBeEnabled() - fireEvent.click(jump()) - expect(planner.handleSelectDay).toHaveBeenLastCalledWith(22, true) + expect(jump()).toBeNull() }) - it('FE-MOB-SHELL-091: with today open the button stays, dimmed, so the rail does not move', () => { + it('FE-MOB-SHELL-091: with today open the active chip keeps its own look, the dot says which day is today', () => { const { container } = renderShell({ days: running(), selectedDayId: 22 } as Partial) - expect(jump()).toBeDisabled() - // The active chip keeps its own look; the dot still says which day is today. expect(container.querySelector('[data-today]')?.className).not.toContain('ring-inset') }) - it('FE-MOB-SHELL-092: outside the trip dates there is no today and no button', () => { + it('FE-MOB-SHELL-092: outside the trip dates there is no today', () => { const { container } = renderShell() expect(container.querySelector('[data-today]')).toBeNull() - expect(screen.queryByRole('button', { name: 'mobileTrip.jumpToToday' })).toBeNull() }) })