Skip to content

Commit e1a3739

Browse files
committed
🚨 Add tests for contest table providers (#1856)
1 parent fc548de commit e1a3739

File tree

2 files changed

+324
-0
lines changed

2 files changed

+324
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { describe, test, expect, vi } from 'vitest';
2+
3+
import { ContestType } from '$lib/types/contest';
4+
import type { TaskResult, TaskResults } from '$lib/types/task';
5+
6+
import { contestTableProviders } from '$lib/utils/contest_table_provider';
7+
import { taskResultsForContestTableProvider } from './test_cases/contest_table_provider';
8+
9+
// Mock the imported functions
10+
vi.mock('$lib/utils/contest', () => ({
11+
classifyContest: vi.fn((contestId: string) => {
12+
if (contestId.startsWith('abc')) {
13+
return ContestType.ABC;
14+
}
15+
16+
return ContestType.OTHERS;
17+
}),
18+
19+
getContestNameLabel: vi.fn((contestId: string) => {
20+
if (contestId.startsWith('abc')) {
21+
return `ABC ${contestId.replace('abc', '')}`;
22+
}
23+
24+
return contestId;
25+
}),
26+
}));
27+
28+
vi.mock('$lib/utils/task', () => ({
29+
getTaskTableHeaderName: vi.fn((_: ContestType, task: TaskResult) => {
30+
return `${task.task_table_index}`;
31+
}),
32+
}));
33+
34+
describe('ContestTableProviderBase and implementations', () => {
35+
const mockTaskResults: TaskResults = taskResultsForContestTableProvider;
36+
37+
describe('ABC latest 20 rounds provider', () => {
38+
test('expects to filter tasks to include only ABC contests', () => {
39+
const provider = contestTableProviders.abcLatest20Rounds;
40+
const filtered = provider.filter(mockTaskResults);
41+
42+
expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
43+
expect(filtered).not.toContainEqual(expect.objectContaining({ contest_id: 'arc100' }));
44+
});
45+
46+
test('expects to limit results to the latest 20 rounds', () => {
47+
const provider = contestTableProviders.abcLatest20Rounds;
48+
49+
const largeDataset = [...mockTaskResults];
50+
const filtered = provider.filter(largeDataset);
51+
const uniqueContests = new Set(filtered.map((task) => task.contest_id));
52+
53+
expect(uniqueContests.size).toBeLessThanOrEqual(20);
54+
});
55+
56+
test('expects to generate correct table structure', () => {
57+
const provider = contestTableProviders.abcLatest20Rounds;
58+
const filtered = provider.filter(mockTaskResults);
59+
const table = provider.generateTable(filtered);
60+
61+
expect(table).toHaveProperty('abc378');
62+
expect(table.abc378).toHaveProperty('G');
63+
expect(table.abc378.G).toEqual(
64+
expect.objectContaining({ contest_id: 'abc378', task_id: 'abc378_g' }),
65+
);
66+
});
67+
68+
test('expects to get correct metadata', () => {
69+
const provider = contestTableProviders.abcLatest20Rounds;
70+
const metadata = provider.getMetadata();
71+
72+
expect(metadata.title).toBe('AtCoder Beginner Contest 最新 20 回');
73+
expect(metadata.buttonLabel).toBe('ABC 最新 20 回');
74+
expect(metadata.ariaLabel).toBe('Filter ABC latest 20 rounds');
75+
});
76+
77+
test('expects to format contest round label correctly', () => {
78+
const provider = contestTableProviders.abcLatest20Rounds;
79+
const label = provider.getContestRoundLabel('abc378');
80+
81+
expect(label).toBe('378');
82+
});
83+
});
84+
85+
describe('ABC319 onwards provider', () => {
86+
test('expects to filter tasks to include only ABC319 and later', () => {
87+
const provider = contestTableProviders.abc319Onwards;
88+
const filtered = provider.filter(mockTaskResults);
89+
90+
expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
91+
expect(
92+
filtered.every((task) => {
93+
const round = parseInt(task.contest_id.replace('abc', ''), 10);
94+
return round >= 319 && round <= 999;
95+
}),
96+
).toBeTruthy();
97+
});
98+
99+
test('expects to get correct metadata', () => {
100+
const provider = contestTableProviders.abc319Onwards;
101+
const metadata = provider.getMetadata();
102+
103+
expect(metadata.title).toBe('AtCoder Beginner Contest 319 〜 ');
104+
expect(metadata.buttonLabel).toBe('ABC 319 〜 ');
105+
expect(metadata.ariaLabel).toBe('Filter contests from ABC 319 onwards');
106+
});
107+
108+
test('expects to format contest round label correctly', () => {
109+
const provider = contestTableProviders.abc319Onwards;
110+
const label = provider.getContestRoundLabel('abc397');
111+
112+
expect(label).toBe('397');
113+
});
114+
});
115+
116+
describe('ABC212 to ABC318 provider', () => {
117+
test('expects to filter tasks to include only ABC between 212 and 318', () => {
118+
const provider = contestTableProviders.fromAbc212ToAbc318;
119+
const filtered = provider.filter(mockTaskResults);
120+
121+
expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
122+
expect(
123+
filtered.every((task) => {
124+
const round = parseInt(task.contest_id.replace('abc', ''), 10);
125+
return round >= 212 && round <= 318;
126+
}),
127+
).toBeTruthy();
128+
});
129+
130+
test('expects to get correct metadata', () => {
131+
const provider = contestTableProviders.fromAbc212ToAbc318;
132+
const metadata = provider.getMetadata();
133+
134+
expect(metadata.title).toBe('AtCoder Beginner Contest 212 〜 318');
135+
expect(metadata.buttonLabel).toBe('ABC 212 〜 318');
136+
expect(metadata.ariaLabel).toBe('Filter contests from ABC 212 to ABC 318');
137+
});
138+
139+
test('expects to format contest round label correctly', () => {
140+
const provider = contestTableProviders.fromAbc212ToAbc318;
141+
const label = provider.getContestRoundLabel('abc318');
142+
143+
expect(label).toBe('318');
144+
});
145+
});
146+
147+
describe('Common provider functionality', () => {
148+
test('expects to get contest round IDs correctly', () => {
149+
const provider = contestTableProviders.abcLatest20Rounds;
150+
const filtered = [
151+
{ contest_id: 'abc397', task_id: 'a', status_name: 'ac' },
152+
{ contest_id: 'abc319', task_id: 'a', status_name: 'ac' },
153+
{ contest_id: 'abc319', task_id: 'b', status_name: 'ac' },
154+
{ contest_id: 'abc319', task_id: 'c', status_name: 'ac' },
155+
{ contest_id: 'abc319', task_id: 'e', status_name: 'ac_with_editorial' },
156+
{ contest_id: 'abc319', task_id: 'f', status_name: 'wa' },
157+
{ contest_id: 'abc319', task_id: 'g', status_name: 'ns' },
158+
{ contest_id: 'abc318', task_id: 'a', status_name: 'ac' },
159+
] as TaskResults;
160+
161+
const roundIds = provider.getContestRoundIds(filtered);
162+
163+
expect(roundIds).toEqual(['abc397', 'abc319', 'abc318']);
164+
});
165+
166+
test('expects to get header IDs for tasks correctly', () => {
167+
const provider = contestTableProviders.abcLatest20Rounds;
168+
const filtered = [
169+
{ contest_id: 'abc319', task_id: 'abc319_a', task_table_index: 'A', status_name: 'ac' },
170+
{ contest_id: 'abc319', task_id: 'abc319_b', task_table_index: 'B', status_name: 'ac' },
171+
{ contest_id: 'abc319', task_id: 'abc319_c', task_table_index: 'C', status_name: 'ac' },
172+
{ contest_id: 'abc319', task_id: 'abc319_d', task_table_index: 'D', status_name: 'ac' },
173+
{
174+
contest_id: 'abc319',
175+
task_id: 'abc319_e',
176+
task_table_index: 'E',
177+
status_name: 'ac_with_editorial',
178+
},
179+
{ contest_id: 'abc319', task_id: 'abc319_f', task_table_index: 'F', status_name: 'wa' },
180+
{ contest_id: 'abc319', task_id: 'abc319_g', task_table_index: 'G', status_name: 'ns' },
181+
] as TaskResults;
182+
183+
const headerIds = provider.getHeaderIdsForTask(filtered);
184+
185+
expect(headerIds).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']);
186+
});
187+
});
188+
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import type { TaskResult, TaskResults } from '$lib/types/task';
2+
3+
// Default task result with minimal initialization.
4+
// Most fields are empty strings as they're not relevant for these tests.
5+
// The updated_at field is set to Unix epoch as we only care about task_table_index
6+
// and task_id for header name testing.
7+
const defaultTaskResult: TaskResult = {
8+
is_ac: false,
9+
user_id: '',
10+
status_name: '',
11+
status_id: '',
12+
submission_status_image_path: '',
13+
submission_status_label_name: '',
14+
contest_id: '',
15+
task_table_index: '',
16+
task_id: '',
17+
title: '',
18+
grade: '',
19+
updated_at: new Date(0), // Use the Unix epoch as the default value.
20+
};
21+
22+
const AC = 'ac';
23+
const AC_WITH_EDITORIAL = 'ac_with_editorial';
24+
const TRYING = 'wa';
25+
const PENDING = 'ns';
26+
27+
/**
28+
* Creates a new TaskResult using defaultTaskResult as a base, overriding the taskId and taskTableIndex.
29+
* @param contestId - The ID of the contest (e.g., 'abc212')
30+
* @param taskId - The ID of the task (e.g., 'abc212_a')
31+
* @param taskTableIndex - The index of the task in the table (e.g., 'A', 'B', 'Ex')
32+
* @param statusName - submission status of the task (e.g., 'ac', 'ac_with_editorial', 'wa', 'ns)
33+
* @returns A new TaskResult object with the specified taskId and taskTableIndex
34+
*/
35+
function createTaskResultWithTaskTableIndex(
36+
contestId: string,
37+
taskId: string,
38+
taskTableIndex: string,
39+
statusName: string,
40+
): TaskResult {
41+
return {
42+
...defaultTaskResult,
43+
contest_id: contestId,
44+
task_id: taskId,
45+
task_table_index: taskTableIndex,
46+
status_name: statusName,
47+
is_ac: statusName === AC || statusName === AC_WITH_EDITORIAL,
48+
};
49+
}
50+
51+
// ABC212 - ABC232: 8 tasks (A, B, C, D, E, F, G and H)
52+
const abc212_a = createTaskResultWithTaskTableIndex('abc212', 'abc212_a', 'A', AC);
53+
const abc212_b = createTaskResultWithTaskTableIndex('abc212', 'abc212_b', 'B', AC);
54+
const abc212_f = createTaskResultWithTaskTableIndex('abc212', 'abc212_f', 'F', AC_WITH_EDITORIAL);
55+
const abc212_g = createTaskResultWithTaskTableIndex('abc212', 'abc212_g', 'G', TRYING);
56+
const abc212_h = createTaskResultWithTaskTableIndex('abc212', 'abc212_h', 'H', PENDING);
57+
const abc213_h = createTaskResultWithTaskTableIndex('abc213', 'abc213_h', 'H', PENDING);
58+
const abc232_h = createTaskResultWithTaskTableIndex('abc232', 'abc232_h', 'H', TRYING);
59+
60+
// ABC233 - ABC318: 8 tasks (A, B, C, D, E, F, G and Ex)
61+
const abc233_a = createTaskResultWithTaskTableIndex('abc233', 'abc233_a', 'A', AC);
62+
const abc233_b = createTaskResultWithTaskTableIndex('abc233', 'abc233_b', 'B', TRYING);
63+
const abc233_ex = createTaskResultWithTaskTableIndex('abc233', 'abc233_ex', 'Ex', PENDING);
64+
const abc234_ex = createTaskResultWithTaskTableIndex('abc234', 'abc234_ex', 'Ex', AC);
65+
const abc317_ex = createTaskResultWithTaskTableIndex('abc317', 'abc317_ex', 'Ex', TRYING);
66+
const abc318_ex = createTaskResultWithTaskTableIndex('abc318', 'abc318_ex', 'Ex', PENDING);
67+
68+
// ABC319 - : 7 tasks (A, B, C, D, E, F and G)
69+
const abc319_a = createTaskResultWithTaskTableIndex('abc319', 'abc319_a', 'A', AC);
70+
const abc319_b = createTaskResultWithTaskTableIndex('abc319', 'abc319_b', 'B', AC);
71+
const abc319_f = createTaskResultWithTaskTableIndex('abc319', 'abc319_f', 'F', TRYING);
72+
const abc319_g = createTaskResultWithTaskTableIndex('abc319', 'abc319_g', 'G', PENDING);
73+
const abc376_g = createTaskResultWithTaskTableIndex('abc376', 'abc376_g', 'G', AC);
74+
const abc377_g = createTaskResultWithTaskTableIndex('abc377', 'abc377_g', 'G', AC);
75+
const abc378_g = createTaskResultWithTaskTableIndex('abc378', 'abc378_g', 'G', TRYING);
76+
const abc379_g = createTaskResultWithTaskTableIndex('abc379', 'abc379_g', 'G', PENDING);
77+
const abc380_g = createTaskResultWithTaskTableIndex('abc380', 'abc380_g', 'G', AC);
78+
const abc381_g = createTaskResultWithTaskTableIndex('abc381', 'abc381_g', 'G', AC_WITH_EDITORIAL);
79+
const abc382_g = createTaskResultWithTaskTableIndex('abc382', 'abc382_g', 'G', TRYING);
80+
const abc383_g = createTaskResultWithTaskTableIndex('abc383', 'abc383_g', 'G', AC);
81+
const abc384_g = createTaskResultWithTaskTableIndex('abc384', 'abc384_g', 'G', AC);
82+
const abc385_g = createTaskResultWithTaskTableIndex('abc385', 'abc385_g', 'G', AC);
83+
const abc386_g = createTaskResultWithTaskTableIndex('abc386', 'abc386_g', 'G', AC_WITH_EDITORIAL);
84+
const abc387_g = createTaskResultWithTaskTableIndex('abc387', 'abc387_g', 'G', TRYING);
85+
const abc388_g = createTaskResultWithTaskTableIndex('abc388', 'abc388_g', 'G', TRYING);
86+
const abc389_g = createTaskResultWithTaskTableIndex('abc389', 'abc389_g', 'G', TRYING);
87+
const abc390_g = createTaskResultWithTaskTableIndex('abc390', 'abc390_g', 'G', TRYING);
88+
const abc391_g = createTaskResultWithTaskTableIndex('abc391', 'abc391_g', 'G', TRYING);
89+
const abc392_g = createTaskResultWithTaskTableIndex('abc392', 'abc392_g', 'G', TRYING);
90+
const abc393_g = createTaskResultWithTaskTableIndex('abc393', 'abc393_g', 'G', TRYING);
91+
const abc394_g = createTaskResultWithTaskTableIndex('abc394', 'abc394_g', 'G', TRYING);
92+
const abc395_g = createTaskResultWithTaskTableIndex('abc395', 'abc395_g', 'G', TRYING);
93+
const abc396_g = createTaskResultWithTaskTableIndex('abc396', 'abc396_g', 'G', TRYING);
94+
const abc397_g = createTaskResultWithTaskTableIndex('abc397', 'abc397_g', 'G', TRYING);
95+
96+
export const taskResultsForContestTableProvider: TaskResults = [
97+
abc212_a,
98+
abc212_b,
99+
abc212_f,
100+
abc212_g,
101+
abc212_h,
102+
abc213_h,
103+
abc232_h,
104+
abc233_a,
105+
abc233_b,
106+
abc233_ex,
107+
abc234_ex,
108+
abc317_ex,
109+
abc318_ex,
110+
abc319_a,
111+
abc319_b,
112+
abc319_f,
113+
abc319_g,
114+
abc376_g,
115+
abc377_g,
116+
abc378_g,
117+
abc379_g,
118+
abc380_g,
119+
abc381_g,
120+
abc382_g,
121+
abc383_g,
122+
abc384_g,
123+
abc385_g,
124+
abc386_g,
125+
abc387_g,
126+
abc388_g,
127+
abc389_g,
128+
abc390_g,
129+
abc391_g,
130+
abc392_g,
131+
abc393_g,
132+
abc394_g,
133+
abc395_g,
134+
abc396_g,
135+
abc397_g,
136+
];

0 commit comments

Comments
 (0)