diff --git a/.claude/skills/add-contest-table-provider/instructions.md b/.claude/skills/add-contest-table-provider/instructions.md index 098a29600..aa0920f44 100644 --- a/.claude/skills/add-contest-table-provider/instructions.md +++ b/.claude/skills/add-contest-table-provider/instructions.md @@ -21,6 +21,7 @@ Step 0 (seed check) is already done. Confirm the following before touching code: **Pattern 1 additional:** - Numeric range: start and end (open-ended if no upper bound)? +- Splitting an existing "Onwards" provider? Rename to `{Start}To{End}Provider`; the new special-edition provider uses a fixed section string (e.g. `super(contestType, '0100')`) to coexist under the same ContestType. - Shared problems with another contest (e.g. ARC–ABC overlap)? Which contest_ids appear in both? - Round label format (e.g. `ABC 042`)? @@ -93,6 +94,7 @@ Step 0 (seed check) is already done. Confirm the following before touching code: - [ ] Add test cases covering range boundaries and at least one mid-range value - [ ] If shared problems exist: add a test case with mixed contest_ids to confirm exclusion +- [ ] If splitting an existing range: add a combined-fixture test confirming the upper bound excludes the adjacent range's contest_id: `[...fixtureA, ...fixtureB]` → `filter` → assert `some(task => task.contest_id === 'out-of-range') === false` - [ ] `pnpm test:unit ` — **expect RED** - [ ] Implement Provider using `parseContestRound()` range check - [ ] `pnpm test:unit ` — **expect GREEN** @@ -124,13 +126,13 @@ Step 0 (seed check) is already done. Confirm the following before touching code: - [ ] Update `contest_table_provider_groups.test.ts`: - New group name string, `buttonLabel`, `ariaLabel` - `getSize()` incremented to reflect the new provider count - - Add `getProvider(ContestType.XXX)` assertion + - Add `getProvider(ContestType.XXX)` assertion; for section-based providers use `getProvider(ContestType.XXX, 'section')` - Add import of new Provider class - [ ] `pnpm test:unit contest_table_provider_groups.test.ts` — **expect RED** - [ ] Update `contest_table_provider_groups.ts`: - Add import of new Provider class - Update group name string, `buttonLabel`, `ariaLabel` - - Add `new XXXProvider(ContestType.XXX)` to `addProviders()` + - Add `new XXXProvider(ContestType.XXX)` to `addProvider()` chain — **`addProvider` call order = display order (first = top)** - [ ] `pnpm test:unit src/features/tasks/utils/contest-table/` — **expect GREEN** --- diff --git a/docs/guides/how-to-add-contest-table-provider.md b/docs/guides/how-to-add-contest-table-provider.md index d4fcafa62..358200993 100644 --- a/docs/guides/how-to-add-contest-table-provider.md +++ b/docs/guides/how-to-add-contest-table-provider.md @@ -202,6 +202,7 @@ protected setFilterCondition(): (taskResult: TaskResult) => boolean { - `super(contestType, String(year))` でセクションを年度文字列にし、プロバイダキーを `AOJ_ICPC::2025` のように一意化 - 年度範囲定数(`OLDEST_YEAR` / `LATEST_YEAR`)をモジュールトップで `export` し、tests でも参照できるようにする - グループ登録時は最新年から古い年へ降順ループし、テーブルを新しい順に並べる +- **固定セクションバリアント**: N 回インスタンス化ではなく、同一グループ内で同一 ContestType のプロバイダーを 2 種類共存させる場合も同じ仕組みが使える。一方に固定文字列を渡し(例: `super(contestType, '0100')` → key `AWC::0100`)、もう一方はセクションなし(key `AWC`)とすることで衝突を回避する **実装例**: @@ -312,18 +313,18 @@ class TessokuBookSectionProvider extends TessokuBookProvider { ### 範囲フィルタ型 -| コンテスト | 範囲 | フォーマット | セクション | ラベル | 特有の注意 | -| ----------- | -------- | ------------ | ---------- | ------ | ------------- | -| ABC 001-041 | 001~041 | 001, 041 | A~D | あり | 旧形式 | -| ABC 042-125 | 042~125 | 042, 125 | A~D | あり | 共有問題(ARC) | -| ABC 126-211 | 126~211 | 126, 211 | A~F | あり | 6問制 | -| ABC 212-318 | 212~318 | 212, 318 | A~Ex/H | あり | 8問制 | -| ABC 319- | 319~ | 319 | A~G | あり | 標準形式 | -| ARC 001-057 | 001~057 | 001, 057 | A~D | あり | 旧形式 | -| ARC 058-103 | 058~103 | 058, 103 | C~F | あり | 共有問題(ABC) | -| ARC 104- | 104~ | 104 | 4~6問 | あり | - | -| AGC 001- | 001~ | 001 | 4~7問 | あり | - | -| AWC 0001- | 0001~ | 0001 | A~E | あり | - | +| コンテスト | 範囲 | フォーマット | セクション | ラベル | 特有の注意 | +| ------------- | ---------- | ------------ | ---------- | ------ | ------------- | +| ABC 001-041 | 001~041 | 001, 041 | A~D | あり | 旧形式 | +| ABC 042-125 | 042~125 | 042, 125 | A~D | あり | 共有問題(ARC) | +| ABC 126-211 | 126~211 | 126, 211 | A~F | あり | 6問制 | +| ABC 212-318 | 212~318 | 212, 318 | A~Ex/H | あり | 8問制 | +| ABC 319- | 319~ | 319 | A~G | あり | 標準形式 | +| ARC 001-057 | 001~057 | 001, 057 | A~D | あり | 旧形式 | +| ARC 058-103 | 058~103 | 058, 103 | C~F | あり | 共有問題(ABC) | +| ARC 104- | 104~ | 104 | 4~6問 | あり | - | +| AGC 001- | 001~ | 001 | 4~7問 | あり | - | +| AWC 0001-0099 | 0001~0099 | 0001 | A~E | あり | - | ### 単一ソース型 @@ -336,9 +337,11 @@ class TessokuBookSectionProvider extends TessokuBookProvider { | ACL_PRACTICE | `'practice2'` | 12問 | A~L | | ACL_BEGINNER\* | `'abl'` | 6問 | A~F | | ACL_CONTEST1\* | `'acl1'` | 6問 | A~F | +| AWC0100† | `'awc0100'` | 15問 | A~O | \*注: ACL_PRACTICE、ACL_BEGINNER、ACL_CONTEST1 は `Acl` グループの下で 3 つのコンテストが統一管理されています。 \*\*注: EDPC・TDPC・NDPC・FPS 24 は `dps` グループ下で 4 つのコンテストが統一管理されています。 +†注: ContestType.AWC を再利用し、section `'0100'` で同グループ内の AWC0001To0099Provider と共存(provider key = `AWC::0100`)。`getProvider(ContestType.AWC, '0100')` で取得。 ### 複合ソース型 @@ -367,7 +370,14 @@ class TessokuBookSectionProvider extends TessokuBookProvider { ### パターン固有テスト -- **範囲フィルタ型**: 範囲境界値テスト、共有問題の有無確認 +- **範囲フィルタ型**: 範囲境界値テスト、共有問題の有無確認。既存プロバイダーを上限付きに分割した場合は、隣接するもう一方のフィクスチャを結合して上限境界の除外を確認する: + + ```typescript + const combined = [...taskResultsForAWC0001To0099Provider, ...taskResultsForAWC0100Provider]; + const filtered = provider.filter(combined); + expect(filtered.some((task) => task.contest_id === 'awc0100')).toBe(false); + ``` + - **複合ソース型**: 複数 contest_id 混在テスト、セクション分割ロジック ### Vitest テスト例 @@ -496,7 +506,7 @@ export const taskResultsForNewProvider: TaskResults = [ --- -## よくあるミス Top 5 +## よくあるミス ### 1. **getDisplayConfig() での属性漏れ** @@ -598,6 +608,21 @@ describe('CustomProvider with unique config', () => { --- +### 6. `addProvider` の順序を意識しない + +**問題**: 同一グループ内の複数プロバイダーは `addProvider` の呼び出し順が画面上の表示順(先 = 上)になる。 + +**解決策**: 上に表示したいプロバイダーを先に `addProvider` する。 + +```typescript +// AWC0100 を AWC0001-0099 の上に表示する場合 +group + .addProvider(new AWC0100Provider(ContestType.AWC)) // 上 + .addProvider(new AWC0001To0099Provider(ContestType.AWC)); // 下 +``` + +--- + ## 実装完了後 ### ドキュメント更新チェックリスト @@ -622,7 +647,7 @@ describe('CustomProvider with unique config', () => { - [#2837](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2837) - AGC001OnwardsProvider - [#2838](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2838) - ABC001~041 & ARC001~057 - [#2840](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2840)、[#3108](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3108) - ABCLikeProvider -- [#3153](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3153) - AWC0001OnwardsProvider +- [#3153](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3153) - AWC0001To0099Provider(旧: AWC0001OnwardsProvider)、AWC0100Provider - [#2776](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2776) - TessokuBookProvider - [#2785](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2785) - MathAndAlgorithmProvider - [#2797](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2797) - FPS24Provider @@ -638,4 +663,4 @@ describe('CustomProvider with unique config', () => { --- -**最終更新**: 2026-05-10 +**最終更新**: 2026-06-24 diff --git a/prisma/tasks.ts b/prisma/tasks.ts index ec1d33f2b..cc2be5877 100755 --- a/prisma/tasks.ts +++ b/prisma/tasks.ts @@ -8354,6 +8354,111 @@ export const tasks = [ name: '4/N', title: 'C. 4/N', }, + { + id: 'awc0100_a', + contest_id: 'awc0100', + problem_index: 'A', + name: 'Task A', + title: 'A. Task A', + }, + { + id: 'awc0100_b', + contest_id: 'awc0100', + problem_index: 'B', + name: 'Task B', + title: 'B. Task B', + }, + { + id: 'awc0100_c', + contest_id: 'awc0100', + problem_index: 'C', + name: 'Task C', + title: 'C. Task C', + }, + { + id: 'awc0100_d', + contest_id: 'awc0100', + problem_index: 'D', + name: 'Task D', + title: 'D. Task D', + }, + { + id: 'awc0100_e', + contest_id: 'awc0100', + problem_index: 'E', + name: 'Task E', + title: 'E. Task E', + }, + { + id: 'awc0100_f', + contest_id: 'awc0100', + problem_index: 'F', + name: 'Task F', + title: 'F. Task F', + }, + { + id: 'awc0100_g', + contest_id: 'awc0100', + problem_index: 'G', + name: 'Task G', + title: 'G. Task G', + }, + { + id: 'awc0100_h', + contest_id: 'awc0100', + problem_index: 'H', + name: 'Task H', + title: 'H. Task H', + }, + { + id: 'awc0100_i', + contest_id: 'awc0100', + problem_index: 'I', + name: 'Task I', + title: 'I. Task I', + }, + { + id: 'awc0100_j', + contest_id: 'awc0100', + problem_index: 'J', + name: 'Task J', + title: 'J. Task J', + }, + { + id: 'awc0100_k', + contest_id: 'awc0100', + problem_index: 'K', + name: 'Task K', + title: 'K. Task K', + }, + { + id: 'awc0100_l', + contest_id: 'awc0100', + problem_index: 'L', + name: 'Task L', + title: 'L. Task L', + }, + { + id: 'awc0100_m', + contest_id: 'awc0100', + problem_index: 'M', + name: 'Task M', + title: 'M. Task M', + }, + { + id: 'awc0100_n', + contest_id: 'awc0100', + problem_index: 'N', + name: 'Task N', + title: 'N. Task N', + }, + { + id: 'awc0100_o', + contest_id: 'awc0100', + problem_index: 'O', + name: 'Task O', + title: 'O. Task O', + }, { id: 'awc0005_e', contest_id: 'awc0005', diff --git a/src/features/tasks/fixtures/contest-table/contest_table_provider.ts b/src/features/tasks/fixtures/contest-table/contest_table_provider.ts index 818ca3058..707c15ea9 100644 --- a/src/features/tasks/fixtures/contest-table/contest_table_provider.ts +++ b/src/features/tasks/fixtures/contest-table/contest_table_provider.ts @@ -836,7 +836,7 @@ export const taskResultsForACLProvider: TaskResults = [ acl1_f, ]; -// AWC 0001 onwards: 5 tasks (A, B, C, D, E) +// AWC 0001-0099: 5 tasks (A, B, C, D, E) // Multiple contests to test range filtering const [awc0001_a, awc0001_b, awc0001_c, awc0001_d, awc0001_e] = createContestTasks('awc0001', [ { taskTableIndex: 'A', statusName: AC }, @@ -862,7 +862,7 @@ const [awc0099_a, awc0099_b, awc0099_c, awc0099_d, awc0099_e] = createContestTas { taskTableIndex: 'E', statusName: AC_WITH_EDITORIAL }, ]); -export const taskResultsForAWC0001OnwardsProvider: TaskResults = [ +export const taskResultsForAWC0001To0099Provider: TaskResults = [ awc0001_a, awc0001_b, awc0001_c, @@ -879,3 +879,56 @@ export const taskResultsForAWC0001OnwardsProvider: TaskResults = [ awc0099_d, awc0099_e, ]; + +// AWC0100 (special edition): 15 tasks (A-O) +const [ + awc0100_a, + awc0100_b, + awc0100_c, + awc0100_d, + awc0100_e, + awc0100_f, + awc0100_g, + awc0100_h, + awc0100_i, + awc0100_j, + awc0100_k, + awc0100_l, + awc0100_m, + awc0100_n, + awc0100_o, +] = createContestTasks('awc0100', [ + { taskTableIndex: 'A', statusName: AC }, + { taskTableIndex: 'B', statusName: AC }, + { taskTableIndex: 'C', statusName: AC_WITH_EDITORIAL }, + { taskTableIndex: 'D', statusName: AC }, + { taskTableIndex: 'E', statusName: TRYING }, + { taskTableIndex: 'F', statusName: AC_WITH_EDITORIAL }, + { taskTableIndex: 'G', statusName: TRYING }, + { taskTableIndex: 'H', statusName: PENDING }, + { taskTableIndex: 'I', statusName: PENDING }, + { taskTableIndex: 'J', statusName: PENDING }, + { taskTableIndex: 'K', statusName: PENDING }, + { taskTableIndex: 'L', statusName: PENDING }, + { taskTableIndex: 'M', statusName: PENDING }, + { taskTableIndex: 'N', statusName: PENDING }, + { taskTableIndex: 'O', statusName: PENDING }, +]); + +export const taskResultsForAWC0100Provider: TaskResults = [ + awc0100_a, + awc0100_b, + awc0100_c, + awc0100_d, + awc0100_e, + awc0100_f, + awc0100_g, + awc0100_h, + awc0100_i, + awc0100_j, + awc0100_k, + awc0100_l, + awc0100_m, + awc0100_n, + awc0100_o, +]; diff --git a/src/features/tasks/utils/contest-table/awc_provider.test.ts b/src/features/tasks/utils/contest-table/awc_provider.test.ts index e27a74239..abfc0445a 100644 --- a/src/features/tasks/utils/contest-table/awc_provider.test.ts +++ b/src/features/tasks/utils/contest-table/awc_provider.test.ts @@ -3,38 +3,43 @@ import { describe, test, expect } from 'vitest'; import { ContestType } from '$lib/types/contest'; import type { TaskResults } from '$lib/types/task'; -import { AWC0001OnwardsProvider } from './awc_provider'; -import { taskResultsForAWC0001OnwardsProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider'; +import { AWC0001To0099Provider, AWC0100Provider } from './awc_provider'; +import { + taskResultsForAWC0001To0099Provider, + taskResultsForAWC0100Provider, +} from '$features/tasks/fixtures/contest-table/contest_table_provider'; -describe('AWC0001OnwardsProvider', () => { +describe('AWC0001To0099Provider', () => { test('expects to filter tasks to include only AWC contests', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); - const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + const provider = new AWC0001To0099Provider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001To0099Provider); expect(filtered.length).toBeGreaterThan(0); expect(filtered.every((task) => task.contest_id.startsWith('awc'))).toBe(true); expect(filtered.every((task) => /^awc\d{4}$/.test(task.contest_id))).toBe(true); }); - test('expects to filter by range (awc0001 to awc9999)', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); - const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + test('expects to filter by range (awc0001 to awc0099)', () => { + const provider = new AWC0001To0099Provider(ContestType.AWC); + const combined = [...taskResultsForAWC0001To0099Provider, ...taskResultsForAWC0100Provider]; + const filtered = provider.filter(combined); expect(filtered.some((task) => task.contest_id === 'awc0001')).toBe(true); expect(filtered.some((task) => task.contest_id === 'awc0002')).toBe(true); expect(filtered.some((task) => task.contest_id === 'awc0099')).toBe(true); + expect(filtered.some((task) => task.contest_id === 'awc0100')).toBe(false); }); test('expects to get correct metadata', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const provider = new AWC0001To0099Provider(ContestType.AWC); const metadata = provider.getMetadata(); - expect(metadata.title).toBe('AtCoder Weekday Contest 0001 〜 '); - expect(metadata.abbreviationName).toBe('awc0001Onwards'); + expect(metadata.title).toBe('AtCoder Weekday Contest 0001 〜 0099'); + expect(metadata.abbreviationName).toBe('awc0001To0099'); }); test('expects to return correct display config', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const provider = new AWC0001To0099Provider(ContestType.AWC); const config = provider.getDisplayConfig(); expect(config.isShownHeader).toBe(true); @@ -45,7 +50,7 @@ describe('AWC0001OnwardsProvider', () => { }); test('expects to format contest round label correctly', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const provider = new AWC0001To0099Provider(ContestType.AWC); expect(provider.getContestRoundLabel('awc0001')).toBe('0001'); expect(provider.getContestRoundLabel('awc0002')).toBe('0002'); @@ -53,8 +58,8 @@ describe('AWC0001OnwardsProvider', () => { }); test('expects to generate table for multiple AWC contests', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); - const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + const provider = new AWC0001To0099Provider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001To0099Provider); const table = provider.generateTable(filtered); expect(Object.keys(table).length).toBeGreaterThan(0); @@ -64,8 +69,8 @@ describe('AWC0001OnwardsProvider', () => { }); test('expects each AWC contest to have 5 problems (A-E)', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); - const filtered = provider.filter(taskResultsForAWC0001OnwardsProvider); + const provider = new AWC0001To0099Provider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0001To0099Provider); const table = provider.generateTable(filtered); Object.entries(table).forEach(([_contestId, problems]) => { @@ -76,7 +81,88 @@ describe('AWC0001OnwardsProvider', () => { }); test('expects to handle empty task results', () => { - const provider = new AWC0001OnwardsProvider(ContestType.AWC); + const provider = new AWC0001To0099Provider(ContestType.AWC); + const filtered = provider.filter([] as TaskResults); + + expect(filtered).toEqual([] as TaskResults); + }); +}); + +describe('AWC0100Provider', () => { + test('expects to filter only awc0100 tasks', () => { + const provider = new AWC0100Provider(ContestType.AWC); + const combined = [...taskResultsForAWC0001To0099Provider, ...taskResultsForAWC0100Provider]; + const filtered = provider.filter(combined); + + expect(filtered.length).toBe(15); + expect(filtered.every((task) => task.contest_id === 'awc0100')).toBe(true); + }); + + test('expects to exclude awc0001 and awc0099', () => { + const provider = new AWC0100Provider(ContestType.AWC); + const combined = [...taskResultsForAWC0001To0099Provider, ...taskResultsForAWC0100Provider]; + const filtered = provider.filter(combined); + + expect(filtered.some((task) => task.contest_id === 'awc0001')).toBe(false); + expect(filtered.some((task) => task.contest_id === 'awc0099')).toBe(false); + }); + + test('expects to get correct metadata', () => { + const provider = new AWC0100Provider(ContestType.AWC); + const metadata = provider.getMetadata(); + + expect(metadata.title).toBe('AtCoder Weekday Contest 0100'); + expect(metadata.abbreviationName).toBe('awc0100'); + }); + + test('expects to return correct display config (EDPC format)', () => { + const provider = new AWC0100Provider(ContestType.AWC); + const config = provider.getDisplayConfig(); + + expect(config.isShownHeader).toBe(false); + expect(config.isShownRoundLabel).toBe(false); + expect(config.roundLabelWidth).toBe(''); + expect(config.tableBodyCellsWidth).toBe( + 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 2xl:w-1/7 px-1 py-2', + ); + expect(config.isShownTaskIndex).toBe(true); + }); + + test('expects getContestRoundLabel to return empty string', () => { + const provider = new AWC0100Provider(ContestType.AWC); + + expect(provider.getContestRoundLabel('awc0100')).toBe(''); + }); + + test('expects generateTable to produce 15 tasks (A-O) for awc0100', () => { + const provider = new AWC0100Provider(ContestType.AWC); + const filtered = provider.filter(taskResultsForAWC0100Provider); + const table = provider.generateTable(filtered); + + expect(table).toHaveProperty('awc0100'); + const problems = table['awc0100']; + expect(Object.keys(problems)).toHaveLength(15); + expect(Object.keys(problems)).toEqual([ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + ]); + }); + + test('expects to handle empty task results', () => { + const provider = new AWC0100Provider(ContestType.AWC); const filtered = provider.filter([] as TaskResults); expect(filtered).toEqual([] as TaskResults); diff --git a/src/features/tasks/utils/contest-table/awc_provider.ts b/src/features/tasks/utils/contest-table/awc_provider.ts index 4c9273124..299a1bbe9 100644 --- a/src/features/tasks/utils/contest-table/awc_provider.ts +++ b/src/features/tasks/utils/contest-table/awc_provider.ts @@ -2,29 +2,30 @@ import { type ContestTableMetaData, type ContestTableDisplayConfig, } from '$features/tasks/types/contest-table/contest_table_provider'; +import { ContestType } from '$lib/types/contest'; import type { TaskResult } from '$lib/types/task'; import { classifyContest, getContestNameLabel } from '$lib/utils/contest'; import { ContestTableProviderBase, parseContestRound } from './contest_table_provider_base'; -// AWC0001 〜 (2026/02/09 〜 ) +// AWC0001 〜 0099 (2026/02/09 〜 2026/06/25) // 5 tasks per contest -export class AWC0001OnwardsProvider extends ContestTableProviderBase { +export class AWC0001To0099Provider extends ContestTableProviderBase { protected setFilterCondition(): (taskResult: TaskResult) => boolean { return (taskResult: TaskResult) => { if (classifyContest(taskResult.contest_id) !== this.contestType) { return false; } const contestRound = parseContestRound(taskResult.contest_id, 'awc'); - return contestRound >= 1 && contestRound <= 9999; + return contestRound >= 1 && contestRound <= 99; }; } getMetadata(): ContestTableMetaData { return { - title: 'AtCoder Weekday Contest 0001 〜 ', - abbreviationName: 'awc0001Onwards', + title: 'AtCoder Weekday Contest 0001 〜 0099', + abbreviationName: 'awc0001To0099', }; } @@ -43,3 +44,38 @@ export class AWC0001OnwardsProvider extends ContestTableProviderBase { return contestNameLabel.replace('AWC ', ''); } } + +// AWC0100 (2026/06/26. special edition, 15 tasks: A-O) +export class AWC0100Provider extends ContestTableProviderBase { + constructor(contestType: ContestType) { + super(contestType, '0100'); // provider key = 'AWC::0100' + } + + protected setFilterCondition(): (taskResult: TaskResult) => boolean { + return (taskResult: TaskResult) => { + if (classifyContest(taskResult.contest_id) !== this.contestType) { + return false; + } + + return taskResult.contest_id === 'awc0100'; + }; + } + + getMetadata(): ContestTableMetaData { + return { title: 'AtCoder Weekday Contest 0100', abbreviationName: 'awc0100' }; + } + + getDisplayConfig(): ContestTableDisplayConfig { + return { + isShownHeader: false, + isShownRoundLabel: false, + roundLabelWidth: '', + tableBodyCellsWidth: 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 2xl:w-1/7 px-1 py-2', + isShownTaskIndex: true, + }; + } + + getContestRoundLabel(_contestId: string): string { + return ''; + } +} diff --git a/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts b/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts index 96c7bbcf6..a78b6c931 100644 --- a/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts +++ b/src/features/tasks/utils/contest-table/contest_table_provider_groups.test.ts @@ -14,7 +14,8 @@ import { ARC001ToARC057Provider, AGC001OnwardsProvider, ABCLikeProvider, - AWC0001OnwardsProvider, + AWC0001To0099Provider, + AWC0100Provider, ACLPracticeProvider, ACLBeginnerProvider, ACLProvider, @@ -185,8 +186,9 @@ describe('prepareContestProviderPresets', () => { buttonLabel: 'AWC 0001 〜 ', ariaLabel: 'Filter contests from AWC 0001 onwards', }); - expect(group.getSize()).toBe(1); - expect(group.getProvider(ContestType.AWC)).toBeInstanceOf(AWC0001OnwardsProvider); + expect(group.getSize()).toBe(2); + expect(group.getProvider(ContestType.AWC, '0100')).toBeInstanceOf(AWC0100Provider); + expect(group.getProvider(ContestType.AWC)).toBeInstanceOf(AWC0001To0099Provider); }); test('expects to create MathAndAlgorithm preset correctly', () => { diff --git a/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts b/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts index f19c84a09..d52860551 100644 --- a/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts +++ b/src/features/tasks/utils/contest-table/contest_table_provider_groups.ts @@ -15,7 +15,7 @@ import { } from './arc_providers'; import { AGC001OnwardsProvider } from './agc_provider'; import { ABCLikeProvider } from './axc_like_provider'; -import { AWC0001OnwardsProvider } from './awc_provider'; +import { AWC0001To0099Provider, AWC0100Provider } from './awc_provider'; import { Typical90Provider } from './typical90_provider'; import { TessokuBookForExamplesProvider, @@ -153,7 +153,9 @@ export const prepareContestProviderPresets = () => { new ContestTableProviderGroup(`AWC 0001 Onwards`, { buttonLabel: 'AWC 0001 〜 ', ariaLabel: 'Filter contests from AWC 0001 onwards', - }).addProvider(new AWC0001OnwardsProvider(ContestType.AWC)), + }) + .addProvider(new AWC0100Provider(ContestType.AWC)) + .addProvider(new AWC0001To0099Provider(ContestType.AWC)), /** * Single group for Typical 90 Problems