- 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
142 lines
4.7 KiB
TypeScript
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');
|
|
});
|
|
});
|