Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions src/test/lib/utils/contest_table_provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { describe, test, expect, vi } from 'vitest';

import { ContestType } from '$lib/types/contest';
import type { TaskResult, TaskResults } from '$lib/types/task';

import { contestTableProviders } from '$lib/utils/contest_table_provider';
import { taskResultsForContestTableProvider } from './test_cases/contest_table_provider';

// Mock the imported functions
vi.mock('$lib/utils/contest', () => ({
classifyContest: vi.fn((contestId: string) => {
if (contestId.startsWith('abc')) {
return ContestType.ABC;
}

return ContestType.OTHERS;
}),

getContestNameLabel: vi.fn((contestId: string) => {
if (contestId.startsWith('abc')) {
return `ABC ${contestId.replace('abc', '')}`;
}

return contestId;
}),
}));

vi.mock('$lib/utils/task', () => ({
getTaskTableHeaderName: vi.fn((_: ContestType, task: TaskResult) => {
return `${task.task_table_index}`;
}),
}));

describe('ContestTableProviderBase and implementations', () => {
const mockTaskResults: TaskResults = taskResultsForContestTableProvider;

describe('ABC latest 20 rounds provider', () => {
test('expects to filter tasks to include only ABC contests', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const filtered = provider.filter(mockTaskResults);

expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
expect(filtered).not.toContainEqual(expect.objectContaining({ contest_id: 'arc100' }));
});

test('expects to limit results to the latest 20 rounds', () => {
const provider = contestTableProviders.abcLatest20Rounds;

const largeDataset = [...mockTaskResults];
const filtered = provider.filter(largeDataset);
const uniqueContests = new Set(filtered.map((task) => task.contest_id));

expect(uniqueContests.size).toBeLessThanOrEqual(20);
});

test('expects to generate correct table structure', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const filtered = provider.filter(mockTaskResults);
const table = provider.generateTable(filtered);

expect(table).toHaveProperty('abc378');
expect(table.abc378).toHaveProperty('G');
expect(table.abc378.G).toEqual(
expect.objectContaining({ contest_id: 'abc378', task_id: 'abc378_g' }),
);
});

test('expects to get correct metadata', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const metadata = provider.getMetadata();

expect(metadata.title).toBe('AtCoder Beginner Contest 最新 20 回');
expect(metadata.buttonLabel).toBe('ABC 最新 20 回');
expect(metadata.ariaLabel).toBe('Filter ABC latest 20 rounds');
});

test('expects to format contest round label correctly', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const label = provider.getContestRoundLabel('abc378');

expect(label).toBe('378');
});
});

describe('ABC319 onwards provider', () => {
test('expects to filter tasks to include only ABC319 and later', () => {
const provider = contestTableProviders.abc319Onwards;
const filtered = provider.filter(mockTaskResults);

expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
expect(
filtered.every((task) => {
const round = parseInt(task.contest_id.replace('abc', ''), 10);
return round >= 319 && round <= 999;
}),
).toBeTruthy();
});

test('expects to get correct metadata', () => {
const provider = contestTableProviders.abc319Onwards;
const metadata = provider.getMetadata();

expect(metadata.title).toBe('AtCoder Beginner Contest 319 〜 ');
expect(metadata.buttonLabel).toBe('ABC 319 〜 ');
expect(metadata.ariaLabel).toBe('Filter contests from ABC 319 onwards');
});

test('expects to format contest round label correctly', () => {
const provider = contestTableProviders.abc319Onwards;
const label = provider.getContestRoundLabel('abc397');

expect(label).toBe('397');
});
});

describe('ABC212 to ABC318 provider', () => {
test('expects to filter tasks to include only ABC between 212 and 318', () => {
const provider = contestTableProviders.fromAbc212ToAbc318;
const filtered = provider.filter(mockTaskResults);

expect(filtered.every((task) => task.contest_id.startsWith('abc'))).toBeTruthy();
expect(
filtered.every((task) => {
const round = parseInt(task.contest_id.replace('abc', ''), 10);
return round >= 212 && round <= 318;
}),
).toBeTruthy();
});

test('expects to get correct metadata', () => {
const provider = contestTableProviders.fromAbc212ToAbc318;
const metadata = provider.getMetadata();

expect(metadata.title).toBe('AtCoder Beginner Contest 212 〜 318');
expect(metadata.buttonLabel).toBe('ABC 212 〜 318');
expect(metadata.ariaLabel).toBe('Filter contests from ABC 212 to ABC 318');
});

test('expects to format contest round label correctly', () => {
const provider = contestTableProviders.fromAbc212ToAbc318;
const label = provider.getContestRoundLabel('abc318');

expect(label).toBe('318');
});
});

