resolve todos in website
This commit is contained in:
@@ -85,4 +85,9 @@ export class RacesApiClient extends BaseApiClient {
|
||||
complete(raceId: string): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/complete`, {});
|
||||
}
|
||||
|
||||
/** Re-open race */
|
||||
reopen(raceId: string): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/reopen`, {});
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,12 @@ describe('RaceService', () => {
|
||||
getDetail: vi.fn(),
|
||||
getPageData: vi.fn(),
|
||||
getTotal: vi.fn(),
|
||||
} as Mocked<RacesApiClient>;
|
||||
register: vi.fn(),
|
||||
withdraw: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
complete: vi.fn(),
|
||||
reopen: vi.fn(),
|
||||
} as unknown as Mocked<RacesApiClient>;
|
||||
|
||||
service = new RaceService(mockApiClient);
|
||||
});
|
||||
@@ -131,4 +136,22 @@ describe('RaceService', () => {
|
||||
await expect(service.getRacesTotal()).rejects.toThrow('API call failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('reopenRace', () => {
|
||||
it('should call apiClient.reopen with raceId', async () => {
|
||||
const raceId = 'race-123';
|
||||
|
||||
await service.reopenRace(raceId);
|
||||
|
||||
expect(mockApiClient.reopen).toHaveBeenCalledWith(raceId);
|
||||
});
|
||||
|
||||
it('should propagate errors from apiClient.reopen', async () => {
|
||||
const raceId = 'race-123';
|
||||
const error = new Error('API call failed');
|
||||
mockApiClient.reopen.mockRejectedValue(error);
|
||||
|
||||
await expect(service.reopenRace(raceId)).rejects.toThrow('API call failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -78,6 +78,12 @@ export class RaceService {
|
||||
await this.apiClient.complete(raceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-open a race
|
||||
*/
|
||||
async reopenRace(raceId: string): Promise<void> {
|
||||
await this.apiClient.reopen(raceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find races by league ID
|
||||
|
||||
@@ -14,6 +14,9 @@ export class ProtestViewModel {
|
||||
status: string;
|
||||
reviewedAt?: string;
|
||||
decisionNotes?: string;
|
||||
incident?: { lap?: number } | null;
|
||||
proofVideoUrl?: string | null;
|
||||
comment?: string | null;
|
||||
|
||||
constructor(dto: ProtestDTO) {
|
||||
this.id = dto.id;
|
||||
|
||||
@@ -252,6 +252,36 @@ describe('RaceDetailViewModel', () => {
|
||||
expect(viewModel.registrationStatusMessage).toBe('Registration not available');
|
||||
});
|
||||
|
||||
it('should expose canReopenRace for completed and cancelled statuses', () => {
|
||||
const completedVm = new RaceDetailViewModel({
|
||||
race: createMockRace({ status: 'completed' }),
|
||||
league: createMockLeague(),
|
||||
entryList: [],
|
||||
registration: createMockRegistration(),
|
||||
userResult: null,
|
||||
});
|
||||
|
||||
const cancelledVm = new RaceDetailViewModel({
|
||||
race: createMockRace({ status: 'cancelled' as any }),
|
||||
league: createMockLeague(),
|
||||
entryList: [],
|
||||
registration: createMockRegistration(),
|
||||
userResult: null,
|
||||
});
|
||||
|
||||
const upcomingVm = new RaceDetailViewModel({
|
||||
race: createMockRace({ status: 'upcoming' }),
|
||||
league: createMockLeague(),
|
||||
entryList: [],
|
||||
registration: createMockRegistration(),
|
||||
userResult: null,
|
||||
});
|
||||
|
||||
expect(completedVm.canReopenRace).toBe(true);
|
||||
expect(cancelledVm.canReopenRace).toBe(true);
|
||||
expect(upcomingVm.canReopenRace).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle error property', () => {
|
||||
const viewModel = new RaceDetailViewModel({
|
||||
race: createMockRace(),
|
||||
|
||||
@@ -70,4 +70,10 @@ export class RaceDetailViewModel {
|
||||
if (this.canRegister) return 'You can register for this race';
|
||||
return 'Registration not available';
|
||||
}
|
||||
|
||||
/** UI-specific: Whether race can be re-opened */
|
||||
get canReopenRace(): boolean {
|
||||
if (!this.race) return false;
|
||||
return this.race.status === 'completed' || this.race.status === 'cancelled';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user