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)
118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
getVisibleSections,
|
|
getAvailableActions,
|
|
pickSmartDefault,
|
|
MAX_VISIBLE_ITEMS,
|
|
MAX_VISIBLE_LIST,
|
|
} from './client.js';
|
|
import type { ProgressiveSection, QuickAction, SmartDefault } from './types.js';
|
|
|
|
describe('getVisibleSections', () => {
|
|
const sections: ProgressiveSection[] = [
|
|
{ id: 'main', title: 'Main', defaultExpanded: true, priority: 'primary' },
|
|
{ id: 'stats', title: 'Stats', defaultExpanded: false, priority: 'secondary' },
|
|
{ id: 'details', title: 'Details', defaultExpanded: false, priority: 'detail' },
|
|
{ id: 'advanced', title: 'Advanced', defaultExpanded: false, priority: 'primary' },
|
|
];
|
|
|
|
it('should show primary and defaultExpanded sections', () => {
|
|
const visible = getVisibleSections(sections, new Set());
|
|
expect(visible.map(s => s.id)).toEqual(['main', 'advanced']);
|
|
});
|
|
|
|
it('should include explicitly expanded sections', () => {
|
|
const visible = getVisibleSections(sections, new Set(['stats']));
|
|
expect(visible.map(s => s.id)).toEqual(['main', 'stats', 'advanced']);
|
|
});
|
|
|
|
it('should return empty for no sections', () => {
|
|
expect(getVisibleSections([], new Set())).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('getAvailableActions', () => {
|
|
const actions: QuickAction[] = [
|
|
{
|
|
id: 'start',
|
|
label: 'Start',
|
|
icon: 'play',
|
|
shortLabel: 'Start',
|
|
action: 'start',
|
|
requiresAuth: false,
|
|
},
|
|
{
|
|
id: 'share',
|
|
label: 'Share',
|
|
icon: 'share',
|
|
shortLabel: 'Share',
|
|
action: 'share',
|
|
requiresAuth: true,
|
|
},
|
|
{
|
|
id: 'stop',
|
|
label: 'Stop',
|
|
icon: 'stop',
|
|
shortLabel: 'Stop',
|
|
action: 'stop',
|
|
requiresAuth: false,
|
|
},
|
|
];
|
|
|
|
it('should filter out auth-required actions when not authenticated', () => {
|
|
const available = getAvailableActions(actions, { isAuthenticated: false });
|
|
expect(available.map(a => a.id)).toEqual(['start', 'stop']);
|
|
});
|
|
|
|
it('should include all when authenticated', () => {
|
|
const available = getAvailableActions(actions, { isAuthenticated: true });
|
|
expect(available).toHaveLength(3);
|
|
});
|
|
|
|
it('should include non-auth actions by default', () => {
|
|
const available = getAvailableActions(actions, {});
|
|
expect(available.map(a => a.id)).toEqual(['start', 'stop']);
|
|
});
|
|
});
|
|
|
|
describe('pickSmartDefault', () => {
|
|
it('should prefer last_used over others', () => {
|
|
const candidates: SmartDefault[] = [
|
|
{ key: 'protocol', value: 'OMAD', source: 'most_common' },
|
|
{ key: 'protocol', value: '16:8', source: 'last_used' },
|
|
{ key: 'protocol', value: '18:6', source: 'recommendation' },
|
|
];
|
|
const result = pickSmartDefault(candidates);
|
|
expect(result?.value).toBe('16:8');
|
|
});
|
|
|
|
it('should fall back to most_common', () => {
|
|
const candidates: SmartDefault[] = [
|
|
{ key: 'protocol', value: 'OMAD', source: 'most_common' },
|
|
{ key: 'protocol', value: '18:6', source: 'system' },
|
|
];
|
|
const result = pickSmartDefault(candidates);
|
|
expect(result?.value).toBe('OMAD');
|
|
});
|
|
|
|
it('should return null for empty array', () => {
|
|
expect(pickSmartDefault([])).toBeNull();
|
|
});
|
|
|
|
it('should return first if no priority match', () => {
|
|
const candidates: SmartDefault[] = [{ key: 'x', value: 'a', source: 'system' }];
|
|
const result = pickSmartDefault(candidates);
|
|
expect(result?.value).toBe('a');
|
|
});
|
|
});
|
|
|
|
describe('constants', () => {
|
|
it('should export MAX_VISIBLE_ITEMS', () => {
|
|
expect(MAX_VISIBLE_ITEMS).toBe(3);
|
|
});
|
|
|
|
it('should export MAX_VISIBLE_LIST', () => {
|
|
expect(MAX_VISIBLE_LIST).toBe(5);
|
|
});
|
|
});
|