1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
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,
{ name: 'clean kitchen deeply', frequencyDays: 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('new routines start with an empty description', async () => {
const r = await add(projectId, 'clean kitchen', 7, db);
expect(r.description).toBe('');
});
it('edit is partial: name/frequency edits keep the description and vice-versa', async () => {
const r = await add(projectId, 'clean kitchen', 7, db);
const described = await edit(r.id, { description: 'wax on, wax off' }, db);
expect(described.name).toBe('clean kitchen');
expect(described.frequencyDays).toBe(7);
expect(described.description).toBe('wax on, wax off');
const renamed = await edit(r.id, { name: 'clean the whole kitchen' }, db);
expect(renamed.version).toBe(3);
expect(renamed.description).toBe('wax on, wax off');
expect(renamed.frequencyDays).toBe(7);
const refreq = await edit(r.id, { frequencyDays: 14 }, db);
expect(refreq.version).toBe(4);
expect(refreq.name).toBe('clean the whole kitchen');
expect(refreq.description).toBe('wax on, wax off');
const cleared = await edit(r.id, { description: '' }, db);
expect(cleared.version).toBe(5);
expect(cleared.description).toBe('');
expect(cleared.name).toBe('clean the whole kitchen');
});
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, { frequencyDays: 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),
);
});
});
|