import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import WegZurAuszeichnungSection from '@/spa/components/WegZurAuszeichnungSection' import { getActiveTimelineStep, getTimelineScrollBehavior, getTimelineStepScrollLeft, } from '@/spa/components/ui/process-timeline' vi.mock('@/spa/hooks/useIsMobile', () => ({ useIsMobile: () => false, })) let reduceMotion = false describe('participation timeline', () => { beforeEach(() => { reduceMotion = false Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn().mockImplementation((query: string) => ({ addEventListener: vi.fn(), matches: query === '(prefers-reduced-motion: reduce)' && reduceMotion, media: query, removeEventListener: vi.fn(), })), }) }) afterEach(() => cleanup()) it('maps the real horizontal scroll range to deterministic steps', () => { expect(getActiveTimelineStep(0, 600, 4)).toBe(0) expect(getActiveTimelineStep(200, 600, 4)).toBe(1) expect(getActiveTimelineStep(400, 600, 4)).toBe(2) expect(getActiveTimelineStep(600, 600, 4)).toBe(3) expect(getTimelineStepScrollLeft(2, 600, 4)).toBe(400) expect(getTimelineStepScrollLeft(99, 600, 4)).toBe(600) }) it('updates progress from native track scroll state', () => { render() const track = screen.getByTestId('timeline-track') Object.defineProperties(track, { clientWidth: { configurable: true, value: 400 }, scrollLeft: { configurable: true, value: 400, writable: true }, scrollWidth: { configurable: true, value: 1000 }, }) fireEvent.scroll(track) expect(screen.getByTestId('timeline-progress-label').textContent).toContain('03 / 04') expect(screen.getByTestId('timeline-progress').style.width).toBe('75%') expect(screen.getAllByTestId(/timeline-step-/)).toHaveLength(4) }) it('uses immediate scrolling in reduced-motion mode while keeping controls operable', () => { reduceMotion = true render() const track = screen.getByTestId('timeline-track') const scrollTo = vi.fn() Object.defineProperties(track, { clientWidth: { configurable: true, value: 400 }, scrollTo: { configurable: true, value: scrollTo }, scrollWidth: { configurable: true, value: 1000 }, }) fireEvent.click(screen.getByRole('button', { name: 'Nächster Schritt' })) expect(getTimelineScrollBehavior(true)).toBe('auto') expect(scrollTo).toHaveBeenCalledWith({ behavior: 'auto', left: 200 }) expect(screen.getByTestId('timeline-progress-label').textContent).toContain('02 / 04') }) it('supports direct keyboard navigation across the track', () => { render() const track = screen.getByTestId('timeline-track') const scrollTo = vi.fn() Object.defineProperties(track, { clientWidth: { configurable: true, value: 400 }, scrollTo: { configurable: true, value: scrollTo }, scrollWidth: { configurable: true, value: 1000 }, }) fireEvent.keyDown(track, { key: 'End' }) expect(scrollTo).toHaveBeenCalledWith({ behavior: 'smooth', left: 600 }) expect(screen.getByTestId('timeline-progress-label').textContent).toContain('04 / 04') }) })