describe('Common provider functionality', () => {
test('expects to get contest round IDs correctly', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const filtered = [
{ contest_id: 'abc397', task_id: 'a', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'a', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'b', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'c', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'e', status_name: 'ac_with_editorial' },
{ contest_id: 'abc319', task_id: 'f', status_name: 'wa' },
{ contest_id: 'abc319', task_id: 'g', status_name: 'ns' },
{ contest_id: 'abc318', task_id: 'a', status_name: 'ac' },
] as TaskResults;

const roundIds = provider.getContestRoundIds(filtered);

expect(roundIds).toEqual(['abc397', 'abc319', 'abc318']);
});

test('expects to get header IDs for tasks correctly', () => {
const provider = contestTableProviders.abcLatest20Rounds;
const filtered = [
{ contest_id: 'abc319', task_id: 'abc319_a', task_table_index: 'A', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'abc319_b', task_table_index: 'B', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'abc319_c', task_table_index: 'C', status_name: 'ac' },
{ contest_id: 'abc319', task_id: 'abc319_d', task_table_index: 'D', status_name: 'ac' },
{
contest_id: 'abc319',
task_id: 'abc319_e',
task_table_index: 'E',
status_name: 'ac_with_editorial',
},
{ contest_id: 'abc319', task_id: 'abc319_f', task_table_index: 'F', status_name: 'wa' },
{ contest_id: 'abc319', task_id: 'abc319_g', task_table_index: 'G', status_name: 'ns' },
] as TaskResults;

const headerIds = provider.getHeaderIdsForTask(filtered);

expect(headerIds).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']);
});
});
});
136 changes: 136 additions & 0 deletions src/test/lib/utils/test_cases/contest_table_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { TaskResult, TaskResults } from '$lib/types/task';

// Default task result with minimal initialization.
// Most fields are empty strings as they're not relevant for these tests.
// The updated_at field is set to Unix epoch as we only care about task_table_index
// and task_id for header name testing.
const defaultTaskResult: TaskResult = {
is_ac: false,
user_id: '',
status_name: '',
status_id: '',
submission_status_image_path: '',
submission_status_label_name: '',
contest_id: '',
task_table_index: '',
task_id: '',
title: '',
grade: '',
updated_at: new Date(0), // Use the Unix epoch as the default value.
};

const AC = 'ac';
const AC_WITH_EDITORIAL = 'ac_with_editorial';
const TRYING = 'wa';
const PENDING = 'ns';

/**
* Creates a new TaskResult using defaultTaskResult as a base, overriding the taskId and taskTableIndex.
* @param contestId - The ID of the contest (e.g., 'abc212')
* @param taskId - The ID of the task (e.g., 'abc212_a')
* @param taskTableIndex - The index of the task in the table (e.g., 'A', 'B', 'Ex')
* @param statusName - submission status of the task (e.g., 'ac', 'ac_with_editorial', 'wa', 'ns)
* @returns A new TaskResult object with the specified taskId and taskTableIndex
*/
function createTaskResultWithTaskTableIndex(
contestId: string,
taskId: string,
taskTableIndex: string,
statusName: string,
): TaskResult {
return {
...defaultTaskResult,
contest_id: contestId,
task_id: taskId,
task_table_index: taskTableIndex,
status_name: statusName,
is_ac: statusName === AC || statusName === AC_WITH_EDITORIAL,
};
}

// ABC212 - ABC232: 8 tasks (A, B, C, D, E, F, G and H)
const abc212_a = createTaskResultWithTaskTableIndex('abc212', 'abc212_a', 'A', AC);
const abc212_b = createTaskResultWithTaskTableIndex('abc212', 'abc212_b', 'B', AC);
const abc212_f = createTaskResultWithTaskTableIndex('abc212', 'abc212_f', 'F', AC_WITH_EDITORIAL);
const abc212_g = createTaskResultWithTaskTableIndex('abc212', 'abc212_g', 'G', TRYING);
const abc212_h = createTaskResultWithTaskTableIndex('abc212', 'abc212_h', 'H', PENDING);
const abc213_h = createTaskResultWithTaskTableIndex('abc213', 'abc213_h', 'H', PENDING);
const abc232_h = createTaskResultWithTaskTableIndex('abc232', 'abc232_h', 'H', TRYING);

