diff --git a/frontend/src/lib/document/TimelineBars.svelte.test.ts b/frontend/src/lib/document/TimelineBars.svelte.test.ts new file mode 100644 index 00000000..13faffc8 --- /dev/null +++ b/frontend/src/lib/document/TimelineBars.svelte.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; + +afterEach(cleanup); + +const baseProps = (overrides: Record = {}) => ({ + filled: [ + { month: '1923-01', count: 5 }, + { month: '1923-02', count: 1 }, + { month: '1923-03', count: 0 } + ], + maxCount: 5, + barAreaHeight: 100, + isSelected: () => false, + isInDragPreview: () => false, + isDragging: false, + dragWindowLeftPct: 0, + dragWindowRightPct: 0, + onbarpointerdown: () => {}, + onbarpointerenter: () => {}, + onbarclick: () => {}, + ...overrides +}); + +import TimelineBars from './TimelineBars.svelte'; + +describe('TimelineBars', () => { + it('renders one bar per filled bucket', async () => { + render(TimelineBars, { props: baseProps() }); + + const bars = document.querySelectorAll('[data-testid="timeline-bar"]'); + expect(bars.length).toBe(3); + }); + + it('uses the singular aria-label when count is 1', async () => { + render(TimelineBars, { props: baseProps() }); + + const bars = Array.from( + document.querySelectorAll('[data-testid="timeline-bar"]') + ) as HTMLButtonElement[]; + expect(bars[1].getAttribute('aria-label')).toContain('1 Dokument'); + }); + + it('uses the plural aria-label when count is greater than 1', async () => { + render(TimelineBars, { props: baseProps() }); + + const bars = Array.from( + document.querySelectorAll('[data-testid="timeline-bar"]') + ) as HTMLButtonElement[]; + expect(bars[0].getAttribute('aria-label')).toContain('5 Dokumente'); + }); + + it('marks the bar as aria-pressed when isSelected returns true', async () => { + render(TimelineBars, { + props: baseProps({ isSelected: (label: string) => label === '1923-01' }) + }); + + const bars = Array.from( + document.querySelectorAll('[data-testid="timeline-bar"]') + ) as HTMLButtonElement[]; + expect(bars[0].getAttribute('aria-pressed')).toBe('true'); + expect(bars[1].getAttribute('aria-pressed')).toBe('false'); + }); + + it('renders the drag window only when isDragging is true', async () => { + render(TimelineBars, { + props: baseProps({ isDragging: true, dragWindowLeftPct: 10, dragWindowRightPct: 30 }) + }); + + const dragWindow = document.querySelector('[data-testid="timeline-drag-window"]'); + expect(dragWindow).not.toBeNull(); + }); + + it('omits the drag window when isDragging is false', async () => { + render(TimelineBars, { props: baseProps() }); + + const dragWindow = document.querySelector('[data-testid="timeline-drag-window"]'); + expect(dragWindow).toBeNull(); + }); + + it('calls onbarclick with the bucket index when a bar is clicked', async () => { + const onbarclick = vi.fn(); + render(TimelineBars, { props: baseProps({ onbarclick }) }); + + const bars = Array.from( + document.querySelectorAll('[data-testid="timeline-bar"]') + ) as HTMLButtonElement[]; + bars[1].click(); + + expect(onbarclick).toHaveBeenCalledWith(1); + }); + + it('uses minimum bar height for zero-count buckets', async () => { + render(TimelineBars, { props: baseProps() }); + + const bars = Array.from( + document.querySelectorAll('[data-testid="timeline-bar"]') + ) as HTMLButtonElement[]; + const zeroBar = bars[2].querySelector('.bar-fill') as HTMLElement; + expect(zeroBar.style.height).toContain('2px'); + }); +});