Skip to content

Commit bc6dafc

Browse files
authored
Merge pull request #68 from coder13/codex/increase-test-coverage-to-25%
Add tests to increase lib coverage
2 parents 3bce36c + 2e76222 commit bc6dafc

17 files changed

Lines changed: 883 additions & 191 deletions

src/lib/api/localStorage.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest';
2+
import { getLocalStorage, localStorageKey, setLocalStorage } from './localStorage';
3+
4+
afterEach(() => {
5+
localStorage.clear();
6+
vi.resetModules();
7+
});
8+
9+
describe('localStorage helpers', () => {
10+
it('prefixes keys with the OAuth client id', async () => {
11+
// Ensure staging values are used for deterministic keys
12+
Object.defineProperty(window, 'location', {
13+
value: { ...window.location, search: '?staging=true' },
14+
writable: true,
15+
});
16+
await import('./wca-env');
17+
18+
expect(localStorageKey('token')).toContain('delegate-dashboard.example-application-id.token');
19+
});
20+
21+
it('sets and retrieves data consistently', async () => {
22+
Object.defineProperty(window, 'location', {
23+
value: { ...window.location, search: '?staging=true' },
24+
writable: true,
25+
});
26+
await import('./wca-env');
27+
28+
setLocalStorage('token', 'abc123');
29+
expect(getLocalStorage('token')).toBe('abc123');
30+
});
31+
});

src/lib/api/wca-env.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest';
2+
3+
const setLocationSearch = (search: string) => {
4+
Object.defineProperty(window, 'location', {
5+
value: { ...window.location, search },
6+
writable: true,
7+
});
8+
};
9+
10+
afterEach(() => {
11+
vi.resetModules();
12+
});
13+
14+
describe('wca env helpers', () => {
15+
it('prefers staging configuration when staging query is present', async () => {
16+
setLocationSearch('?staging=true');
17+
const env = await import('./wca-env');
18+
19+
expect(env.STAGING_QUERY_PARAMS).toBe(true);
20+
expect(env.WCA_ORIGIN).toBe('https://staging.worldcubeassociation.org');
21+
expect(env.WCA_OAUTH_CLIENT_ID).toBe('example-application-id');
22+
});
23+
24+
it('falls back to environment variables when staging is absent', async () => {
25+
process.env.VITE_WCA_ORIGIN = 'https://prod.example';
26+
process.env.VITE_WCA_OAUTH_CLIENT_ID = 'prod-client';
27+
setLocationSearch('');
28+
29+
const env = await import('./wca-env');
30+
31+
expect(env.STAGING_QUERY_PARAMS).toBe(false);
32+
expect(env.WCA_ORIGIN).toBe('https://prod.example');
33+
expect(env.WCA_OAUTH_CLIENT_ID).toBe('prod-client');
34+
});
35+
});

