From f1e3bfbdb997fb77f750e8b027d17abd901a37c2 Mon Sep 17 00:00:00 2001 From: river0525 <0525sotaro@gmail.com> Date: Thu, 23 Apr 2026 07:35:45 +0900 Subject: [PATCH 1/6] feat(votes): add getRelativeEvaluationJapaneseLabel for dropdown labels --- .../votes/utils/relative_evaluation.test.ts | 33 ++++++++++++++++++ .../votes/utils/relative_evaluation.ts | 34 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/features/votes/utils/relative_evaluation.test.ts b/src/features/votes/utils/relative_evaluation.test.ts index 39aa29085..1e6f31ef1 100644 --- a/src/features/votes/utils/relative_evaluation.test.ts +++ b/src/features/votes/utils/relative_evaluation.test.ts @@ -5,6 +5,7 @@ import { calcGradeDiff, getRelativeEvaluationLabel, getRelativeEvaluationTooltipText, + getRelativeEvaluationJapaneseLabel, } from './relative_evaluation'; describe('calcGradeDiff', () => { @@ -121,3 +122,35 @@ describe('getRelativeEvaluationTooltipText', () => { expect(getRelativeEvaluationTooltipText('--')).toBe('ユーザは「易しい」と評価'); }); }); + +describe('getRelativeEvaluationJapaneseLabel', () => { + test('returns "" for diff <= -3 (out of expected range)', () => { + expect(getRelativeEvaluationJapaneseLabel(-3)).toBe(''); + expect(getRelativeEvaluationJapaneseLabel(-16)).toBe(''); + }); + + test('returns "易しい" for diff === -2', () => { + expect(getRelativeEvaluationJapaneseLabel(-2)).toBe('易しい'); + }); + + test('returns "やや易しい" for diff === -1', () => { + expect(getRelativeEvaluationJapaneseLabel(-1)).toBe('やや易しい'); + }); + + test('returns "ふつう" for diff === 0', () => { + expect(getRelativeEvaluationJapaneseLabel(0)).toBe('ふつう'); + }); + + test('returns "やや難しい" for diff === 1', () => { + expect(getRelativeEvaluationJapaneseLabel(1)).toBe('やや難しい'); + }); + + test('returns "難しい" for diff === 2', () => { + expect(getRelativeEvaluationJapaneseLabel(2)).toBe('難しい'); + }); + + test('returns "" for diff >= 3 (out of expected range)', () => { + expect(getRelativeEvaluationJapaneseLabel(3)).toBe(''); + expect(getRelativeEvaluationJapaneseLabel(16)).toBe(''); + }); +}); diff --git a/src/features/votes/utils/relative_evaluation.ts b/src/features/votes/utils/relative_evaluation.ts index 5fd56c729..5d86d6bc1 100644 --- a/src/features/votes/utils/relative_evaluation.ts +++ b/src/features/votes/utils/relative_evaluation.ts @@ -34,6 +34,40 @@ export function getRelativeEvaluationTooltipText(label: string): string { } } +/** + * Maps a grade difference to a Japanese label for display in the vote dropdown. + * Returns an empty string when the diff falls outside the expected ±2 range. + * + * | diff | label | + * | ---- | ------------ | + * | ≤ −3 | `''` | + * | −2 | `易しい` | + * | −1 | `やや易しい` | + * | 0 | `ふつう` | + * | +1 | `やや難しい` | + * | +2 | `難しい` | + * | ≥ +3 | `''` | + * + * @param diff - The result of {@link calcGradeDiff}. + * @returns The Japanese label string, or `''` if out of expected range. + */ +export function getRelativeEvaluationJapaneseLabel(diff: number): string { + switch (diff) { + case -2: + return '易しい'; + case -1: + return 'やや易しい'; + case 0: + return 'ふつう'; + case 1: + return 'やや難しい'; + case 2: + return '難しい'; + default: + return ''; + } +} + /** * Converts a grade difference to a 5-level relative evaluation label. * From 02dac7665339e71c4cc1156acd8949449d388e5c Mon Sep 17 00:00:00 2001 From: river0525 <0525sotaro@gmail.com> Date: Thu, 23 Apr 2026 07:38:30 +0900 Subject: [PATCH 2/6] feat(votes): show relative evaluation labels in vote dropdown --- .../votes/components/VotableGrade.svelte | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/features/votes/components/VotableGrade.svelte b/src/features/votes/components/VotableGrade.svelte index 95fdf0eb2..745870105 100644 --- a/src/features/votes/components/VotableGrade.svelte +++ b/src/features/votes/components/VotableGrade.svelte @@ -20,6 +20,7 @@ import { calcGradeDiff, getRelativeEvaluationLabel, + getRelativeEvaluationJapaneseLabel, } from '$features/votes/utils/relative_evaluation'; import { SIGNUP_PAGE, LOGIN_PAGE, EDIT_PROFILE_PAGE } from '$lib/constants/navbar-links'; @@ -207,16 +208,29 @@ {#each nonPendingGrades as grade (grade)} handleClick(grade)} class="rounded-md">
- {getTaskGradeLabel(grade)} + {getTaskGradeLabel(grade)} + {#if taskResult.grade !== TaskGrade.PENDING} + {@const diff = calcGradeDiff(taskResult.grade, grade)} + {@const relLabel = getRelativeEvaluationJapaneseLabel(diff)} + {#if relLabel} + {relLabel} + {/if} + {/if} {#if votedGrade === grade} - + {/if}
From db69d0644e36d03ddcb0e83cdbf6e732d3d37d01 Mon Sep 17 00:00:00 2001 From: river0525 <0525sotaro@gmail.com> Date: Thu, 23 Apr 2026 07:42:33 +0900 Subject: [PATCH 3/6] fix(votes): add dark mode variant for orange label and clarify JSDoc --- src/features/votes/components/VotableGrade.svelte | 2 +- src/features/votes/utils/relative_evaluation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/votes/components/VotableGrade.svelte b/src/features/votes/components/VotableGrade.svelte index 745870105..1b3cd4c66 100644 --- a/src/features/votes/components/VotableGrade.svelte +++ b/src/features/votes/components/VotableGrade.svelte @@ -225,7 +225,7 @@ ? 'text-sky-500 dark:text-sky-400' : diff === 0 ? 'text-gray-400 dark:text-gray-500' - : 'text-orange-400'}" + : 'text-orange-400 dark:text-orange-300'}" >{relLabel} {/if} {/if} diff --git a/src/features/votes/utils/relative_evaluation.ts b/src/features/votes/utils/relative_evaluation.ts index 5d86d6bc1..0f48b6c46 100644 --- a/src/features/votes/utils/relative_evaluation.ts +++ b/src/features/votes/utils/relative_evaluation.ts @@ -48,7 +48,7 @@ export function getRelativeEvaluationTooltipText(label: string): string { * | +2 | `難しい` | * | ≥ +3 | `''` | * - * @param diff - The result of {@link calcGradeDiff}. + * @param diff - A grade difference (e.g., from {@link calcGradeDiff}). * @returns The Japanese label string, or `''` if out of expected range. */ export function getRelativeEvaluationJapaneseLabel(diff: number): string { From 15a59295276a28f4637519f0661371b686699df1 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Thu, 23 Apr 2026 09:58:32 +0000 Subject: [PATCH 4/6] chore: fix format --- src/features/votes/components/VotableGrade.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/votes/components/VotableGrade.svelte b/src/features/votes/components/VotableGrade.svelte index 1b3cd4c66..e8196aeee 100644 --- a/src/features/votes/components/VotableGrade.svelte +++ b/src/features/votes/components/VotableGrade.svelte @@ -225,8 +225,8 @@ ? 'text-sky-500 dark:text-sky-400' : diff === 0 ? 'text-gray-400 dark:text-gray-500' - : 'text-orange-400 dark:text-orange-300'}" - >{relLabel} + : 'text-orange-400 dark:text-orange-300'}">{relLabel} {/if} {/if} {#if votedGrade === grade} From e788f99fa1f460d4ff880c0ed9fe9443f58e83a5 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Thu, 23 Apr 2026 11:32:29 +0000 Subject: [PATCH 5/6] refactor(votes): extract color class helpers for relative evaluation badge Co-Authored-By: Claude Sonnet 4.6 --- .../components/RelativeEvaluationBadge.svelte | 11 ++- .../votes/components/VotableGrade.svelte | 7 +- .../votes/utils/relative_evaluation.test.ts | 42 +++++++++++ .../votes/utils/relative_evaluation.ts | 73 ++++++++++++++----- 4 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/features/votes/components/RelativeEvaluationBadge.svelte b/src/features/votes/components/RelativeEvaluationBadge.svelte index cefff7294..04e5683b6 100644 --- a/src/features/votes/components/RelativeEvaluationBadge.svelte +++ b/src/features/votes/components/RelativeEvaluationBadge.svelte @@ -6,6 +6,7 @@ calcGradeDiff, getRelativeEvaluationLabel, getRelativeEvaluationTooltipText, + getRelativeEvaluationBadgeColorClass, } from '$features/votes/utils/relative_evaluation'; interface Props { @@ -24,8 +25,9 @@ let { officialGrade, medianGrade, badgeId, showTooltip = true }: Props = $props(); - const label = $derived(getRelativeEvaluationLabel(calcGradeDiff(officialGrade, medianGrade))); - const isHarder = $derived(label.startsWith('+')); + const diff = $derived(calcGradeDiff(officialGrade, medianGrade)); + const label = $derived(getRelativeEvaluationLabel(diff)); + const badgeColorClass = $derived(getRelativeEvaluationBadgeColorClass(diff)); const tooltipText = $derived(getRelativeEvaluationTooltipText(label)); @@ -33,10 +35,7 @@ {#if label} {relLabel}{relLabel} {/if} {/if} diff --git a/src/features/votes/utils/relative_evaluation.test.ts b/src/features/votes/utils/relative_evaluation.test.ts index 1e6f31ef1..710bcf747 100644 --- a/src/features/votes/utils/relative_evaluation.test.ts +++ b/src/features/votes/utils/relative_evaluation.test.ts @@ -6,6 +6,8 @@ import { getRelativeEvaluationLabel, getRelativeEvaluationTooltipText, getRelativeEvaluationJapaneseLabel, + getRelativeEvaluationColorClass, + getRelativeEvaluationBadgeColorClass, } from './relative_evaluation'; describe('calcGradeDiff', () => { @@ -154,3 +156,43 @@ describe('getRelativeEvaluationJapaneseLabel', () => { expect(getRelativeEvaluationJapaneseLabel(16)).toBe(''); }); }); + +describe('getRelativeEvaluationColorClass', () => { + test('returns sky text classes for negative diff (easier)', () => { + expect(getRelativeEvaluationColorClass(-1)).toBe('text-sky-500 dark:text-sky-400'); + expect(getRelativeEvaluationColorClass(-16)).toBe('text-sky-500 dark:text-sky-400'); + }); + + test('returns gray text classes for diff === 0', () => { + expect(getRelativeEvaluationColorClass(0)).toBe('text-gray-400 dark:text-gray-500'); + }); + + test('returns orange text classes for positive diff (harder)', () => { + expect(getRelativeEvaluationColorClass(1)).toBe('text-orange-400 dark:text-orange-300'); + expect(getRelativeEvaluationColorClass(16)).toBe('text-orange-400 dark:text-orange-300'); + }); +}); + +describe('getRelativeEvaluationBadgeColorClass', () => { + test('returns sky bg classes for negative diff (easier)', () => { + expect(getRelativeEvaluationBadgeColorClass(-1)).toBe( + 'bg-sky-400 text-white dark:bg-sky-500 dark:text-white', + ); + expect(getRelativeEvaluationBadgeColorClass(-16)).toBe( + 'bg-sky-400 text-white dark:bg-sky-500 dark:text-white', + ); + }); + + test('returns empty string for diff === 0 (badge not shown)', () => { + expect(getRelativeEvaluationBadgeColorClass(0)).toBe(''); + }); + + test('returns orange bg classes for positive diff (harder)', () => { + expect(getRelativeEvaluationBadgeColorClass(1)).toBe( + 'bg-orange-400 text-white dark:bg-orange-500 dark:text-white', + ); + expect(getRelativeEvaluationBadgeColorClass(16)).toBe( + 'bg-orange-400 text-white dark:bg-orange-500 dark:text-white', + ); + }); +}); diff --git a/src/features/votes/utils/relative_evaluation.ts b/src/features/votes/utils/relative_evaluation.ts index 0f48b6c46..116cb58bd 100644 --- a/src/features/votes/utils/relative_evaluation.ts +++ b/src/features/votes/utils/relative_evaluation.ts @@ -13,6 +13,36 @@ export function calcGradeDiff(officialGrade: TaskGrade, medianGrade: TaskGrade): return getGradeOrder(medianGrade) - getGradeOrder(officialGrade); } +/** + * Converts a grade difference to a 5-level relative evaluation label. + * + * | diff | label | + * | ------ | ----- | + * | ≤ −2 | `--` | + * | −1 | `-` | + * | 0 | `""` | + * | +1 | `+` | + * | ≥ +2 | `++` | + * + * @param diff - The result of {@link calcGradeDiff}. + * @returns The display label string. + */ +export function getRelativeEvaluationLabel(diff: number): string { + if (diff <= -2) { + return '--'; + } + if (diff === -1) { + return '-'; + } + if (diff === 0) { + return ''; + } + if (diff === 1) { + return '+'; + } + return '++'; +} + /** * Returns a Japanese tooltip string explaining the relative evaluation label. * @@ -69,31 +99,34 @@ export function getRelativeEvaluationJapaneseLabel(diff: number): string { } /** - * Converts a grade difference to a 5-level relative evaluation label. - * - * | diff | label | - * | ------ | ----- | - * | ≤ −2 | `--` | - * | −1 | `-` | - * | 0 | `""` | - * | +1 | `+` | - * | ≥ +2 | `++` | + * Returns Tailwind text color classes for a diff value in the vote dropdown. + * Negative diff (easier) → sky, zero → gray, positive (harder) → orange. * * @param diff - The result of {@link calcGradeDiff}. - * @returns The display label string. */ -export function getRelativeEvaluationLabel(diff: number): string { - if (diff <= -2) { - return '--'; - } - if (diff === -1) { - return '-'; +export function getRelativeEvaluationColorClass(diff: number): string { + if (diff < 0) { + return 'text-sky-500 dark:text-sky-400'; } if (diff === 0) { - return ''; + return 'text-gray-400 dark:text-gray-500'; } - if (diff === 1) { - return '+'; + return 'text-orange-400 dark:text-orange-300'; +} + +/** + * Returns Tailwind background + text color classes for the relative evaluation badge. + * Negative diff (easier) → sky, positive (harder) → orange. + * Returns empty string for diff === 0 (badge is not shown). + * + * @param diff - The result of {@link calcGradeDiff}. + */ +export function getRelativeEvaluationBadgeColorClass(diff: number): string { + if (diff < 0) { + return 'bg-sky-400 text-white dark:bg-sky-500 dark:text-white'; } - return '++'; + if (diff > 0) { + return 'bg-orange-400 text-white dark:bg-orange-500 dark:text-white'; + } + return ''; } From ad4f082dbcd8d25dd79f3197540f477ee5aa35c3 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Thu, 23 Apr 2026 11:32:58 +0000 Subject: [PATCH 6/6] chore: fix format Co-Authored-By: Claude Sonnet 4.6 --- src/features/votes/components/VotableGrade.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/votes/components/VotableGrade.svelte b/src/features/votes/components/VotableGrade.svelte index 68a9b787c..457e02995 100644 --- a/src/features/votes/components/VotableGrade.svelte +++ b/src/features/votes/components/VotableGrade.svelte @@ -221,9 +221,9 @@ {@const diff = calcGradeDiff(taskResult.grade, grade)} {@const relLabel = getRelativeEvaluationJapaneseLabel(diff)} {#if relLabel} - {relLabel} + + {relLabel} + {/if} {/if} {#if votedGrade === grade}