Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,6 @@ describe.each([

**ターゲット**:

- `ABCLatest20RoundsProvider` テストの `generateTable` 検証を追加
- `ABC319Onwards` と `ABC212to318` のテストもTypical90 同等レベルに

**期間**: 2-3 日
Expand Down
106 changes: 106 additions & 0 deletions docs/dev-notes/2025-12-31/remove-abc-latest-20-rounds/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# **ABCLatest20RoundsProvider の削除計画**

## **目的**

`ABCLatest20RoundsProvider` を削除し、関連するコードやテストを整理することで、コードベースの簡素化とメンテナンス性の向上を図る。

## ** 理由**

- ABC 319 〜 に完全に包含されている
- X 公式アカウントでのアンケートで、「ABC 最新 20 回」を日常的に使っているユーザは投票数の約1/4程度
- 当初の目的であった初回表示の負荷軽減対策には、「ABS」をデフォルトで表示することで対応

---

## **削除対象**

### 1. `src/lib/utils/contest_table_provider.ts`

- **行167**: `export class ABCLatest20RoundsProvider extends ContestTableProviderBase {` を削除。
- **行1086**: `.addProvider(new ABCLatest20RoundsProvider(ContestType.ABC)),` を削除。
- **行1244**: `abcLatest20Rounds: prepareContestProviderPresets().ABCLatest20Rounds(),` を削除。

### 2. `src/test/lib/utils/contest_table_provider.test.ts`

- **`describe('ABC providers', () => {` 内の以下を削除**:
```typescript
{
providerClass: ABCLatest20RoundsProvider,
label: 'Latest 20 rounds',
displayConfig: {
roundLabelWidth: 'xl:w-16',
tableBodyCellsWidth: 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 px-1 py-1',
},
},
```
- **`describe('ABC Latest 20 Rounds', () => {` のテスト全体を削除**。
- **行349, 357, 374, 382**: `ABCLatest20RoundsProvider` に関連するテストを削除。
- **行2425, 2437, 2466, 2512, 2526, 2604**: `ABCLatest20RoundsProvider` を `ABSProvider` に置き換え。

### 3. `src/lib/stores/active_contest_type.svelte.ts`

- **行16**: コメント内の `'abcLatest20Rounds'` を `'abs'` に変更。
- **行21**: `'abcLatest20Rounds'` を `'abs'` に変更。
- **行28**: コメント内の `'abcLatest20Rounds'` を `'abs'` に変更。
- **行30**: コンストラクタのデフォルト値 `'abcLatest20Rounds'` を `'abs'` に変更。
- **行79**: コメント内の `'abcLatest20Rounds'` を `'abs'` に変更。
- **行82**: `this.storage.value = 'abcLatest20Rounds';` を `this.storage.value = 'abs';` に変更。

### 4. `src/test/lib/stores/active_contest_type.svelte.test.ts`

- **行44**: `expect(store.get()).toBe('abcLatest20Rounds');` を `expect(store.get()).toBe('abs');` に変更。
- **行48**: 同上。
- **行64**: `expect(store.isSame('abcLatest20Rounds' as ContestTableProviderGroups)).toBe(true);` を `expect(store.isSame('abs' as ContestTableProviderGroups)).toBe(true);` に変更。
- **行70**: 同上。
- **行75**: 同上。
- **行86**: `expect(store.get()).toBe('abcLatest20Rounds');` を `expect(store.get()).toBe('abs');` に変更。
- **行93**: 同上。
- **行101**: `expect(newStore.get()).toBe('abcLatest20Rounds');` を `expect(newStore.get()).toBe('abs');` に変更。
- **行108**: 同上。
- **行115**: `'abcLatest20Rounds' as ContestTableProviderGroups` を `'abs' as ContestTableProviderGroups` に変更。
- **行138**: `expect(activeContestTypeStore.get()).toBe('abcLatest20Rounds');` を `expect(activeContestTypeStore.get()).toBe('abs');` に変更。

### 5. ドキュメントの更新

- **`/usr/src/app/docs/dev-notes/2025-11-01/add_and_refactoring_tests_for_contest_table_provider/plan.md`**
- **行516**: `- ABCLatest20RoundsProvider テストの generateTable 検証を追加` を削除。

---

## **影響範囲**

1. **`src/lib/utils/contest_table_provider.ts`**
- 他のプロバイダーやロジックに影響を与えないか確認済み。

2. **`src/test/lib/utils/contest_table_provider.test.ts`**
- 削除対象のテストが他のテストケースに依存していないことを確認済み。

3. **テストカバレッジ**
- 削除前後でテストカバレッジを比較し、削除が他のテストに影響を与えないことを確認済み。

4. **ドキュメント**
- 関連するドキュメントを特定し、更新箇所を明確化。

---

## **次のステップ**

1. **コードの修正**
- 上記の削除対象箇所を修正。

2. **テストの実行**
- 修正後にテストを再実行し、他の箇所に影響がないことを確認。

3. **ドキュメントの更新**
- 削除に伴う変更をドキュメントに反映。

## 教訓

1. **削除対象の影響範囲を明確化する**
- 削除対象が他のコードやテストに与える影響を事前に洗い出し、計画に反映する。

2. **テストカバレッジの維持**
- 削除後もテストカバレッジが低下しないよう、必要に応じて代替テストを追加する。

3. **ドキュメントの更新を徹底する**
- 削除に伴う変更をドキュメントに反映し、後続の開発者が変更内容を正確に把握できるようにする。
15 changes: 6 additions & 9 deletions src/lib/stores/active_contest_type.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,18 @@ import {
*
* The store uses the ContestTableProviderGroups type which represents
* different contest table configurations or data providers,
* with a default value of 'abcLatest20Rounds'.
* with a default value of 'abs'.
*/
export class ActiveContestTypeStore {
private storage = useLocalStorage<ContestTableProviderGroups>(
'contest_table_providers',
'abcLatest20Rounds',
);
private storage = useLocalStorage<ContestTableProviderGroups>('contest_table_providers', 'abs');

/**
* Creates an instance with the specified contest type.
*
* @param defaultContestType - The default contest type to initialize.
* Defaults to 'abcLatest20Rounds'.
* Defaults to 'abs'.
*/
constructor(defaultContestType: ContestTableProviderGroups = 'abcLatest20Rounds') {
constructor(defaultContestType: ContestTableProviderGroups = 'abs') {
if (!this.isValidContestType(this.storage.value)) {
this.storage.value = defaultContestType;
}
Expand Down Expand Up @@ -76,10 +73,10 @@ export class ActiveContestTypeStore {

/**
* Resets the active contest type to the default value.
* Sets the internal value to 'abcLatest20Rounds'.
* Sets the internal value to 'abs'.
*/
reset(): void {
this.storage.value = 'abcLatest20Rounds';
this.storage.value = 'abs';
}
}

Expand Down
45 changes: 0 additions & 45 deletions src/lib/utils/contest_table_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,41 +164,6 @@ export class ABSProvider extends ContestTableProviderBase {
}
}

export class ABCLatest20RoundsProvider extends ContestTableProviderBase {
filter(taskResults: TaskResults): TaskResults {
const taskResultsOnlyABC = taskResults.filter(this.setFilterCondition());

const CONTEST_ROUND_COUNT = 20;
const latest20ContestIds = Array.from(
new Set(taskResultsOnlyABC.map((taskResult: TaskResult) => taskResult.contest_id)),
)
.sort()
.reverse()
.slice(0, CONTEST_ROUND_COUNT);

return taskResultsOnlyABC.filter((task: TaskResult) =>
latest20ContestIds.includes(task.contest_id),
);
}

protected setFilterCondition(): (taskResult: TaskResult) => boolean {
// Note: Narrow down taskResults in advance to reduce time to display.
return (task: TaskResult) => classifyContest(task.contest_id) === ContestType.ABC;
}

getMetadata(): ContestTableMetaData {
return {
title: 'AtCoder Beginner Contest 最新 20 回',
abbreviationName: 'abcLatest20Rounds',
};
}

getContestRoundLabel(contestId: string): string {
const contestNameLabel = getContestNameLabel(contestId);
return contestNameLabel.replace('ABC ', '');
}
}

// ABC319 〜 (2023/09/09 〜 )
// 7 tasks per contest
export class ABC319OnwardsProvider extends ContestTableProviderBase {
Expand Down Expand Up @@ -1076,15 +1041,6 @@ export const prepareContestProviderPresets = () => {
ariaLabel: 'Filter AtCoder Beginners Selection',
}).addProvider(new ABSProvider(ContestType.ABS)),

/**
* Single group for ABC latest 20 rounds
*/
ABCLatest20Rounds: () =>
new ContestTableProviderGroup(`ABC Latest 20 Rounds`, {
buttonLabel: 'ABC 最新 20 回',
ariaLabel: 'Filter ABC latest 20 rounds',
}).addProvider(new ABCLatest20RoundsProvider(ContestType.ABC)),

/**
* Single group for ABC 319 onwards
*/
Expand Down Expand Up @@ -1241,7 +1197,6 @@ export const prepareContestProviderPresets = () => {

export const contestTableProviderGroups = {
abs: prepareContestProviderPresets().ABS(),
abcLatest20Rounds: prepareContestProviderPresets().ABCLatest20Rounds(),
abc319Onwards: prepareContestProviderPresets().ABC319Onwards(),
fromAbc212ToAbc318: prepareContestProviderPresets().ABC212ToABC318(),
fromAbc126ToAbc211: prepareContestProviderPresets().ABC126ToABC211(),
Expand Down
22 changes: 11 additions & 11 deletions src/test/lib/stores/active_contest_type.svelte.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ describe('ActiveContestTypeStore', () => {
});

test('expects to initialize with default value', () => {
expect(store.get()).toBe('abcLatest20Rounds');
expect(store.get()).toBe('abs');
});

test('expects to return the current value when calling get()', () => {
expect(store.get()).toBe('abcLatest20Rounds');
expect(store.get()).toBe('abs');

// Change the value and verify get() returns the new value
store.set('abc319Onwards' as ContestTableProviderGroups);
Expand All @@ -61,18 +61,18 @@ describe('ActiveContestTypeStore', () => {
});

test('expects to correctly determine if contest type is the same with isSame()', () => {
expect(store.isSame('abcLatest20Rounds' as ContestTableProviderGroups)).toBe(true);
expect(store.isSame('abs' as ContestTableProviderGroups)).toBe(true);
expect(store.isSame('abc319Onwards' as ContestTableProviderGroups)).toBe(false);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviderGroups)).toBe(false);

store.set('abc319Onwards' as ContestTableProviderGroups);
expect(store.isSame('abc319Onwards' as ContestTableProviderGroups)).toBe(true);
expect(store.isSame('abcLatest20Rounds' as ContestTableProviderGroups)).toBe(false);
expect(store.isSame('abs' as ContestTableProviderGroups)).toBe(false);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviderGroups)).toBe(false);

store.set('fromAbc212ToAbc318' as ContestTableProviderGroups);
expect(store.isSame('fromAbc212ToAbc318' as ContestTableProviderGroups)).toBe(true);
expect(store.isSame('abcLatest20Rounds' as ContestTableProviderGroups)).toBe(false);
expect(store.isSame('abs' as ContestTableProviderGroups)).toBe(false);
expect(store.isSame('abc319Onwards' as ContestTableProviderGroups)).toBe(false);
});

Expand All @@ -83,36 +83,36 @@ describe('ActiveContestTypeStore', () => {

// Call reset and verify it goes back to default
store.reset();
expect(store.get()).toBe('abcLatest20Rounds');
expect(store.get()).toBe('abs');

// Change to a different value and reset again to verify consistency
store.set('fromAbc212ToAbc318' as ContestTableProviderGroups);
expect(store.get()).toBe('fromAbc212ToAbc318');

store.reset();
expect(store.get()).toBe('abcLatest20Rounds');
expect(store.get()).toBe('abs');
});

test('expects to reset to default when initialized with invalid localStorage key', () => {
// Simulate invalid key in localStorage
mockStorage['contest_table_providers'] = JSON.stringify('invalidContestType');

const newStore = new ActiveContestTypeStore();
expect(newStore.get()).toBe('abcLatest20Rounds');
expect(newStore.get()).toBe('abs');
});

test('expects to reset to default when initialized with null', () => {
mockStorage['contest_table_providers'] = JSON.stringify(null);

const newStore = new ActiveContestTypeStore();
expect(newStore.get()).toBe('abcLatest20Rounds');
expect(newStore.get()).toBe('abs');
});

test('expects to handle multiple contest type changes', () => {
const types: ContestTableProviderGroups[] = [
'abc319Onwards' as ContestTableProviderGroups,
'fromAbc212ToAbc318' as ContestTableProviderGroups,
'abcLatest20Rounds' as ContestTableProviderGroups,
'abs' as ContestTableProviderGroups,
];

types.forEach((type) => {
Expand All @@ -135,6 +135,6 @@ describe('Active contest type store in SSR', () => {
});

test('handles SSR gracefully', () => {
expect(activeContestTypeStore.get()).toBe('abcLatest20Rounds');
expect(activeContestTypeStore.get()).toBe('abs');
});
});
Loading
Loading