diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index 5c5b3823..d9c58e40 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import { useLocalizedDate } from '../../hooks/useLocalizedDate'; -import { Loading } from '../../components/common'; +import { Button, Card, Loading } from '../../components/common'; import { PasswordResetModal, PublishGalleryDialog, SendGalleryEmailDialog, DuplicateEventDialog, EventRenameDialog, AdminGuestsList } from '../../components/admin'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { eventsService } from '../../services/events.service'; @@ -101,7 +101,7 @@ export const EventDetailsPage: React.FC = () => { }); // Fetch event details - const { data: event, isLoading: eventLoading, refetch: refetchEvent } = useQuery({ + const { data: event, isLoading: eventLoading, isError: eventError, refetch: refetchEvent } = useQuery({ queryKey: ['admin-event', id], queryFn: () => eventsService.getEvent(parseInt(id!)), enabled: !!id, @@ -324,7 +324,7 @@ export const EventDetailsPage: React.FC = () => { }, }); - if (eventLoading || !event) { + if (eventLoading) { return (
@@ -332,6 +332,19 @@ export const EventDetailsPage: React.FC = () => { ); } + // A 404 (or any settled failure) leaves `event` undefined forever — without + // this branch the spinner above never resolved (QA 7.02). + if (eventError || !event) { + return ( + +

{t('events.notFound', 'Event not found')}

+ +
+ ); + } + const expiresAtDate = safeParseDate(event.expires_at); // Timestamp comparison, not truncated whole days (#909): the old // differenceInDays <= 0 marked events "expired" up to 24h early. diff --git a/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx b/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx new file mode 100644 index 00000000..621f7cdb --- /dev/null +++ b/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx @@ -0,0 +1,84 @@ +/** + * /admin/events/:id hung on the spinner forever for a nonexistent id + * (QA 7.02). The backend returns a clean 404, but the page gated on + * `eventLoading || !event`, so once the query settled `event` stayed + * undefined and the condition never went false. + */ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +vi.mock('react-i18next', async () => { + const actual = await vi.importActual('react-i18next'); + return { + ...actual, + useTranslation: () => ({ + t: (k: string, fb?: unknown) => (typeof fb === 'string' ? fb : k), + i18n: { language: 'en' }, + }), + }; +}); + +vi.mock('react-toastify', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })); + +const getEvent = vi.fn(); +vi.mock('../../../services/events.service', () => ({ + eventsService: { + getEvent: (...args: unknown[]) => getEvent(...args), + updateEvent: vi.fn(), + deleteEvent: vi.fn(), + extendExpiration: vi.fn(), + duplicateEvent: vi.fn(), + resetPassword: vi.fn(), + publishEvent: vi.fn(), + renameEvent: vi.fn(), + }, +})); + +vi.mock('../../../hooks/usePublicSettings', () => ({ + PUBLIC_SETTINGS_QUERY_KEY: ['public-settings'], + usePublicSettings: () => ({ data: {} }), +})); + +vi.mock('../../../contexts/FeatureFlagsContext', () => ({ + useFeatureFlags: () => ({ flags: {}, isLoading: false }), + useFeatureEnabled: () => false, +})); + +vi.mock('../../../contexts/PermissionsContext', () => ({ + usePermissions: () => ({ hasAnyPermission: () => true, hasPermission: () => true, isLoading: false }), +})); + +import { EventDetailsPage } from '../EventDetailsPage'; + +function renderPage() { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + return render( + + + + } /> + events list
} /> + + + + ); +} + +describe('EventDetailsPage 404 handling (QA 7.02)', () => { + it('renders a not-found state instead of spinning forever when the event 404s', async () => { + getEvent.mockRejectedValue({ response: { status: 404, data: { error: 'Event not found' } } }); + + renderPage(); + + expect(screen.getByText('events.loadingEventDetails')).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText('Event not found')).toBeInTheDocument(); + }); + expect(screen.queryByText('events.loadingEventDetails')).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'events.backToEvents' })).toBeInTheDocument(); + }); +});