// ABC233 - ABC318: 8 tasks (A, B, C, D, E, F, G and Ex)
const abc233_a = createTaskResultWithTaskTableIndex('abc233', 'abc233_a', 'A', AC);
const abc233_b = createTaskResultWithTaskTableIndex('abc233', 'abc233_b', 'B', TRYING);
const abc233_ex = createTaskResultWithTaskTableIndex('abc233', 'abc233_ex', 'Ex', PENDING);
const abc234_ex = createTaskResultWithTaskTableIndex('abc234', 'abc234_ex', 'Ex', AC);
const abc317_ex = createTaskResultWithTaskTableIndex('abc317', 'abc317_ex', 'Ex', TRYING);
const abc318_ex = createTaskResultWithTaskTableIndex('abc318', 'abc318_ex', 'Ex', PENDING);

// ABC319 - : 7 tasks (A, B, C, D, E, F and G)
const abc319_a = createTaskResultWithTaskTableIndex('abc319', 'abc319_a', 'A', AC);
const abc319_b = createTaskResultWithTaskTableIndex('abc319', 'abc319_b', 'B', AC);
const abc319_f = createTaskResultWithTaskTableIndex('abc319', 'abc319_f', 'F', TRYING);
const abc319_g = createTaskResultWithTaskTableIndex('abc319', 'abc319_g', 'G', PENDING);
const abc376_g = createTaskResultWithTaskTableIndex('abc376', 'abc376_g', 'G', AC);
const abc377_g = createTaskResultWithTaskTableIndex('abc377', 'abc377_g', 'G', AC);
const abc378_g = createTaskResultWithTaskTableIndex('abc378', 'abc378_g', 'G', TRYING);
const abc379_g = createTaskResultWithTaskTableIndex('abc379', 'abc379_g', 'G', PENDING);
const abc380_g = createTaskResultWithTaskTableIndex('abc380', 'abc380_g', 'G', AC);
const abc381_g = createTaskResultWithTaskTableIndex('abc381', 'abc381_g', 'G', AC_WITH_EDITORIAL);
const abc382_g = createTaskResultWithTaskTableIndex('abc382', 'abc382_g', 'G', TRYING);
const abc383_g = createTaskResultWithTaskTableIndex('abc383', 'abc383_g', 'G', AC);
const abc384_g = createTaskResultWithTaskTableIndex('abc384', 'abc384_g', 'G', AC);
const abc385_g = createTaskResultWithTaskTableIndex('abc385', 'abc385_g', 'G', AC);
const abc386_g = createTaskResultWithTaskTableIndex('abc386', 'abc386_g', 'G', AC_WITH_EDITORIAL);
const abc387_g = createTaskResultWithTaskTableIndex('abc387', 'abc387_g', 'G', TRYING);
const abc388_g = createTaskResultWithTaskTableIndex('abc388', 'abc388_g', 'G', TRYING);
const abc389_g = createTaskResultWithTaskTableIndex('abc389', 'abc389_g', 'G', TRYING);
const abc390_g = createTaskResultWithTaskTableIndex('abc390', 'abc390_g', 'G', TRYING);
const abc391_g = createTaskResultWithTaskTableIndex('abc391', 'abc391_g', 'G', TRYING);
const abc392_g = createTaskResultWithTaskTableIndex('abc392', 'abc392_g', 'G', TRYING);
const abc393_g = createTaskResultWithTaskTableIndex('abc393', 'abc393_g', 'G', TRYING);
const abc394_g = createTaskResultWithTaskTableIndex('abc394', 'abc394_g', 'G', TRYING);
const abc395_g = createTaskResultWithTaskTableIndex('abc395', 'abc395_g', 'G', TRYING);
const abc396_g = createTaskResultWithTaskTableIndex('abc396', 'abc396_g', 'G', TRYING);
const abc397_g = createTaskResultWithTaskTableIndex('abc397', 'abc397_g', 'G', TRYING);

export const taskResultsForContestTableProvider: TaskResults = [
abc212_a,
abc212_b,
abc212_f,
abc212_g,
abc212_h,
abc213_h,
abc232_h,
abc233_a,
abc233_b,
abc233_ex,
abc234_ex,
abc317_ex,
abc318_ex,
abc319_a,
abc319_b,
abc319_f,
abc319_g,
abc376_g,
abc377_g,
abc378_g,
abc379_g,
abc380_g,
abc381_g,
abc382_g,
abc383_g,
abc384_g,
abc385_g,
abc386_g,
abc387_g,
abc388_g,
abc389_g,
abc390_g,
abc391_g,
abc392_g,
abc393_g,
abc394_g,
abc395_g,
abc396_g,
abc397_g,
];
Loading