diff --git a/frontend/src/lib/components/ProgressSidebar.svelte b/frontend/src/lib/components/ProgressSidebar.svelte new file mode 100644 index 0000000..102d065 --- /dev/null +++ b/frontend/src/lib/components/ProgressSidebar.svelte @@ -0,0 +1,71 @@ + + + + + + + 🥗 + + Mealplan + + + + + {#each steps as step (step.number)} + + + {step.number < currentStep ? '✓' : step.number} + + + {step.label} + {step.subtitle} + + + {/each} + + diff --git a/frontend/src/lib/components/ProgressSidebar.test.ts b/frontend/src/lib/components/ProgressSidebar.test.ts new file mode 100644 index 0000000..66348d0 --- /dev/null +++ b/frontend/src/lib/components/ProgressSidebar.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import ProgressSidebar from './ProgressSidebar.svelte'; + +describe('ProgressSidebar', () => { + it('renders the app logo and name', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + expect(screen.getByText('Mealplan')).toBeInTheDocument(); + }); + + it('renders all 3 step labels', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + expect(screen.getByText('Haushalt benennen')).toBeInTheDocument(); + expect(screen.getByText('Vorräte einrichten')).toBeInTheDocument(); + expect(screen.getByText('Mitglieder einladen')).toBeInTheDocument(); + }); + + it('step 1 active: renders green circle for step 1', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + const step1 = screen.getByTestId('step-1'); + expect(step1).toHaveAttribute('aria-current', 'step'); + }); + + it('step 1 active: steps 2 and 3 are not current', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + expect(screen.getByTestId('step-2')).not.toHaveAttribute('aria-current'); + expect(screen.getByTestId('step-3')).not.toHaveAttribute('aria-current'); + }); + + it('step 2 active: step 1 is completed (checkmark), step 2 is current, step 3 is future', () => { + render(ProgressSidebar, { props: { currentStep: 2 } }); + expect(screen.getByTestId('step-1')).toHaveAttribute('data-state', 'completed'); + expect(screen.getByTestId('step-2')).toHaveAttribute('aria-current', 'step'); + expect(screen.getByTestId('step-3')).not.toHaveAttribute('aria-current'); + }); + + it('step 1 completed has accessible label', () => { + render(ProgressSidebar, { props: { currentStep: 2 } }); + const step1 = screen.getByTestId('step-1'); + expect(step1).toHaveAttribute('data-state', 'completed'); + expect(screen.getByLabelText(/schritt 1/i)).toBeInTheDocument(); + }); + + it('each step has an accessible aria-label', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + expect(screen.getByLabelText(/schritt 1/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/schritt 2/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/schritt 3/i)).toBeInTheDocument(); + }); + + it('future steps do not have aria-current', () => { + render(ProgressSidebar, { props: { currentStep: 1 } }); + expect(screen.getByTestId('step-2')).not.toHaveAttribute('aria-current'); + expect(screen.getByTestId('step-3')).not.toHaveAttribute('aria-current'); + }); +});