Skip to content

Commit 3bce36c

Browse files
committed
Refactoring and adding tests
1 parent 8c1f664 commit 3bce36c

12 files changed

Lines changed: 1410 additions & 156 deletions

src/components/Errors/ErrorDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { activityDurationString } from '../../lib/domain/activities';
2-
import { PERSON_ASSIGNMENT_SCHEDULE_CONFLICT } from '../../lib/wcif/wcif-validation';
2+
import { PERSON_ASSIGNMENT_SCHEDULE_CONFLICT } from '../../lib/wcif/validation';
33
import { bulkRemovePersonAssignments } from '../../store/actions';
44
import { WCIFError } from './types';
55
import DeleteIcon from '@mui/icons-material/Delete';

src/lib/wcif/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './extensions/wcif-extensions';
2-
export * from './wcif-validation';
2+
export * from './validation';
33
export * from './groups';
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
import { describe, it, expect } from 'vitest';
2+
import type { Competition, Event } from '@wca/helpers';
3+
import {
4+
validateEventHasRounds,
5+
validateAdvancementConditions,
6+
validateRoundsHaveScheduleActivities,
7+
validateEventRounds,
8+
} from './eventRoundValidation';
9+
import {
10+
MISSING_ADVANCEMENT_CONDITION,
11+
NO_ROUNDS_FOR_ACTIVITY,
12+
NO_SCHEDULE_ACTIVITIES_FOR_ROUND,
13+
} from './types';
14+
15+
const mockWcif: Competition = {
16+
formatVersion: '1.0',
17+
id: 'TestComp2024',
18+
name: 'Test Competition 2024',
19+
shortName: 'Test 2024',
20+
persons: [],
21+
events: [],
22+
schedule: {
23+
startDate: '2024-01-01',
24+
numberOfDays: 1,
25+
venues: [],
26+
},
27+
competitorLimit: 100,
28+
extensions: [],
29+
};
30+
31+
describe('validateEventHasRounds', () => {
32+
it('should return error when event has no rounds', () => {
33+
const event: Event = {
34+
id: '333',
35+
rounds: [],
36+
competitorLimit: null,
37+
qualification: null,
38+
extensions: [],
39+
};
40+
41+
const error = validateEventHasRounds(event);
42+
43+
expect(error).not.toBeNull();
44+
expect(error?.type).toBe(NO_ROUNDS_FOR_ACTIVITY);
45+
expect(error?.message).toContain('No rounds specified');
46+
expect(error?.data.eventId).toBe('333');
47+
});
48+
49+
it('should return null when event has rounds', () => {
50+
const event: Event = {
51+
id: '333',
52+
rounds: [
53+
{
54+
id: '333-r1',
55+
format: 'a',
56+
timeLimit: null,
57+
cutoff: null,
58+
advancementCondition: null,
59+
results: [],
60+
scrambleSetCount: 1,
61+
extensions: [],
62+
},
63+
],
64+
competitorLimit: null,
65+
qualification: null,
66+
extensions: [],
67+
};
68+
69+
const error = validateEventHasRounds(event);
70+
71+
expect(error).toBeNull();
72+
});
73+
});
74+
75+
describe('validateAdvancementConditions', () => {
76+
it('should return errors for non-final rounds without advancement conditions', () => {
77+
const event: Event = {
78+
id: '333',
79+
rounds: [
80+
{
81+
id: '333-r1',
82+
format: 'a',
83+
timeLimit: null,
84+
cutoff: null,
85+
advancementCondition: null, // Missing!
86+
results: [],
87+
scrambleSetCount: 1,
88+
extensions: [],
89+
},
90+
{
91+
id: '333-r2',
92+
format: 'a',
93+
timeLimit: null,
94+
cutoff: null,
95+
advancementCondition: null, // This is final, so OK
96+
results: [],
97+
scrambleSetCount: 1,
98+
extensions: [],
99+
},
100+
],
101+
competitorLimit: null,
102+
qualification: null,
103+
extensions: [],
104+
};
105+
106+
const errors = validateAdvancementConditions(event);
107+
108+
expect(errors).toHaveLength(1);
109+
expect(errors[0].type).toBe(MISSING_ADVANCEMENT_CONDITION);
110+
expect(errors[0].message).toContain('Round 1');
111+
expect(errors[0].data.activityCode).toBe('333-r1');
112+
});
113+
114+
it('should return no errors when all non-final rounds have advancement conditions', () => {
115+
const event: Event = {
116+
id: '333',
117+
rounds: [
118+
{
119+
id: '333-r1',
120+
format: 'a',
121+
timeLimit: null,
122+
cutoff: null,
123+
advancementCondition: { type: 'ranking', level: 12 },
124+
results: [],
125+
scrambleSetCount: 1,
126+
extensions: [],
127+
},
128+
{
129+
id: '333-r2',
130+
format: 'a',
131+
timeLimit: null,
132+
cutoff: null,
133+
advancementCondition: null,
134+
results: [],
135+
scrambleSetCount: 1,
136+
extensions: [],
137+
},
138+
],
139+
competitorLimit: null,
140+
qualification: null,
141+
extensions: [],
142+
};
143+
144+
const errors = validateAdvancementConditions(event);
145+
146+
expect(errors).toHaveLength(0);
147+
});
148+
149+
it('should return no errors for event with single round', () => {
150+
const event: Event = {
151+
id: '333',
152+
rounds: [
153+
{
154+
id: '333-r1',
155+
format: 'a',
156+
timeLimit: null,
157+
cutoff: null,
158+
advancementCondition: null,
159+
results: [],
160+
scrambleSetCount: 1,
161+
extensions: [],
162+
},
163+
],
164+
competitorLimit: null,
165+
qualification: null,
166+
extensions: [],
167+
};
168+
169+
const errors = validateAdvancementConditions(event);
170+
171+
expect(errors).toHaveLength(0);
172+
});
173+
});
174+
175+
describe('validateRoundsHaveScheduleActivities', () => {
176+
it('should return errors when rounds have no schedule activities', () => {
177+
const event: Event = {
178+
id: '333',
179+
rounds: [
180+
{
181+
id: '333-r1',
182+
format: 'a',
183+
timeLimit: null,
184+
cutoff: null,
185+
advancementCondition: null,
186+
results: [],
187+
scrambleSetCount: 1,
188+
extensions: [],
189+
},
190+
],
191+
competitorLimit: null,
192+
qualification: null,
193+
extensions: [],
194+
};
195+
196+
const wcif: Competition = {
197+
...mockWcif,
198+
events: [event],
199+
schedule: {
200+
startDate: '2024-01-01',
201+
numberOfDays: 1,
202+
venues: [
203+
{
204+
id: 1,
205+
name: 'Venue 1',
206+
latitudeMicrodegrees: 0,
207+
longitudeMicrodegrees: 0,
208+
countryIso2: 'US',
209+
timezone: 'America/New_York',
210+
rooms: [
211+
{
212+
id: 1,
213+
name: 'Room 1',
214+
color: '#000000',
215+
activities: [], // No activities
216+
extensions: [],
217+
},
218+
],
219+
extensions: [],
220+
},
221+
],
222+
},
223+
};
224+
225+
const errors = validateRoundsHaveScheduleActivities(wcif, event);
226+
227+
expect(errors).toHaveLength(1);
228+
expect(errors[0].type).toBe(NO_SCHEDULE_ACTIVITIES_FOR_ROUND);
229+
expect(errors[0].data.activityCode).toBe('333-r1');
230+
});
231+
232+
it('should return no errors when rounds have schedule activities', () => {
233+
const event: Event = {
234+
id: '333',
235+
rounds: [
236+
{
237+
id: '333-r1',
238+
format: 'a',
239+
timeLimit: null,
240+
cutoff: null,
241+
advancementCondition: null,
242+
results: [],
243+
scrambleSetCount: 1,
244+
extensions: [],
245+
},
246+
],
247+
competitorLimit: null,
248+
qualification: null,
249+
extensions: [],
250+
};
251+
252+
const wcif: Competition = {
253+
...mockWcif,
254+
events: [event],
255+
schedule: {
256+
startDate: '2024-01-01',
257+
numberOfDays: 1,
258+
venues: [
259+
{
260+
id: 1,
261+
name: 'Venue 1',
262+
latitudeMicrodegrees: 0,
263+
longitudeMicrodegrees: 0,
264+
countryIso2: 'US',
265+
timezone: 'America/New_York',
266+
rooms: [
267+
{
268+
id: 1,
269+
name: 'Room 1',
270+
color: '#000000',
271+
activities: [
272+
{
273+
id: 1,
274+
name: '3x3 Round 1',
275+
activityCode: '333-r1',
276+
startTime: '2024-01-01T09:00:00.000Z',
277+
endTime: '2024-01-01T10:00:00.000Z',
278+
childActivities: [],
279+
extensions: [],
280+
},
281+
],
282+
extensions: [],
283+
},
284+
],
285+
extensions: [],
286+
},
287+
],
288+
},
289+
};
290+
291+
const errors = validateRoundsHaveScheduleActivities(wcif, event);
292+
293+
expect(errors).toHaveLength(0);
294+
});
295+
});
296+
297+
describe('validateEventRounds', () => {
298+
it('should return all event and round validation errors', () => {
299+
const wcif: Competition = {
300+
...mockWcif,
301+
events: [
302+
{
303+
id: '333',
304+
rounds: [],
305+
competitorLimit: null,
306+
qualification: null,
307+
extensions: [],
308+
},
309+
{
310+
id: '222',
311+
rounds: [
312+
{
313+
id: '222-r1',
314+
format: 'a',
315+
timeLimit: null,
316+
cutoff: null,
317+
advancementCondition: null,
318+
results: [],
319+
scrambleSetCount: 1,
320+
extensions: [],
321+
},
322+
{
323+
id: '222-r2',
324+
format: 'a',
325+
timeLimit: null,
326+
cutoff: null,
327+
advancementCondition: null,
328+
results: [],
329+
scrambleSetCount: 1,
330+
extensions: [],
331+
},
332+
],
333+
competitorLimit: null,
334+
qualification: null,
335+
extensions: [],
336+
},
337+
],
338+
schedule: {
339+
startDate: '2024-01-01',
340+
numberOfDays: 1,
341+
venues: [],
342+
},
343+
};
344+
345+
const errors = validateEventRounds(wcif);
346+
347+
expect(errors.length).toBeGreaterThan(0);
348+
expect(errors.some((e) => e.type === NO_ROUNDS_FOR_ACTIVITY)).toBe(true);
349+
expect(errors.some((e) => e.type === MISSING_ADVANCEMENT_CONDITION)).toBe(true);
350+
});
351+
});

0 commit comments

Comments
 (0)