learning_ai_clock/web/src/lib/prep-time.test.ts
saravanakumardb1 48a4b7d024 feat(web): prep-time intelligence, adaptive snooze, event countdown, TimerCard badges
- Prep time intelligence (lib/prep-time.ts): 12 keyword rules + 6 category defaults, prep/travel time suggestions, warning formatter, shouldShowPrepWarning() (22 tests)
- Adaptive snooze learning (lib/adaptive-snooze.ts): snooze pattern tracking, label normalization, suggestion engine with 5+ data point threshold, localStorage persistence (22 tests)
- Event countdown timer: createEvent factory with milestone warnings (30/7/3/1 days), addEvent store action, Event tab in CreateTimerModal with date picker
- TimerCard: category badge, chain link badge, prep time warning integration
- Analytics: added 'event' to trackTimerCreated type union
- Updated roadmap: marked prep time, adaptive snooze, event countdown, export/import, history as completed
- Phase 2 exit criteria: 6/10 met, 373 tests across 16 files, tsc clean
2026-02-27 22:25:36 -08:00

142 lines
4.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
getSuggestedPrepTime,
getTotalPrepMinutes,
getPrepStartTime,
formatPrepWarning,
shouldShowPrepWarning,
getPrepKeywords,
} from './prep-time';
describe('getSuggestedPrepTime', () => {
it('returns keyword-based config for "meeting"', () => {
const config = getSuggestedPrepTime('Team Meeting');
expect(config.prepMinutes).toBe(10);
expect(config.message).toContain('agenda');
});
it('returns keyword-based config for "flight"', () => {
const config = getSuggestedPrepTime('Flight to NYC');
expect(config.prepMinutes).toBe(60);
expect(config.travelMinutes).toBe(60);
});
it('returns keyword-based config for "dentist"', () => {
const config = getSuggestedPrepTime('Dentist appointment');
expect(config.prepMinutes).toBe(15);
expect(config.travelMinutes).toBe(20);
});
it('returns keyword-based config for "interview"', () => {
const config = getSuggestedPrepTime('Job Interview');
expect(config.prepMinutes).toBe(30);
});
it('returns keyword-based config for "gym"', () => {
const config = getSuggestedPrepTime('Go to gym');
expect(config.prepMinutes).toBe(10);
expect(config.travelMinutes).toBe(10);
});
it('returns keyword-based config for "exam"', () => {
const config = getSuggestedPrepTime('Math Exam');
expect(config.prepMinutes).toBe(30);
});
it('falls back to category default when no keyword match', () => {
const config = getSuggestedPrepTime('Random task', 'cooking');
expect(config.prepMinutes).toBe(15);
expect(config.message).toContain('ingredients');
});
it('returns generic default when no keyword or category match', () => {
const config = getSuggestedPrepTime('Something');
expect(config.prepMinutes).toBe(5);
expect(config.travelMinutes).toBe(0);
});
it('keyword takes priority over category', () => {
const config = getSuggestedPrepTime('Flight to LA', 'personal');
expect(config.prepMinutes).toBe(60); // keyword "flight", not category "personal" (5)
});
it('handles case-insensitive matching', () => {
const config = getSuggestedPrepTime('PRESENTATION at work');
expect(config.prepMinutes).toBe(20);
});
});
describe('getTotalPrepMinutes', () => {
it('sums prep and travel', () => {
expect(getTotalPrepMinutes({ prepMinutes: 10, travelMinutes: 20, message: '' })).toBe(30);
});
it('handles zero travel', () => {
expect(getTotalPrepMinutes({ prepMinutes: 15, travelMinutes: 0, message: '' })).toBe(15);
});
});
describe('getPrepStartTime', () => {
it('calculates correct start time', () => {
const target = 1000 * 60 * 60; // 1 hour in ms
const config = { prepMinutes: 10, travelMinutes: 5, message: '' };
const start = getPrepStartTime(target, config);
expect(start).toBe(target - 15 * 60_000);
});
});
describe('formatPrepWarning', () => {
const config = { prepMinutes: 10, travelMinutes: 5, message: 'Review your agenda' };
it('returns "Time\'s up" when no time left', () => {
expect(formatPrepWarning(config, 0)).toContain("Time's up");
});
it('returns "Start now" when within total prep window', () => {
expect(formatPrepWarning(config, 10)).toContain('Start now');
});
it('returns "Prepare soon" when close to prep start', () => {
expect(formatPrepWarning(config, 18)).toContain('Prepare soon');
});
it('returns prep start countdown when far away', () => {
expect(formatPrepWarning(config, 60)).toContain('prep starts in 45m');
});
});
describe('shouldShowPrepWarning', () => {
const config = { prepMinutes: 10, travelMinutes: 5, message: '' };
const target = Date.now() + 60 * 60_000; // 1 hour from now
it('returns false when too early', () => {
const earlyNow = target - 90 * 60_000; // 90 min before
expect(shouldShowPrepWarning(target, config, earlyNow)).toBe(false);
});
it('returns true within prep window', () => {
const inPrepWindow = target - 10 * 60_000; // 10 min before (within 15m window)
expect(shouldShowPrepWarning(target, config, inPrepWindow)).toBe(true);
});
it('returns false after target time', () => {
const afterTarget = target + 60_000;
expect(shouldShowPrepWarning(target, config, afterTarget)).toBe(false);
});
it('returns true exactly at prep start', () => {
const prepStart = target - 15 * 60_000;
expect(shouldShowPrepWarning(target, config, prepStart)).toBe(true);
});
});
describe('getPrepKeywords', () => {
it('returns an array of keyword strings', () => {
const keywords = getPrepKeywords();
expect(keywords.length).toBeGreaterThan(10);
expect(keywords).toContain('meeting');
expect(keywords).toContain('flight');
expect(keywords).toContain('dentist');
});
});