diff --git a/client/src/pages/DashboardPage.test.tsx b/client/src/pages/DashboardPage.test.tsx index d87279808..88f93cc49 100644 --- a/client/src/pages/DashboardPage.test.tsx +++ b/client/src/pages/DashboardPage.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { render, screen, waitFor } from '../../tests/helpers/render'; +import { render, screen, waitFor, within } from '../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { server } from '../../tests/helpers/msw/server'; @@ -943,4 +943,61 @@ describe('DashboardPage', () => { expect(useSettingsStore.getState().settings.dashboard_timezones).toBeUndefined(); }); }); + + describe('FE-PAGE-DASH-035: A trip the hero fell back to still appears in the grid (#1706)', () => { + const onlyTrips = (trips: unknown[]) => + server.use( + http.get('/api/trips', ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get('archived')) return HttpResponse.json({ trips: [] }); + return HttpResponse.json({ trips }); + }), + ); + // The grid, not the hero — the hero renders the same title and would mask the bug. + const grid = () => document.querySelector('.trips') as HTMLElement; + + it('lists a finished trip under Completed when it is the only one', async () => { + onlyTrips([buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' })]); + const user = userEvent.setup(); + render(); + + await user.click(screen.getByText('Completed')); + + await waitFor(() => expect(within(grid()).getByText('Lisbon 2025')).toBeInTheDocument()); + }); + + it('lists a dateless trip under Planned when it is the only one', async () => { + onlyTrips([buildTrip({ title: 'Someday Iceland', start_date: null, end_date: null })]); + render(); + + await waitFor(() => expect(within(grid()).getByText('Someday Iceland')).toBeInTheDocument()); + expect(screen.queryByText('No trips yet')).not.toBeInTheDocument(); + }); + + it('does not claim "No trips yet" when the only trip is simply finished', async () => { + onlyTrips([buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' })]); + render(); + + await waitFor(() => expect(screen.getAllByText('Lisbon 2025').length).toBeGreaterThan(0)); + expect(screen.queryByText('No trips yet')).not.toBeInTheDocument(); + }); + + it('still says "No trips yet" for an account with no trips at all', async () => { + onlyTrips([]); + render(); + + await waitFor(() => expect(screen.getByText('No trips yet')).toBeInTheDocument()); + }); + + it('still keeps a running trip out of the grid, so the hero does not double up', async () => { + onlyTrips([ + buildTrip({ title: 'Paris Adventure', start_date: '2026-07-01', end_date: '2026-07-10' }), + buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' }), + ]); + render(); + + await waitFor(() => expect(screen.getAllByText('Paris Adventure').length).toBeGreaterThan(0)); + expect(within(grid()).queryByText('Paris Adventure')).not.toBeInTheDocument(); + }); + }); }); diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 1120ba024..04f6a4279 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -220,7 +220,10 @@ function DashboardPageDesktop(): React.ReactElement { /> )} - {gridTrips.length === 0 && tripFilter === 'planned' && !isLoading && !loadError && ( + {/* "No trips yet" only when there really are none — a user whose trips are + all finished has a hero, and telling them to create their first trip is + simply wrong (#1706). Same condition the mobile dashboard already uses. */} + {gridTrips.length === 0 && !spotlight && tripFilter === 'planned' && !isLoading && !loadError && (
diff --git a/client/src/pages/dashboard/useDashboard.ts b/client/src/pages/dashboard/useDashboard.ts index c0223b59a..478ec9b89 100644 --- a/client/src/pages/dashboard/useDashboard.ts +++ b/client/src/pages/dashboard/useDashboard.ts @@ -93,11 +93,17 @@ export function useDashboard() { } const today = new Date().toISOString().split('T')[0] - const spotlight = trips.find(t => t.start_date && t.end_date && t.start_date <= today && t.end_date >= today) + // A trip the hero features on its own merit: one that is running, else the next + // one coming up. Only that one is taken out of the grid below, so the same trip + // isn't shown twice. + const featured = trips.find(t => t.start_date && t.end_date && t.start_date <= today && t.end_date >= today) || trips.find(t => t.start_date && t.start_date >= today) - || trips[0] || null - const rest = spotlight ? trips.filter(t => t.id !== spotlight.id) : trips + // With neither, the hero still shows something rather than sitting empty — but + // that trip is only borrowed for the header and must stay in the grid, or a user + // whose trips are all finished (or undated) sees "No trips yet". #1706 + const spotlight = featured || trips[0] || null + const rest = featured ? trips.filter(t => t.id !== featured.id) : trips // Pull the spotlight trip's members + places so the boarding pass can show // real buddies and place thumbnails instead of placeholders.