mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(dashboard): keep a trip the hero fell back to in the grid
The hero picks the trip that is running, else the next one coming up, else just trips[0] — and that pick was then removed from the grid below. The first two cases are deliberate: the trip is already on screen, showing it again right underneath would be noise. The trips[0] fallback is a different thing. It fires precisely when nothing qualifies, so it borrows a trip that belongs in the grid, and removing it there leaves the user with a hero and an empty list. Someone whose trips are all finished sees "No trips yet" under a card of the trip they just came back from. Split the two: only a trip that earned the hero is taken out of the grid. Undated trips were caught by this too, and worse — getTripStatus returns null rather than 'past' for them, so a lone dateless trip appeared in no tab at all. The empty state was the second half of the report. It equated "planned is empty" with "you have no trips" and told people to create their first one. Gate it on there being no trip at all, which is the condition the mobile dashboard already uses. Mobile shares useDashboard, so it is fixed along with the desktop. Closes #1706
This commit is contained in:
@@ -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(<DashboardPage />);
|
||||
|
||||
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(<DashboardPage />);
|
||||
|
||||
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(<DashboardPage />);
|
||||
|
||||
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(<DashboardPage />);
|
||||
|
||||
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(<DashboardPage />);
|
||||
|
||||
await waitFor(() => expect(screen.getAllByText('Paris Adventure').length).toBeGreaterThan(0));
|
||||
expect(within(grid()).queryByText('Paris Adventure')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 && (
|
||||
<div className="trips-empty">
|
||||
<EmptyState scene="dashboard" title={t('dashboard.emptyTitle')} />
|
||||
</div>
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user