Skip to content

Commit 8652fb8

Browse files
committed
Polish competition results display
1 parent 7aff285 commit 8652fb8

12 files changed

Lines changed: 388 additions & 58 deletions

src/containers/CompetitionResults/CompetitionResults.test.tsx

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { CompetitionResultsContainer } from './CompetitionResults';
77
let mockWcaLiveRoundResults: unknown[] = [];
88
let mockWcaCompetitionResults: unknown[] | undefined = [];
99
let mockWcaCompetitionResultsStatus = 'success';
10+
const mockUseWcaLiveRoundLink = jest.fn();
11+
const mockUseWcaLiveRoundResults = jest.fn();
1012

1113
jest.mock('@/components/Container', () => ({
1214
Container: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
@@ -18,14 +20,8 @@ jest.mock('@/lib/events', () => ({
1820
}));
1921

2022
jest.mock('@/hooks/queries/useWcaLive', () => ({
21-
useWcaLiveRoundLink: () => ({
22-
data: 'https://live.worldcubeassociation.org/competitions/TestComp2026/rounds/1',
23-
status: 'success',
24-
}),
25-
useWcaLiveRoundResults: () => ({
26-
data: { results: mockWcaLiveRoundResults },
27-
status: 'success',
28-
}),
23+
useWcaLiveRoundLink: (...args: unknown[]) => mockUseWcaLiveRoundLink(...args),
24+
useWcaLiveRoundResults: (...args: unknown[]) => mockUseWcaLiveRoundResults(...args),
2925
}));
3026

3127
jest.mock('@/hooks/queries/useWcaResults', () => ({
@@ -248,6 +244,14 @@ describe('CompetitionResultsContainer', () => {
248244
mockWcaLiveRoundResults = [];
249245
mockWcaCompetitionResults = [];
250246
mockWcaCompetitionResultsStatus = 'success';
247+
mockUseWcaLiveRoundLink.mockReturnValue({
248+
data: 'https://live.worldcubeassociation.org/competitions/TestComp2026/rounds/1',
249+
status: 'success',
250+
});
251+
mockUseWcaLiveRoundResults.mockImplementation(() => ({
252+
data: { results: mockWcaLiveRoundResults },
253+
status: 'success',
254+
}));
251255
});
252256

253257
afterEach(() => {
@@ -380,7 +384,11 @@ describe('CompetitionResultsContainer', () => {
380384
round_type_id: '2',
381385
format_id: 'a',
382386
wca_id: '2010THOM03',
383-
attempts: [900, 950, 1000],
387+
attempts: [900, 950, 1000, 1050, 1200],
388+
best_index: 0,
389+
worst_index: 4,
390+
regional_single_record: 'NR',
391+
regional_average_record: 'WR',
384392
},
385393
];
386394

@@ -391,10 +399,31 @@ describe('CompetitionResultsContainer', () => {
391399
'href',
392400
'/competitions/TestComp2026/persons/1/results',
393401
);
402+
expect(
403+
within(screen.getByRole('row', { name: /1 Blake Thompson/ })).getByText('(9.00)'),
404+
).toBeInTheDocument();
405+
expect(
406+
within(screen.getByRole('row', { name: /1 Blake Thompson/ })).getByText('(12.00)'),
407+
).toBeInTheDocument();
394408
expect(
395409
within(screen.getByRole('row', { name: /1 Blake Thompson/ })).getAllByText('9.50'),
396410
).toHaveLength(2);
397-
expect(screen.getAllByText('9.00')).toHaveLength(2);
411+
expect(screen.getAllByText('9.00')).toHaveLength(1);
412+
expect(screen.getByLabelText('WR record')).toHaveTextContent('WR');
413+
expect(screen.getByLabelText('NR record')).toHaveTextContent('NR');
414+
expect(
415+
within(screen.getByRole('navigation', { name: 'Event rounds' }))
416+
.getAllByRole('link')
417+
.map((link) => link.getAttribute('href')),
418+
).toEqual([
419+
'/competitions/TestComp2026/results/333-r1',
420+
'/competitions/TestComp2026/results/333-r2',
421+
]);
422+
expect(
423+
within(screen.getByRole('navigation', { name: 'Event rounds' })).getByRole('link', {
424+
name: /Round 2/,
425+
}),
426+
).toHaveClass('bg-blue-700', 'text-white');
398427
expect(
399428
within(screen.getByRole('navigation', { name: 'Round' })).getAllByText('Done'),
400429
).toHaveLength(2);
@@ -413,16 +442,23 @@ describe('CompetitionResultsContainer', () => {
413442
});
414443

415444
it('uses WCA Live partial results for the selected round when they are available', () => {
416-
jest.useFakeTimers().setSystemTime(new Date('2026-05-03T12:00:00'));
417445
mockWcaLiveRoundResults = [
418446
{
419447
id: 'live-result-1',
420448
ranking: 3,
421449
advancing: true,
422450
advancingQuestionable: true,
423-
attempts: [{ result: 1100 }, { result: 1200 }],
451+
attempts: [
452+
{ result: 1100 },
453+
{ result: 1200 },
454+
{ result: 1300 },
455+
{ result: 1400 },
456+
{ result: 1500 },
457+
],
424458
best: 1100,
425-
average: 0,
459+
average: 1300,
460+
singleRecordTag: 'PR',
461+
averageRecordTag: 'PR',
426462
person: {
427463
id: 'live-person-1',
428464
registrantId: 1,
@@ -433,6 +469,15 @@ describe('CompetitionResultsContainer', () => {
433469

434470
renderResults('333-r1');
435471

472+
expect(mockUseWcaLiveRoundLink).toHaveBeenCalledWith('TestComp2026', '333', 1, {
473+
enabled: true,
474+
});
475+
expect(mockUseWcaLiveRoundResults).toHaveBeenCalledWith(
476+
'https://live.worldcubeassociation.org/competitions/TestComp2026/rounds/1',
477+
{
478+
enabled: true,
479+
},
480+
);
436481
expect(screen.getByRole('link', { name: 'Blake Thompson' })).toHaveAttribute(
437482
'href',
438483
'/competitions/TestComp2026/persons/1/results',
@@ -451,14 +496,23 @@ describe('CompetitionResultsContainer', () => {
451496
expect(
452497
within(screen.getByRole('row', { name: /Casey Nguyen/ })).getAllByRole('cell')[0],
453498
).toBeEmptyDOMElement();
454-
expect(screen.getAllByText('11.00')).toHaveLength(2);
499+
expect(
500+
within(screen.getByRole('row', { name: /3 Blake Thompson/ })).getByText('(11.00)'),
501+
).toBeInTheDocument();
502+
expect(
503+
within(screen.getByRole('row', { name: /3 Blake Thompson/ })).getByText('(15.00)'),
504+
).toBeInTheDocument();
505+
expect(screen.getAllByText('11.00')).toHaveLength(1);
506+
expect(screen.getAllByLabelText('PR record').map((badge) => badge.textContent)).toEqual([
507+
'PR',
508+
'PR',
509+
]);
455510
expect(
456511
within(screen.getByRole('row', { name: /2 Nick Silvestri/ })).getAllByText('15.00'),
457512
).toHaveLength(2);
458513
expect(
459514
screen.getByText('Data is pulled from WCA Live and may be delayed.'),
460515
).toBeInTheDocument();
461-
expect(screen.queryByText('13.00')).not.toBeInTheDocument();
462516
});
463517

464518
it('opens full result details from a narrow results row', () => {

src/containers/CompetitionResults/CompetitionResults.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { AnchorLink, LinkRenderer } from '@/lib/linkRenderer';
2020
import { findRoundWithEvent, getAllRoundsWithEvents } from '@/lib/rounds';
2121
import { useWCIF } from '@/providers/WCIFProvider';
2222
import { CompetitionResultsTable } from './CompetitionResultsTable';
23+
import { ResultsEventRoundSelector } from './ResultsEventRoundSelector';
2324
import { getWcaApiResultsByRoundId } from './resultSources';
2425
import { getRoundResultsFromSources } from './resultsProvider';
2526

@@ -82,7 +83,7 @@ function ResultsRoundNav({
8283
return (
8384
<nav
8485
aria-label={t('common.wca.round')}
85-
className="hidden overflow-y-auto rounded-md border border-tertiary-weak bg-panel shadow-sm md:sticky md:top-4 md:block md:max-h-[calc(100vh-2rem)]">
86+
className="hidden overflow-y-auto rounded-md border border-tertiary-weak bg-panel shadow-sm lg:sticky lg:top-4 lg:block lg:max-h-[calc(100vh-2rem)]">
8687
<div>
8788
{groups.map(({ event, rounds }) => (
8889
<div key={event.id} className="border-b border-tertiary-weak last:border-b-0">
@@ -107,7 +108,7 @@ function ResultsRoundNav({
107108
className={classNames(
108109
'flex min-h-10 items-center gap-2 px-3 py-2 type-body-sm hover-transition hover:bg-gray-100 dark:hover:bg-gray-700',
109110
{
110-
'bg-active text-primary': isSelected,
111+
'bg-blue-700 text-white dark:bg-blue-300 dark:text-gray-950': isSelected,
111112
'text-default': !isSelected,
112113
},
113114
)}>
@@ -218,20 +219,34 @@ export function CompetitionResultsContainer({
218219
competitionId,
219220
selectedRound?.event.id ?? '',
220221
selectedRound?.roundNumber ?? 1,
221-
{ enabled: Boolean(selectedRound) && isTodayCompetitionDay },
222+
{ enabled: Boolean(selectedRound) },
222223
);
223224
const { data: wcaLiveRound } = useWcaLiveRoundResults(wcaLiveRoundLink, {
224-
enabled: isTodayCompetitionDay && wcaLiveRoundLinkStatus === 'success',
225+
enabled: wcaLiveRoundLinkStatus === 'success',
225226
});
226227
const roundResults = useMemo(
227228
() => getRoundResultsFromSources({ wcif, selectedRound, wcaLiveRound, wcaApiResults }),
228229
[selectedRound, wcaApiResults, wcaLiveRound, wcif],
229230
);
231+
const selectedEventRoundLinks = useMemo(
232+
() =>
233+
selectedRound
234+
? (pickerEvents.find((event) => event.id === selectedRound.event.id)?.rounds ?? []).map(
235+
({ href, id, resultStatus, roundNumber }) => ({
236+
href: href ?? `/competitions/${competitionId}/results/${id}`,
237+
id,
238+
resultStatus,
239+
roundNumber,
240+
}),
241+
)
242+
: [],
243+
[competitionId, pickerEvents, selectedRound],
244+
);
230245

231246
if (selectedRoundId) {
232247
return (
233248
<Container className="pt-4" fullWidth>
234-
<div className="mx-auto grid w-full max-w-screen-xl gap-4 p-2 type-body md:grid-cols-[16rem_minmax(0,1fr)] md:items-start">
249+
<div className="mx-auto grid w-full max-w-screen-xl gap-4 p-2 type-body lg:grid-cols-[16rem_minmax(0,1fr)] lg:items-start">
235250
<ResultsRoundNav
236251
competitionId={competitionId}
237252
groups={eventsWithResults}
@@ -242,7 +257,7 @@ export function CompetitionResultsContainer({
242257
apiResultsByRoundId={apiResultsByRoundId}
243258
/>
244259
<div className="flex flex-col min-w-0 space-y-4">
245-
<div className="md:hidden">
260+
<div className="lg:hidden">
246261
<LinkButton
247262
to={`/competitions/${competitionId}/results`}
248263
title={t('competition.results.back')}
@@ -260,6 +275,11 @@ export function CompetitionResultsContainer({
260275
})}
261276
</h2>
262277
<NoteBox text={t('competition.results.liveResultsDelayNote')} />
278+
<ResultsEventRoundSelector
279+
selectedRoundId={selectedRound.round.id}
280+
rounds={selectedEventRoundLinks}
281+
LinkComponent={LinkComponent}
282+
/>
263283
{isWcaApiResultsLoading && roundResults.length === 0 ? (
264284
<section className="p-4 border rounded-md border-tertiary-weak bg-panel text-muted">
265285
<p>{t('common.loading')}</p>

0 commit comments

Comments
 (0)