diff --git a/client/src/components/Tours/TourDayRowFacts.test.tsx b/client/src/components/Tours/TourDayRowFacts.test.tsx index 355404b2d..b8d7b309e 100644 --- a/client/src/components/Tours/TourDayRowFacts.test.tsx +++ b/client/src/components/Tours/TourDayRowFacts.test.tsx @@ -86,9 +86,14 @@ describe('PAD-09 the check bench on a tour row', () => { expect(pill.getAttribute('aria-label')).toContain('A checklist is no safety or legal clearance.'); }); - it('PCD-002: missing obstacle data reads as such in the pill, never as a count', () => { + it('PCD-002: an area without obstacle data shows no pill at all, never a count', () => { render(); - expect(screen.getByTestId('plan-tour-paddle-check')).toHaveTextContent('No data'); + expect(screen.queryByTestId('plan-tour-paddle-check')).not.toBeInTheDocument(); + }); + + it('PCD-002b: data that could not be read still shows, without a count', () => { + render(); + expect(screen.getByTestId('plan-tour-paddle-check')).not.toHaveTextContent('3'); }); it.each([false, true])('PCD-003: the day plan facts name the bench as one more fact (mobile=%s)', (mobile) => { @@ -111,3 +116,31 @@ describe('PAD-09 the check bench on a tour row', () => { expect(screen.queryByTestId('plan-tour-paddle-check')).not.toBeInTheDocument(); }); }); + +describe('the compact facts of a tour list row', () => { + const testIds = () => + [...document.querySelectorAll('[data-testid^="plan-tour"], [data-testid="trip-plan-tour-time"]')].map((el) => + el.getAttribute('data-testid') + ); + const timed = { ...tour, distance: 4.2, elevation_gain: 478, planned_duration_minutes: 50 }; + + it('PCD-005: a hike keeps time, distance and climbing, in that order', () => { + render(); + expect(testIds()).toEqual(['trip-plan-tour-time', 'plan-tour-distance', 'plan-tour-ascent']); + }); + + it('PCD-006: a paddle tour puts its check bench second and leaves out the climbing', () => { + render(); + expect(testIds()).toEqual(['trip-plan-tour-time', 'plan-tour-paddle-check', 'plan-tour-distance']); + }); + + it('PCD-007: the day plan still shows every fact', () => { + render(); + expect(testIds()).toEqual([ + 'trip-plan-tour-time', + 'plan-tour-distance', + 'plan-tour-ascent', + 'plan-tour-paddle-check', + ]); + }); +}); diff --git a/client/src/components/Tours/TourDayRowFacts.tsx b/client/src/components/Tours/TourDayRowFacts.tsx index 68428d1d0..bda028676 100644 --- a/client/src/components/Tours/TourDayRowFacts.tsx +++ b/client/src/components/Tours/TourDayRowFacts.tsx @@ -141,18 +141,25 @@ export function TourDayRowPill({ ); } -/** A paddle tour's check bench as one fact (PAD-09), or null for a hike or a tour listed without one. */ +/** + * A paddle tour's check bench as one fact (PAD-09), or null for a hike, a tour + * listed without one, or an area without obstacle data: a row has no room for + * a badge that says nothing, and the checklist itself still names the gap. + */ function paddleCheckOf(tour: TourListItem, t: Parameters[1]) { - return tour.tour_type === 'kayak' && tour.paddle_check ? paddleCheckFact(tour.paddle_check, t) : null; + const check = tour.tour_type === 'kayak' ? tour.paddle_check : null; + return check && check.data !== 'none' ? paddleCheckFact(check, t) : null; } /** * A tour's facts on its day-plan row, in the badges every other stop uses: the * time it takes (the planned total where there is one, otherwise the walking * time, the parts in the tooltip), how far, how much climbing and, for a paddle - * tour, its check bench. + * tour, its check bench. `compact` (the tour list) keeps the three that matter + * most, in that order: the time, then a paddle tour's check bench and its + * distance, or a hike's distance and climbing. */ -export function TourDayRowPills({ tour }: { tour: TourListItem }) { +export function TourDayRowPills({ tour, compact = false }: { tour: TourListItem; compact?: boolean }) { const { t } = useTranslation(); const unit = useSettingsStore((state) => state.settings.distance_unit) || 'metric'; const times = tourPlannedTimes(tour); @@ -169,35 +176,37 @@ export function TourDayRowPills({ tour }: { tour: TourListItem }) { : null, ].filter((part): part is string => part !== null); const check = paddleCheckOf(tour, t); - return ( - <> - {shownMinutes != null && ( - - )} - {tour.distance != null && ( - - )} - {tour.elevation_gain != null && ( - - )} - {check && } - + const time = shownMinutes != null && ( + ); + const distance = tour.distance != null && ( + + ); + const ascent = tour.elevation_gain != null && ( + + ); + const bench = check && ; + if (!compact) return <>{[time, distance, ascent, bench]}; + // A paddle tour's climbing is the river's fall, nothing to plan by; its check bench is. + return <>{tour.tour_type === 'kayak' ? [time, bench, distance] : [time, distance, ascent]}; } export default function TourDayRowFacts({ tour, mobile = false }: { tour: TourListItem; mobile?: boolean }) { diff --git a/client/src/components/Tours/TourListRow.tsx b/client/src/components/Tours/TourListRow.tsx index a54d87709..aa4ab40ce 100644 --- a/client/src/components/Tours/TourListRow.tsx +++ b/client/src/components/Tours/TourListRow.tsx @@ -47,7 +47,7 @@ function MapToggleButton({ toggle }: { toggle: NonNullable {tour.name} - {/* One line like a plan row's: the badges keep their place, the description gives way, - and what does not fit fades out instead of ending at a hard edge. */} + {tour.description && ( + + {tour.description} + + )} + {/* One line of whole badges, most important first: one that does not fit wraps onto a + second line the fixed height hides, so a narrow sidebar shows two and never half of one. */}
- + - {tour.description && ( - - {tour.description} - - )}
@@ -108,9 +108,9 @@ function TourRowActions({ action, mapToggle, selected }: Pick { expect(row).toHaveClass('bg-surface-selected') }) - it('RS-01: trails the description after the badges on one line, as a plan row does', () => { + it('RS-01: puts the description on its own line between the name and the badges', () => { const listed = { ...tour, description: 'A quiet path above the lake', website: 'https://www.komoot.com/tour/42', planned_duration_minutes: 95 } render() const row = screen.getByRole('option', { name: /Selected ridge walk/i }) const line = within(row).getByTestId('tour-metrics') - expect(line).toHaveClass('flex-nowrap', 'overflow-hidden') + expect(line).toHaveClass('flex-wrap', 'h-6', 'overflow-hidden') expect(line).toHaveTextContent('1 h 35 min') - expect(within(line).getByTestId('tour-description-preview')).toHaveTextContent('A quiet path above the lake') - expect(within(line).getByTestId('tour-description-preview')).toHaveClass('truncate') + const description = within(row).getByTestId('tour-description-preview') + expect(line).not.toContainElement(description) + expect(description).toHaveTextContent('A quiet path above the lake') + expect(description).toHaveClass('truncate') + expect(description.compareDocumentPosition(line) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() // The information link belongs to the detail, not the list row. expect(row).not.toHaveTextContent('Komoot') })