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)
113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { createCelebrationEngine } from './client.js';
|
|
import type { Celebration } from './types.js';
|
|
|
|
describe('createCelebrationEngine', () => {
|
|
it('should return default celebration for known triggers', () => {
|
|
const engine = createCelebrationEngine();
|
|
const c = engine.getCelebration('level_up');
|
|
expect(c.id).toBe('level_up');
|
|
expect(c.title).toContain('Level Up');
|
|
expect(c.confetti).toBe(true);
|
|
expect(c.sound).toBe('level_up');
|
|
});
|
|
|
|
it('should return fallback for unknown triggers', () => {
|
|
const engine = createCelebrationEngine();
|
|
const c = engine.getCelebration('custom_unknown');
|
|
expect(c.id).toBe('custom_unknown');
|
|
expect(c.title).toBe('Nice!');
|
|
});
|
|
|
|
it('should use custom triggers when provided', () => {
|
|
const custom: Celebration = {
|
|
id: 'fasting_complete',
|
|
title: 'Fast Complete!',
|
|
body: 'You did it!',
|
|
emoji: '🍃',
|
|
hapticType: 'heavy',
|
|
confetti: true,
|
|
sound: 'success',
|
|
};
|
|
const engine = createCelebrationEngine({ customTriggers: { fasting_complete: custom } });
|
|
const c = engine.getCelebration('fasting_complete');
|
|
expect(c).toEqual(custom);
|
|
});
|
|
|
|
it('should prefer custom triggers over defaults', () => {
|
|
const custom: Celebration = {
|
|
id: 'level_up',
|
|
title: 'Custom Level Up!',
|
|
body: 'Custom body',
|
|
emoji: '🎮',
|
|
hapticType: 'light',
|
|
confetti: false,
|
|
sound: 'none',
|
|
};
|
|
const engine = createCelebrationEngine({ customTriggers: { level_up: custom } });
|
|
const c = engine.getCelebration('level_up');
|
|
expect(c.title).toBe('Custom Level Up!');
|
|
});
|
|
|
|
it('should return timed celebrations based on progress', () => {
|
|
const engine = createCelebrationEngine();
|
|
const shown = new Set<string>();
|
|
|
|
const at25 = engine.getTimedCelebrations(250, 1000, shown);
|
|
expect(at25).toHaveLength(1);
|
|
expect(at25[0].id).toBe('timed_25');
|
|
shown.add('timed_25');
|
|
|
|
const at50 = engine.getTimedCelebrations(500, 1000, shown);
|
|
expect(at50).toHaveLength(1);
|
|
expect(at50[0].id).toBe('timed_50');
|
|
});
|
|
|
|
it('should not repeat shown timed celebrations', () => {
|
|
const engine = createCelebrationEngine();
|
|
const shown = new Set(['timed_25', 'timed_50']);
|
|
|
|
const results = engine.getTimedCelebrations(500, 1000, shown);
|
|
expect(results).toHaveLength(0);
|
|
});
|
|
|
|
it('should return empty for zero target', () => {
|
|
const engine = createCelebrationEngine();
|
|
expect(engine.getTimedCelebrations(100, 0, new Set())).toHaveLength(0);
|
|
});
|
|
|
|
it('should detect personal best', () => {
|
|
const engine = createCelebrationEngine();
|
|
expect(engine.isPersonalBest(10, 5)).toBe(true);
|
|
expect(engine.isPersonalBest(5, 10)).toBe(false);
|
|
expect(engine.isPersonalBest(5, 5)).toBe(false);
|
|
expect(engine.isPersonalBest(1, 0)).toBe(true);
|
|
});
|
|
|
|
it('should return positive messages', () => {
|
|
const engine = createCelebrationEngine();
|
|
const msg0 = engine.getPositiveMessage(0);
|
|
const msg100 = engine.getPositiveMessage(100);
|
|
expect(msg0.length).toBeGreaterThan(0);
|
|
expect(msg100.length).toBeGreaterThan(0);
|
|
expect(msg0).not.toBe(msg100);
|
|
});
|
|
|
|
it('should return positive incomplete messages', () => {
|
|
const engine = createCelebrationEngine();
|
|
const msg = engine.getPositiveIncompleteMessage(30);
|
|
expect(msg.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should clamp progress percent', () => {
|
|
const engine = createCelebrationEngine();
|
|
const msgNeg = engine.getPositiveMessage(-10);
|
|
const msg0 = engine.getPositiveMessage(0);
|
|
expect(msgNeg).toBe(msg0);
|
|
|
|
const msgOver = engine.getPositiveMessage(200);
|
|
const msg100 = engine.getPositiveMessage(100);
|
|
expect(msgOver).toBe(msg100);
|
|
});
|
|
});
|