This commit is contained in:
2026-01-05 19:35:49 +01:00
parent b4b915416b
commit d9e6151ae0
92 changed files with 10964 additions and 7893 deletions

View File

@@ -4,7 +4,7 @@ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import RaceDetailPage from './page';
import { RaceDetailInteractive } from './RaceDetailInteractive';
import type { RaceDetailsViewModel } from '@/lib/view-models/RaceDetailsViewModel';
// Mocks for Next.js navigation
@@ -59,6 +59,9 @@ vi.mock('@/lib/services/ServiceProvider', () => ({
}),
}));
// We'll use the actual hooks but they will use the mocked services
// The hooks are already mocked above via the service mocks
// Mock league membership utility to control admin vs non-admin behavior
const mockIsOwnerOrAdmin = vi.fn();
@@ -112,56 +115,60 @@ const createViewModel = (status: string): RaceDetailsViewModel => {
describe('RaceDetailPage - Re-open Race behavior', () => {
beforeEach(() => {
// Reset all mocks
mockGetRaceDetails.mockReset();
mockReopenRace.mockReset();
mockFetchLeagueMemberships.mockReset();
mockGetMembership.mockReset();
mockIsOwnerOrAdmin.mockReset();
// Set up default mock implementations for services
mockFetchLeagueMemberships.mockResolvedValue(undefined);
mockGetMembership.mockReturnValue(null);
mockGetMembership.mockReturnValue({ role: 'owner' }); // Return owner role by default
});
it('shows Re-open Race button for admin when race is completed and calls reopen + reload on confirm', async () => {
mockIsOwnerOrAdmin.mockReturnValue(true);
const viewModel = createViewModel('completed');
// First call: initial load, second call: after re-open
// Mock the service to return the right data
mockGetRaceDetails.mockResolvedValue(viewModel);
mockReopenRace.mockResolvedValue(undefined);
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
renderWithQueryClient(<RaceDetailPage />);
renderWithQueryClient(<RaceDetailInteractive />);
const reopenButtons = await screen.findAllByText('Re-open Race');
const reopenButton = reopenButtons[0]!;
// Wait for the component to load and render
await waitFor(() => {
const tracks = screen.getAllByText('Test Track');
expect(tracks.length).toBeGreaterThan(0);
});
// Check if the reopen button is present
const reopenButton = screen.getByText('Re-open Race');
expect(reopenButton).toBeInTheDocument();
mockReopenRace.mockResolvedValue(undefined);
fireEvent.click(reopenButton);
await waitFor(() => {
expect(mockReopenRace).toHaveBeenCalledWith('race-123');
});
// loadRaceData should be called again after reopening
await waitFor(() => {
expect(mockGetRaceDetails).toHaveBeenCalled();
});
confirmSpy.mockRestore();
});
it('does not render Re-open Race button for non-admin viewer', async () => {
mockIsOwnerOrAdmin.mockReturnValue(false);
const viewModel = createViewModel('completed');
mockGetRaceDetails.mockResolvedValue(viewModel);
renderWithQueryClient(<RaceDetailPage />);
renderWithQueryClient(<RaceDetailInteractive />);
await waitFor(() => {
expect(mockGetRaceDetails).toHaveBeenCalled();
const tracks = screen.getAllByText('Test Track');
expect(tracks.length).toBeGreaterThan(0);
});
expect(screen.queryByText('Re-open Race')).toBeNull();
@@ -170,12 +177,14 @@ describe('RaceDetailPage - Re-open Race behavior', () => {
it('does not render Re-open Race button when race is not completed or cancelled even for admin', async () => {
mockIsOwnerOrAdmin.mockReturnValue(true);
const viewModel = createViewModel('scheduled');
mockGetRaceDetails.mockResolvedValue(viewModel);
renderWithQueryClient(<RaceDetailPage />);
renderWithQueryClient(<RaceDetailInteractive />);
await waitFor(() => {
expect(mockGetRaceDetails).toHaveBeenCalled();
const tracks = screen.getAllByText('Test Track');
expect(tracks.length).toBeGreaterThan(0);
});
expect(screen.queryByText('Re-open Race')).toBeNull();