wip league admin tools
This commit is contained in:
@@ -5,7 +5,7 @@ import '@testing-library/jest-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import RaceDetailPage from './page';
|
||||
import { RaceDetailViewModel } from '@/lib/view-models/RaceDetailViewModel';
|
||||
import type { RaceDetailsViewModel } from '@/lib/view-models/RaceDetailsViewModel';
|
||||
|
||||
// Mocks for Next.js navigation
|
||||
const mockPush = vi.fn();
|
||||
@@ -40,7 +40,7 @@ vi.mock('@/components/sponsors/SponsorInsightsCard', () => ({
|
||||
}));
|
||||
|
||||
// Mock services hook to provide raceService and leagueMembershipService
|
||||
const mockGetRaceDetail = vi.fn();
|
||||
const mockGetRaceDetails = vi.fn();
|
||||
const mockReopenRace = vi.fn();
|
||||
const mockFetchLeagueMemberships = vi.fn();
|
||||
const mockGetMembership = vi.fn();
|
||||
@@ -48,7 +48,7 @@ const mockGetMembership = vi.fn();
|
||||
vi.mock('@/lib/services/ServiceProvider', () => ({
|
||||
useServices: () => ({
|
||||
raceService: {
|
||||
getRaceDetail: mockGetRaceDetail,
|
||||
getRaceDetails: mockGetRaceDetails,
|
||||
reopenRace: mockReopenRace,
|
||||
// other methods are not used in this test
|
||||
},
|
||||
@@ -79,8 +79,10 @@ const renderWithQueryClient = (ui: React.ReactElement) => {
|
||||
return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
const createViewModel = (status: string) => {
|
||||
return new RaceDetailViewModel({
|
||||
const createViewModel = (status: string): RaceDetailsViewModel => {
|
||||
const canReopenRace = status === 'completed' || status === 'cancelled';
|
||||
|
||||
return {
|
||||
race: {
|
||||
id: 'race-123',
|
||||
track: 'Test Track',
|
||||
@@ -88,10 +90,7 @@ const createViewModel = (status: string) => {
|
||||
scheduledAt: '2023-12-31T20:00:00Z',
|
||||
status,
|
||||
sessionType: 'race',
|
||||
strengthOfField: null,
|
||||
registeredCount: 0,
|
||||
maxParticipants: 32,
|
||||
} as any,
|
||||
},
|
||||
league: {
|
||||
id: 'league-1',
|
||||
name: 'Test League',
|
||||
@@ -100,19 +99,20 @@ const createViewModel = (status: string) => {
|
||||
maxDrivers: 32,
|
||||
qualifyingFormat: 'open',
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
entryList: [],
|
||||
registration: {
|
||||
isRegistered: false,
|
||||
isUserRegistered: false,
|
||||
canRegister: false,
|
||||
} as any,
|
||||
},
|
||||
userResult: null,
|
||||
}, 'driver-1');
|
||||
canReopenRace,
|
||||
};
|
||||
};
|
||||
|
||||
describe('RaceDetailPage - Re-open Race behavior', () => {
|
||||
beforeEach(() => {
|
||||
mockGetRaceDetail.mockReset();
|
||||
mockGetRaceDetails.mockReset();
|
||||
mockReopenRace.mockReset();
|
||||
mockFetchLeagueMemberships.mockReset();
|
||||
mockGetMembership.mockReset();
|
||||
@@ -127,7 +127,7 @@ describe('RaceDetailPage - Re-open Race behavior', () => {
|
||||
const viewModel = createViewModel('completed');
|
||||
|
||||
// First call: initial load, second call: after re-open
|
||||
mockGetRaceDetail.mockResolvedValue(viewModel);
|
||||
mockGetRaceDetails.mockResolvedValue(viewModel);
|
||||
|
||||
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('RaceDetailPage - Re-open Race behavior', () => {
|
||||
|
||||
// loadRaceData should be called again after reopening
|
||||
await waitFor(() => {
|
||||
expect(mockGetRaceDetail).toHaveBeenCalled();
|
||||
expect(mockGetRaceDetails).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
@@ -156,12 +156,12 @@ describe('RaceDetailPage - Re-open Race behavior', () => {
|
||||
it('does not render Re-open Race button for non-admin viewer', async () => {
|
||||
mockIsOwnerOrAdmin.mockReturnValue(false);
|
||||
const viewModel = createViewModel('completed');
|
||||
mockGetRaceDetail.mockResolvedValue(viewModel);
|
||||
mockGetRaceDetails.mockResolvedValue(viewModel);
|
||||
|
||||
renderWithQueryClient(<RaceDetailPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRaceDetail).toHaveBeenCalled();
|
||||
expect(mockGetRaceDetails).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Re-open Race')).toBeNull();
|
||||
@@ -170,12 +170,12 @@ 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');
|
||||
mockGetRaceDetail.mockResolvedValue(viewModel);
|
||||
mockGetRaceDetails.mockResolvedValue(viewModel);
|
||||
|
||||
renderWithQueryClient(<RaceDetailPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRaceDetail).toHaveBeenCalled();
|
||||
expect(mockGetRaceDetails).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Re-open Race')).toBeNull();
|
||||
|
||||
@@ -278,7 +278,7 @@ export default function RaceDetailPage() {
|
||||
const entryList: RaceDetailEntryViewModel[] = viewModel.entryList;
|
||||
const registration = viewModel.registration;
|
||||
const userResult: RaceDetailUserResultViewModel | null = viewModel.userResult;
|
||||
const raceSOF = null; // TODO: Add strengthOfField to RaceDetailRaceDTO
|
||||
const raceSOF = null; // TODO: Add strength of field to race details response
|
||||
|
||||
const config = statusConfig[race.status as keyof typeof statusConfig];
|
||||
const StatusIcon = config.icon;
|
||||
@@ -636,7 +636,7 @@ export default function RaceDetailPage() {
|
||||
{raceSOF ?? '—'}
|
||||
</p>
|
||||
</div>
|
||||
{/* TODO: Add registeredCount and maxParticipants to RaceDetailRaceDTO */}
|
||||
{/* TODO: Add registered count and max participants to race details response */}
|
||||
{/* {race.registeredCount !== undefined && (
|
||||
<div className="p-4 bg-deep-graphite rounded-lg">
|
||||
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Registered</p>
|
||||
|
||||
Reference in New Issue
Block a user