learning_ai_common_plat/packages/accessibility/src/client.test.ts
saravanakumardb1 be03efa111 feat(shared-packages): add 9 @bytelyst/* client packages with 100% API coverage
Packages added:
- @bytelyst/referral-client — referral API client + share helpers
- @bytelyst/subscription-client — subscription/plan API client + cache
- @bytelyst/celebrations — milestone triggers, confetti, positive messages
- @bytelyst/gentle-notifications — ND-friendly messaging, forbidden phrases
- @bytelyst/accessibility — VoiceOver/TalkBack label generators
- @bytelyst/quick-actions — progressive disclosure, smart defaults
- @bytelyst/time-references — familiar duration references
- @bytelyst/org-client — org/workspace/membership/license API client
- @bytelyst/marketplace-client — listing/review/install API client

All packages: pure TS, ESM, globalThis.fetch, no Node.js deps.
99 Vitest tests across 9 packages, 79/79 public methods covered.

Review fixes applied:
- time-references: fix module-level mutable state leak + add clearCustomReferences()
- accessibility: fix parameter reassignment in formatDurationForA11y/numberToWords
- subscription-client: fix flaky daysRemaining test (ms boundary race)
2026-03-19 13:10:09 -07:00

155 lines
5.1 KiB
TypeScript

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);
});
});