Skip to content

Commit 57e5213

Browse files
committed
add participation condition typing and stories
1 parent ef9d104 commit 57e5213

6 files changed

Lines changed: 168 additions & 65 deletions

File tree

src/components/CutoffTimeLimitPanel/CutoffTimeLimitPanel.stories.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react';
22
import {
33
getStorybookRoundFixture,
44
makeStorybookCompetitionFixtureWithRound,
5+
storybookParticipationConditionLinkedRoundsFixture,
6+
storybookParticipationConditionPercentFixture,
57
} from '@/storybook/competitionFixtures';
68
import { makeCompetitionContainerDecorator } from '@/storybook/competitionStorybook';
79
import { CutoffTimeLimitPanel } from './CutoffTimeLimitPanel';
@@ -53,6 +55,24 @@ export const RankingAdvancement: Story = {
5355
},
5456
};
5557

58+
export const ParticipationConditionPercent: Story = {
59+
parameters: {
60+
competitionFixture: storybookParticipationConditionPercentFixture,
61+
},
62+
args: {
63+
round: storybookParticipationConditionPercentFixture.events[0].rounds[0],
64+
},
65+
};
66+
67+
export const ParticipationConditionLinkedRounds: Story = {
68+
parameters: {
69+
competitionFixture: storybookParticipationConditionLinkedRoundsFixture,
70+
},
71+
args: {
72+
round: storybookParticipationConditionLinkedRoundsFixture.events[0].rounds[1],
73+
},
74+
};
75+
5676
export const CutoffAndTimeLimit: Story = {
5777
args: {
5878
round: getStorybookRoundFixture('222-r1'),

src/containers/CompetitionRound/CompetitionRound.stories.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react';
22
import {
33
makeStorybookCompetitionFixtureWithRound,
44
makeStorybookEventCompetitionFixture,
5+
storybookParticipationConditionLinkedRoundsFixture,
6+
storybookParticipationConditionPercentFixture,
57
} from '@/storybook/competitionFixtures';
68
import { makeCompetitionContainerDecorator } from '@/storybook/competitionStorybook';
79
import { CompetitionRoundContainer } from './CompetitionRound';
@@ -24,13 +26,33 @@ export const RoundOne: Story = {
2426
},
2527
};
2628

29+
export const ParticipationConditionPercent: Story = {
30+
parameters: {
31+
competitionFixture: storybookParticipationConditionPercentFixture,
32+
},
33+
args: {
34+
competitionId: 'SeattleSummerOpen2026',
35+
roundId: '333-r1',
36+
},
37+
};
38+
2739
export const RoundTwo: Story = {
2840
args: {
2941
competitionId: 'SeattleSummerOpen2026',
3042
roundId: '333-r2',
3143
},
3244
};
3345

46+
export const ParticipationConditionLinkedRounds: Story = {
47+
parameters: {
48+
competitionFixture: storybookParticipationConditionLinkedRoundsFixture,
49+
},
50+
args: {
51+
competitionId: 'SeattleSummerOpen2026',
52+
roundId: '333-r2',
53+
},
54+
};
55+
3456
export const FinalRound: Story = {
3557
parameters: {
3658
competitionFixture: makeStorybookCompetitionFixtureWithRound('333-r3', (round) => ({

src/lib/wcif.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import {
2-
CompatibleRound,
3-
getAdvancementConditionForRound,
4-
getRoundParticipationRuleset,
5-
} from './wcif';
1+
import { Round } from '@wca/helpers';
2+
import { getAdvancementConditionForRound, getRoundParticipationRuleset } from './wcif';
63

74
describe('wcif participation helpers', () => {
85
it('backfills stable advancement conditions into a participation ruleset', () => {
@@ -21,7 +18,7 @@ describe('wcif participation helpers', () => {
2118
},
2219
results: [],
2320
},
24-
] as unknown as CompatibleRound[];
21+
] as unknown as Round[];
2522

2623
expect(getRoundParticipationRuleset(rounds, rounds[1])).toEqual({
2724
participationSource: {
@@ -57,7 +54,7 @@ describe('wcif participation helpers', () => {
5754
},
5855
results: [],
5956
},
60-
] as unknown as CompatibleRound[];
57+
] as unknown as Round[];
6158

6259
expect(getAdvancementConditionForRound(rounds, rounds[0])).toEqual({
6360
sourceType: 'round',
@@ -97,7 +94,7 @@ describe('wcif participation helpers', () => {
9794
},
9895
results: [],
9996
},
100-
] as unknown as CompatibleRound[];
97+
] as unknown as Round[];
10198

10299
expect(getAdvancementConditionForRound(rounds, rounds[1])).toEqual({
103100
sourceType: 'linkedRounds',

src/lib/wcif.ts

Lines changed: 20 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,15 @@
1-
import { Round, parseActivityCode } from '@wca/helpers';
1+
import {
2+
ParticipationResultCondition,
3+
ParticipationRuleset,
4+
ParticipationSource,
5+
ReservedPlaces,
6+
Round,
7+
parseActivityCode,
8+
} from '@wca/helpers';
29

3-
type LegacyAdvancementCondition = {
4-
type: 'ranking' | 'percent' | 'attemptResult';
5-
level: number;
6-
};
7-
8-
export type ResultCondition =
9-
| {
10-
type: 'ranking' | 'percent';
11-
value: number;
12-
}
13-
| {
14-
type: 'resultAchieved';
15-
scope: 'single' | 'average';
16-
value: number | null;
17-
};
10+
type LegacyAdvancementCondition = NonNullable<Round['advancementCondition']>;
1811

19-
export type ParticipationSource =
20-
| {
21-
type: 'registrations';
22-
}
23-
| {
24-
type: 'round';
25-
roundId: string;
26-
resultCondition: ResultCondition;
27-
}
28-
| {
29-
type: 'linkedRounds';
30-
roundIds: string[];
31-
resultCondition: ResultCondition;
32-
};
33-
34-
export type ReservedPlaces = {
35-
nationalities: string[];
36-
count?: number;
37-
reservations?: number;
38-
};
39-
40-
export type ParticipationRuleset = {
41-
participationSource: ParticipationSource;
42-
reservedPlaces?: ReservedPlaces | null;
43-
};
12+
export type ResultCondition = ParticipationResultCondition;
4413

4514
export interface RoundAdvancementCondition {
4615
sourceType: ParticipationSource['type'];
@@ -49,19 +18,16 @@ export interface RoundAdvancementCondition {
4918
reservedPlaces?: ReservedPlaces | null;
5019
}
5120

52-
export type CompatibleRound = Round & {
53-
advancementCondition?: LegacyAdvancementCondition | null;
54-
participationRuleset?: ParticipationRuleset | null;
55-
};
21+
export type CompatibleRound = Round;
5622

5723
const averagedFormats = new Set(['a', 'm', '5', 'h']);
5824

59-
const getRoundResultType = (round: Pick<CompatibleRound, 'format'>): 'single' | 'average' =>
25+
const getRoundResultType = (round: Pick<Round, 'format'>): 'single' | 'average' =>
6026
averagedFormats.has(round.format) ? 'average' : 'single';
6127

6228
const getLegacyResultCondition = (
6329
advancementCondition: LegacyAdvancementCondition,
64-
sourceRound?: CompatibleRound,
30+
sourceRound?: Round,
6531
): ResultCondition => {
6632
switch (advancementCondition.type) {
6733
case 'ranking':
@@ -83,10 +49,7 @@ const getLegacyResultCondition = (
8349
}
8450
};
8551

86-
const getPreviousRound = (
87-
eventRounds: CompatibleRound[],
88-
round: CompatibleRound,
89-
): CompatibleRound | undefined => {
52+
const getPreviousRound = (eventRounds: Round[], round: Round): Round | undefined => {
9053
const { eventId, roundNumber } = parseActivityCode(round.id);
9154

9255
if (!roundNumber || roundNumber <= 1) {
@@ -97,8 +60,8 @@ const getPreviousRound = (
9760
};
9861

9962
export const getRoundParticipationRuleset = (
100-
eventRounds: CompatibleRound[],
101-
round: CompatibleRound,
63+
eventRounds: Round[],
64+
round: Round,
10265
): ParticipationRuleset | null => {
10366
if (round.participationRuleset) {
10467
return round.participationRuleset;
@@ -123,14 +86,14 @@ export const getRoundParticipationRuleset = (
12386
};
12487

12588
const getRoundParticipationSource = (
126-
eventRounds: CompatibleRound[],
127-
round: CompatibleRound,
89+
eventRounds: Round[],
90+
round: Round,
12891
): ParticipationSource | null =>
12992
getRoundParticipationRuleset(eventRounds, round)?.participationSource ?? null;
13093

13194
export const getAdvancementConditionForRound = (
132-
eventRounds: CompatibleRound[],
133-
round: CompatibleRound,
95+
eventRounds: Round[],
96+
round: Round,
13497
): RoundAdvancementCondition | null => {
13598
if (round.advancementCondition) {
13699
return {

src/storybook/competitionFixtures.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,60 @@ export const makeStorybookCompetitionFixtureWithRound = (
730730

731731
return competition;
732732
};
733+
734+
export const makeStorybookCompetitionFixtureWithRoundUpdates = (
735+
updates: Record<string, (round: Round) => Round>,
736+
): Competition => {
737+
const competition = cloneCompetition(storybookCompetitionFixture);
738+
739+
competition.events = competition.events.map((event) => ({
740+
...event,
741+
rounds: event.rounds.map((round) => updates[round.id]?.(round) ?? round),
742+
}));
743+
744+
return competition;
745+
};
746+
747+
export const storybookParticipationConditionPercentFixture =
748+
makeStorybookCompetitionFixtureWithRoundUpdates({
749+
'333-r1': (round) => ({
750+
...round,
751+
advancementCondition: null,
752+
}),
753+
'333-r2': (round) => ({
754+
...round,
755+
advancementCondition: null,
756+
participationRuleset: {
757+
participationSource: {
758+
type: 'round',
759+
roundId: '333-r1',
760+
resultCondition: {
761+
type: 'percent',
762+
value: 75,
763+
},
764+
},
765+
},
766+
}),
767+
});
768+
769+
export const storybookParticipationConditionLinkedRoundsFixture =
770+
makeStorybookCompetitionFixtureWithRoundUpdates({
771+
'333-r2': (round) => ({
772+
...round,
773+
advancementCondition: null,
774+
}),
775+
'333-r3': (round) => ({
776+
...round,
777+
advancementCondition: null,
778+
participationRuleset: {
779+
participationSource: {
780+
type: 'linkedRounds',
781+
roundIds: ['333-r1', '333-r2'],
782+
resultCondition: {
783+
type: 'ranking',
784+
value: 12,
785+
},
786+
},
787+
},
788+
}),
789+
});

src/types/wca-helpers.d.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import '@wca/helpers';
2+
3+
declare module '@wca/helpers' {
4+
export type ParticipationResultCondition =
5+
| {
6+
type: 'ranking' | 'percent';
7+
value: number;
8+
}
9+
| {
10+
type: 'resultAchieved';
11+
scope: 'single' | 'average';
12+
value: number | null;
13+
};
14+
15+
export type ParticipationSource =
16+
| {
17+
type: 'registrations';
18+
}
19+
| {
20+
type: 'round';
21+
roundId: string;
22+
resultCondition: ParticipationResultCondition;
23+
}
24+
| {
25+
type: 'linkedRounds';
26+
roundIds: string[];
27+
resultCondition: ParticipationResultCondition;
28+
};
29+
30+
export interface ReservedPlaces {
31+
nationalities: string[];
32+
count?: number;
33+
reservations?: number;
34+
}
35+
36+
export interface ParticipationRuleset {
37+
participationSource: ParticipationSource;
38+
reservedPlaces?: ReservedPlaces | null;
39+
}
40+
41+
export interface Round {
42+
participationRuleset?: ParticipationRuleset | null;
43+
}
44+
}

0 commit comments

Comments
 (0)