diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 11:32:41 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 11:32:41 +0200 |
| commit | 18a0f9c874aaa09c0f71252dad853c23f9511ed6 (patch) | |
| tree | 93103b83f6d0057c40f0bab9c9473bf04292e9c4 /src/lib/server/services/routines.test.ts | |
| parent | 113415ce357a9cea1759d0452ebae6352f8fe36b (diff) | |
Add projects with routines, urgency tracking and health scores
Projects group recurring routines (chores included) with an append-only
snapshot table (id, version) so edits never rewrite history; completions
reference the exact snapshot. Pure metrics compute due dates, urgency
buckets (ok/due/late/veryLate) and 0-100 health scores per routine,
project, and globally. Home page lists projects; project dashboard sorts
routines most-urgent first with one-tap completion and interval parser
inputs (1d/2w/3m/1y).
Diffstat (limited to 'src/lib/server/services/routines.test.ts')
| -rw-r--r-- | src/lib/server/services/routines.test.ts | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/src/lib/server/services/routines.test.ts b/src/lib/server/services/routines.test.ts new file mode 100644 index 0000000..9e1f429 --- /dev/null +++ b/src/lib/server/services/routines.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { createTestDb } from '$lib/server/db/test-helper'; +import { add as addProject } from './projects'; +import { + add, + edit, + archive, + complete, + loadAllVersions, + loadProjectDashboard, + loadOverview, +} from './routines'; + +const DAY_MS = 24 * 60 * 60 * 1000; + +describe('routines service', () => { + let db: Awaited<ReturnType<typeof createTestDb>>['db']; + let projectId: number; + + beforeEach(async () => { + const testDb = await createTestDb(); + db = testDb.db; + const project = await addProject('Household', db); + projectId = project.id; + }); + + it('creates a routine with version 1', async () => { + const r = await add(projectId, 'clean kitchen', 7, db); + expect(r.id).toBe(1); + expect(r.version).toBe(1); + expect(r.frequencyDays).toBe(7); + }); + + it('edit inserts a new snapshot version and keeps history', async () => { + const r = await add(projectId, 'clean kitchen', 7, db); + const updated = await edit(r.id, 'clean kitchen deeply', 3, db); + expect(updated.version).toBe(2); + expect(updated.frequencyDays).toBe(3); + + const versions = await loadAllVersions(r.id, db); + expect(versions).toHaveLength(2); + expect(versions[0].frequencyDays).toBe(7); + expect(versions[1].frequencyDays).toBe(3); + }); + + it('archive inserts an archived snapshot without removing history', async () => { + const r = await add(projectId, 'clean kitchen', 7, db); + await archive(r.id, db); + const versions = await loadAllVersions(r.id, db); + expect(versions).toHaveLength(2); + expect(versions[0].archived).toBe(false); + expect(versions[1].archived).toBe(true); + + const dashboard = await loadProjectDashboard(projectId, Date.now(), db); + expect(dashboard.routines).toHaveLength(0); + expect(dashboard.healthScore).toBe(100); + }); + + it('complete records the snapshot version it was made against', async () => { + const r = await add(projectId, 'clean kitchen', 7, db); + await edit(r.id, 'clean kitchen', 5, db); + const doneAt = new Date().toISOString(); + const completion = await complete(r.id, 'with vinegar', doneAt, db); + expect(completion.routineVersion).toBe(2); + expect(completion.note).toBe('with vinegar'); + }); + + it('dashboard computes urgency and health from completions', async () => { + const r = await add(projectId, 'clean kitchen', 7, db); + const createdAt = Date.parse(r.createdAt); + await complete(r.id, null, new Date(createdAt).toISOString(), db); + + // 15 days after completion with a 7-day routine => very late + const now = createdAt + 15 * DAY_MS; + const dashboard = await loadProjectDashboard(projectId, now, db); + expect(dashboard.routines).toHaveLength(1); + expect(dashboard.routines[0].status).toBe('veryLate'); + expect(dashboard.healthScore).toBe(0); + }); + + it('overview aggregates per-project health and a global score', async () => { + await add(projectId, 'clean kitchen', 7, db); + const growth = await addProject('Personal growth', db); + await add(growth.id, 'read 30 minutes', 1, db); + + const now = Date.now(); + const overview = await loadOverview(now, db); + expect(overview.projects).toHaveLength(2); + expect(overview.globalScore).toBe( + Math.round(overview.projects.reduce((s, p) => s + p.healthScore, 0) / 2), + ); + }); +}); |