src/lib/api/wca-env.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
console.log('env', import.meta.env);
21
const searchParams = new URLSearchParams(window.location.search);
32
export const STAGING_QUERY_PARAMS = searchParams.has('staging');
43

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { Activity, Competition, Person } from '@wca/helpers';
3+
import {
4+
balanceStartAndEndTimes,
5+
determineMissingGroupActivities,
6+
determineStageForAssignments,
7+
findCompetingAssignment,
8+
findStaffingAssignments,
9+
generateAssignments,
10+
generateMissingGroupActivities,
11+
ParsedAssignment,
12+
upsertCompetitorAssignments,
13+
} from './import';
14+
15+
const baseRound = (id: number, activityCode = '333-r1'): Activity => ({
16+
id,
17+
name: '333 Round 1',
18+
activityCode,
19+
startTime: '2024-01-01T10:00:00.000Z',
20+
endTime: '2024-01-01T11:00:00.000Z',
21+
childActivities: [],
22+
extensions: [],
23+
});
24+
25+
const groupActivity = (id: number, activityCode: string): Activity => ({
26+
id,
27+
name: activityCode,
28+
activityCode,
29+
startTime: '2024-01-01T10:00:00.000Z',
30+
endTime: '2024-01-01T10:15:00.000Z',
31+
childActivities: [],
32+
extensions: [],
33+
});
34+
35+
const buildCompetition = (rooms: any[], persons: Person[]): Competition => ({
36+
id: 'comp',
37+
shortName: 'comp',
38+
name: 'Comp',
39+
formatVersion: 'v1.0',
40+
competitorLimit: null,
41+
extensions: [],
42+
schedule: {
43+
startDate: '2024-01-01',
44+
numberOfDays: 1,
45+
venues: [
46+
{
47+
id: 1,
48+
name: 'Venue',
49+
latitudeMicrodegrees: 0,
50+
longitudeMicrodegrees: 0,
51+
countryIso2: 'US',
52+
timezone: 'America/New_York',
53+
rooms,
54+
extensions: [],
55+
},
56+
],
57+
},
58+
persons,
59+
events: [
60+
{
61+
id: '333',
62+
rounds: [
63+
{
64+
id: '333-r1',
65+
format: 'a',
66+
cutoff: null,
67+
timeLimit: null,
68+
advancementCondition: null,
69+
results: [],
70+
scrambleSetCount: 1,
71+
extensions: [],
72+
},
73+
],
74+
competitorLimit: null,
75+
qualification: null,
76+
extensions: [],
77+
},
78+
],
79+
});
80+
81+
const competitor = (overrides: Partial<Person>): Person => ({
82+
registrantId: 1,
83+
wcaUserId: 1,
84+
name: 'Alice',
85+
email: 'alice@example.com',
86+
countryIso2: 'US',
87+
assignments: [],
88+
registration: {
89+
wcaRegistrationId: 1,
90+
status: 'accepted',
91+
eventIds: ['333'],
92+
isCompeting: true,
93+
},
94+
extensions: [],
95+
...overrides,
96+
});
97+
98+
describe('import helpers', () => {
99+
it('parses competitor assignments across named stages', () => {
100+
const round = baseRound(1);
101+
const wcif = buildCompetition(
102+
[
103+
{ id: 10, name: 'A Stage', activities: [round], color: '#000' },
104+
{ id: 11, name: 'B Stage', activities: [round], color: '#111' },
105+
],
106+
[competitor({})]
107+
);
108+
109+
const assignment = findCompetingAssignment(
110+
wcif.schedule.venues[0].rooms,
111+
{ email: 'alice@example.com', '333': 'B 2' },
112+
wcif.persons[0],
113+
'333'
114+
);
115+
116+
expect(assignment).toEqual({
117+
registrantId: 1,
118+
eventId: '333',
119+
groupNumber: 2,
120+
activityCode: '333-r1-g2',
121+
assignmentCode: 'competitor',
122+
roomId: 11,
123+
});
124+
});
125+
126+
it('throws when no stage is provided with multiple rooms', () => {
127+
const wcif = buildCompetition(
128+
[
129+
{ id: 10, name: 'A Stage', activities: [baseRound(1)], color: '#000' },
130+
{ id: 11, name: 'B Stage', activities: [baseRound(2)], color: '#111' },
131+
],
132+
[competitor({})]
133+
);
134+
135+
expect(() =>
136+
findCompetingAssignment(wcif.schedule.venues[0].rooms, { email: 'alice', '333': '1' }, wcif.persons[0], '333')
137+
).toThrow('Stage data for competitor assignment is ambiguous');
138+
});
139+
140+
it('extracts staffing assignments with default judge role and stage aware ids', () => {
141+
const wcif = buildCompetition(
142+
[{ id: 10, name: 'Main Stage', activities: [baseRound(1)], color: '#000' }],
143+
[competitor({})]
144+
);
145+
146+
const assignments = findStaffingAssignments(
147+
wcif.schedule.venues[0].rooms,
148+
{
149+
meta: { fields: ['333', '333-staff'] },
150+
data: [{ email: 'alice@example.com', '333': '1', '333-staff': 'R1, 2;S3' }],
151+
},
152+
{ email: 'alice@example.com', '333': '1', '333-staff': 'R1, 2;S3' },
153+
wcif.persons[0],
154+
'333'
155+
);
156+
157+
expect(assignments).toEqual([
158+
{
159+
registrantId: 1,
160+
eventId: '333',
161+
roundNumber: 1,
162+
activityCode: '333-r1-g1',
163+
groupNumber: 1,
164+
assignmentCode: 'staff-runner',
165+
roomId: 10,
166+
},
167+
{
168+
registrantId: 1,
169+
eventId: '333',
170+
roundNumber: 1,
171+
activityCode: '333-r1-g2',
172+
groupNumber: 2,
173+
assignmentCode: 'staff-judge',
174+
roomId: 10,
175+
},
176+
{
177+
registrantId: 1,
178+
eventId: '333',
179+
roundNumber: 1,
180+
activityCode: '333-r1-g3',
181+
groupNumber: 3,
182+
assignmentCode: 'staff-scrambler',
183+
roomId: 10,
184+
},
185+
]);
186+
});
187+
188+
it('generates assignments from CSV rows', () => {
189+
const rooms = [{ id: 10, name: 'Main Stage', activities: [baseRound(1)], color: '#000' }];
190+
const person = competitor({});
191+
const wcif = buildCompetition(rooms, [person]);
192+
193+
const assignments = generateAssignments(wcif, {
194+
meta: { fields: ['email', '333', '333-staff'] },
195+
data: [
196+
{
197+
email: 'alice@example.com',
198+
'333': '1',
199+
'333-staff': 'J2',
200+
},
201+
],
202+
});
203+
204+
expect(assignments).toEqual([
205+
{
206+
registrantId: 1,
207+
eventId: '333',
208+
groupNumber: 1,
209+
activityCode: '333-r1-g1',
210+
assignmentCode: 'competitor',
211+
roomId: 10,
212+
},
213+
{
214+
registrantId: 1,
215+
eventId: '333',
216+
groupNumber: 2,
217+
activityCode: '333-r1-g2',
218+
assignmentCode: 'staff-judge',
219+
roomId: 10,
220+
roundNumber: 1,
221+
},
222+
]);
223+
});
224+
225+
it('reports missing group activities and fills stage information', () => {
226+
const round = {
227+
...baseRound(1),
228+
childActivities: [groupActivity(2, '333-r1-g1')],
229+
};
230+
const room = { id: 10, name: 'Main Stage', activities: [round], color: '#000' };
231+
const wcif = buildCompetition([room], [competitor({})]);
232+
233+
const assignments: ParsedAssignment[] = [
234+
{ registrantId: 1, eventId: '333', groupNumber: 2, activityCode: '333-r1-g2', assignmentCode: 'competitor', roomId: 10 },
235+
{ registrantId: 1, eventId: '333', groupNumber: 2, activityCode: '333-r1-g2', assignmentCode: 'staff-judge' },
236+
];
237+
238+
const missing = determineMissingGroupActivities(wcif, assignments);
239+
expect(missing).toEqual([{ activityCode: '333-r1-g2', roomId: 10 }]);
240+
241+
const stagedAssignments = determineStageForAssignments(wcif, assignments);
242+
expect(stagedAssignments[1].roomId).toBe(10);
243+
});
244+
245+
it('creates and balances missing group activities', () => {
246+
const round = { ...baseRound(1), childActivities: [] };
247+
const room = { id: 10, name: 'Main Stage', activities: [round], color: '#000' };
248+
const wcif = buildCompetition([room], [competitor({})]);
249+
250+
const withGroups = generateMissingGroupActivities(wcif, [
251+
{ activityCode: '333-r1-g1', roomId: 10 },
252+
{ activityCode: '333-r1-g2', roomId: 10 },
253+
]);
254+
255+
expect(withGroups.schedule.venues[0].rooms[0].activities[0].childActivities).toHaveLength(2);
256+
257+
const balanced = balanceStartAndEndTimes(withGroups, [
258+
{ activityCode: '333-r1-g1', roomId: 10 },
259+
{ activityCode: '333-r1-g2', roomId: 10 },
260+
]);
261+
262+
const [firstGroup, secondGroup] = balanced.schedule.venues[0].rooms[0].activities[0].childActivities;
263+
expect(firstGroup.startTime).toBe('2024-01-01T10:00:00.000Z');
264+
expect(secondGroup.startTime).toBe('2024-01-01T10:30:00.000Z');
265+
expect(secondGroup.endTime).toBe('2024-01-01T11:00:00.000Z');
266+
});
267+
268+
it('upserts competitor assignments without duplication', () => {
269+
const groups = [groupActivity(2, '333-r1-g1')];
270+
const round = { ...baseRound(1), childActivities: groups };
271+
const room = { id: 10, name: 'Main Stage', activities: [round], color: '#000' };
272+
const person = competitor({ assignments: [] });
273+
const wcif = buildCompetition([room], [person]);
274+
275+
const updated = upsertCompetitorAssignments(wcif, [
276+
{ registrantId: 1, eventId: '333', groupNumber: 1, activityCode: '333-r1-g1', assignmentCode: 'competitor', roomId: 10 },
277+
{ registrantId: 1, eventId: '333', groupNumber: 1, activityCode: '333-r1-g1', assignmentCode: 'competitor', roomId: 10 },
278+
]);
279+
280+
expect(updated.persons[0].assignments).toHaveLength(1);
281+
expect(updated.persons[0].assignments?.[0].assignmentCode).toBe('competitor');
282+
});
283+
});

0 commit comments

Comments
 (0)