feat(variety): implement C3 variety review screen (Issue #28)

- Add /planner/variety route with mobile stacked + desktop 2-column layout
- Implement VarietyScoreHero: Fraunces score display + progress bar + color-coded description
- Implement ScoreBreakdownList: 3 sub-score rows (protein diversity, ingredient overlap, effort balance)
- Implement VarietyWarningCards: yellow-tint warning cards derived from API tagRepeats/ingredientOverlaps
- Implement EffortBar: proportional colored segments (Easy/Medium/Hard) with ×N labels
- Desktop: protein grid (7 columns, repeat highlight with yellow ring) + effort bar in right panel
- Client-side sub-score derivation from VarietyScoreResponse (tagged for TODO to move to API)
- 26 new tests across 5 components + server load function; 455 tests total, 0 type errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 11:23:29 +02:00
parent 7c07bc443b
commit 8ad636f825
11 changed files with 727 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import EffortBar from './EffortBar.svelte';
describe('EffortBar', () => {
it('renders segment for easy effort', () => {
render(EffortBar, { props: { easy: 3, medium: 3, hard: 1 } });
expect(screen.getByTestId('effort-easy').textContent).toContain('3');
});
it('renders segment for medium effort', () => {
render(EffortBar, { props: { easy: 3, medium: 3, hard: 1 } });
expect(screen.getByTestId('effort-medium').textContent).toContain('3');
});
it('renders segment for hard effort', () => {
render(EffortBar, { props: { easy: 3, medium: 3, hard: 1 } });
expect(screen.getByTestId('effort-hard').textContent).toContain('1');
});
it('hides zero-count segments', () => {
render(EffortBar, { props: { easy: 7, medium: 0, hard: 0 } });
expect(screen.queryByTestId('effort-medium')).toBeNull();
expect(screen.queryByTestId('effort-hard')).toBeNull();
});
it('renders label with ×N count', () => {
render(EffortBar, { props: { easy: 3, medium: 3, hard: 1 } });
expect(screen.getByTestId('effort-easy').textContent).toContain('×3');
});
});