import { describe, it, expect } from 'vitest'; import { buttonLabel, timerLabel, progressLabel, sliderLabel, alertLabel, achievementLabel, streakLabel, listItemLabel, formatDurationForA11y, formatNumberForA11y, buildAnnouncement, getPositiveBreakMessage, } from './client.js'; describe('accessibility label generators', () => { it('buttonLabel returns correct props', () => { const props = buttonLabel('Start Timer', 'Begins a new timer'); expect(props.accessible).toBe(true); expect(props.accessibilityLabel).toBe('Start Timer'); expect(props.accessibilityHint).toBe('Begins a new timer'); expect(props.accessibilityRole).toBe('button'); }); it('timerLabel with context', () => { const props = timerLabel('running', '2 hours 30 minutes', 'Intermittent fasting'); expect(props.accessibilityLabel).toContain('running'); expect(props.accessibilityLabel).toContain('2 hours 30 minutes'); expect(props.accessibilityLabel).toContain('Intermittent fasting'); expect(props.accessibilityRole).toBe('timer'); }); it('timerLabel without context', () => { const props = timerLabel('paused', '10 minutes'); expect(props.accessibilityLabel).toBe('Timer paused, 10 minutes'); }); it('progressLabel with description', () => { const props = progressLabel('Fasting', 75, 'On track'); expect(props.accessibilityLabel).toContain('75 percent'); expect(props.accessibilityLabel).toContain('On track'); expect(props.accessibilityRole).toBe('progressbar'); expect(props.accessibilityValue?.now).toBe(75); }); it('progressLabel clamps to 0-100', () => { const neg = progressLabel('Test', -10); expect(neg.accessibilityValue?.now).toBe(0); const over = progressLabel('Test', 150); expect(over.accessibilityValue?.now).toBe(100); }); it('sliderLabel with max', () => { const props = sliderLabel('Volume', 7, 10); expect(props.accessibilityLabel).toBe('Volume: 7 of 10'); expect(props.accessibilityRole).toBe('adjustable'); }); it('sliderLabel without max', () => { const props = sliderLabel('Score', 42); expect(props.accessibilityLabel).toBe('Score: 42'); }); it('alertLabel', () => { const props = alertLabel('Warning', 'High heart rate detected'); expect(props.accessibilityLabel).toBe('Warning alert: High heart rate detected'); expect(props.accessibilityRole).toBe('alert'); }); it('achievementLabel earned', () => { const props = achievementLabel('Early Bird', 'Complete 5 morning sessions', true); expect(props.accessibilityLabel).toContain('Earned'); expect(props.accessibilityLabel).toContain('Early Bird'); expect(props.accessibilityState?.selected).toBe(true); }); it('achievementLabel locked', () => { const props = achievementLabel('Night Owl', 'Complete 5 late sessions', false); expect(props.accessibilityLabel).toContain('Locked'); expect(props.accessibilityState?.selected).toBe(false); }); it('streakLabel', () => { const props = streakLabel(7, 14); expect(props.accessibilityLabel).toContain('7 days'); expect(props.accessibilityLabel).toContain('14 days'); }); it('listItemLabel with badge', () => { const props = listItemLabel('16:8 Protocol', '16 hours fasting', 'Popular'); expect(props.accessibilityLabel).toBe('16:8 Protocol, 16 hours fasting, Popular'); }); it('listItemLabel minimal', () => { const props = listItemLabel('Simple Item'); expect(props.accessibilityLabel).toBe('Simple Item'); }); }); describe('formatDurationForA11y', () => { it('formats hours and minutes', () => { const result = formatDurationForA11y(16.5 * 60 * 60 * 1000); expect(result).toBe('16 hours 30 minutes'); }); it('formats zero', () => { expect(formatDurationForA11y(0)).toBe('0 seconds'); }); it('formats singular units', () => { const result = formatDurationForA11y(3661000); // 1h 1m 1s expect(result).toBe('1 hour 1 minute 1 second'); }); it('handles negative as zero', () => { expect(formatDurationForA11y(-1000)).toBe('0 seconds'); }); }); describe('formatNumberForA11y', () => { it('formats small numbers', () => { expect(formatNumberForA11y(5)).toBe('five'); expect(formatNumberForA11y(0)).toBe('zero'); expect(formatNumberForA11y(13)).toBe('thirteen'); }); it('formats larger numbers', () => { expect(formatNumberForA11y(42)).toBe('forty two'); expect(formatNumberForA11y(100)).toBe('one hundred'); expect(formatNumberForA11y(1234)).toBe('one thousand two hundred thirty four'); }); }); describe('buildAnnouncement', () => { it('combines headline and detail', () => { const result = buildAnnouncement('Fast Complete', 'You fasted for 16 hours'); expect(result).toBe('Fast Complete. You fasted for 16 hours'); }); }); describe('getPositiveBreakMessage', () => { it('returns a positive message', () => { const msg = getPositiveBreakMessage(50); expect(msg.length).toBeGreaterThan(0); }); it('clamps to valid range', () => { const msgNeg = getPositiveBreakMessage(-10); const msg0 = getPositiveBreakMessage(0); expect(msgNeg).toBe(msg0); }); });