calendar-timeline-unit.test.mjs
114 lines 3.6 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Tier 1–2 — UNIT/INTEGRATION: timeline merge and range parsing.
3 */
4 import { describe, it, beforeEach, afterEach } from 'node:test';
5 import assert from 'node:assert/strict';
6 import fs from 'node:fs';
7 import path from 'node:path';
8 import { fileURLToPath } from 'node:url';
9 import {
10 parseTimelineRange,
11 parseTimelineLayers,
12 buildCalendarTimeline,
13 noteCalendarDayKey,
14 } from '../lib/calendar/timeline.mjs';
15 import { importIcsIntoVault } from '../lib/calendar/event-store.mjs';
16 import { writeNote } from '../lib/write.mjs';
17
18 const __dirname = path.dirname(fileURLToPath(import.meta.url));
19 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-calendar-timeline');
20
21 describe('parseTimelineRange', () => {
22 it('accepts YYYY-MM-DD boundaries', () => {
23 const r = parseTimelineRange('2026-06-01', '2026-06-30');
24 assert.equal(r.fromDate, '2026-06-01');
25 assert.equal(r.toDate, '2026-06-30');
26 assert.equal(r.fromIso, '2026-06-01T00:00:00.000Z');
27 });
28
29 it('rejects inverted ranges', () => {
30 assert.throws(() => parseTimelineRange('2026-06-30', '2026-06-01'), /before/);
31 });
32 });
33
34 describe('parseTimelineLayers', () => {
35 it('defaults to notes and events', () => {
36 assert.deepEqual(parseTimelineLayers(undefined), ['notes', 'events']);
37 });
38
39 it('supports notes-only preset', () => {
40 assert.deepEqual(parseTimelineLayers('notes'), ['notes']);
41 });
42 });
43
44 describe('buildCalendarTimeline', () => {
45 const dataDir = path.join(tmpRoot, 'data');
46 const vaultPath = path.join(tmpRoot, 'vault');
47 const vaultId = 'default';
48
49 beforeEach(() => {
50 fs.rmSync(tmpRoot, { recursive: true, force: true });
51 fs.mkdirSync(dataDir, { recursive: true });
52 fs.mkdirSync(vaultPath, { recursive: true });
53 writeNote(vaultPath, 'notes/june-note.md', {
54 body: 'Biology reading',
55 frontmatter: { title: 'Bio', date: '2026-06-18' },
56 });
57 const ics = fs.readFileSync(path.join(__dirname, 'fixtures', 'calendar', 'simple-utc.ics'), 'utf8');
58 importIcsIntoVault(dataDir, vaultId, { icsText: ics, displayName: 'Work' });
59 });
60
61 afterEach(() => {
62 fs.rmSync(tmpRoot, { recursive: true, force: true });
63 });
64
65 it('merges note and event layers sorted by sort_at', () => {
66 const timeline = buildCalendarTimeline({
67 dataDir,
68 vaultId,
69 vaultPath,
70 from: '2026-06-01',
71 to: '2026-06-30',
72 layers: 'notes,events',
73 });
74 assert.equal(timeline.schema, 'knowtation.calendar_timeline/v0');
75 assert.equal(timeline.items.length, 2);
76 const kinds = timeline.items.map((i) => i.kind);
77 assert.deepEqual(kinds.sort(), ['event', 'note']);
78 });
79
80 it('notes-only layer excludes external events', () => {
81 const timeline = buildCalendarTimeline({
82 dataDir,
83 vaultId,
84 vaultPath,
85 from: '2026-06-01',
86 to: '2026-06-30',
87 layers: 'notes',
88 });
89 assert.ok(timeline.items.every((i) => i.kind === 'note'));
90 });
91
92 it('hides events when enabled_for_display is false', () => {
93 const storePath = path.join(dataDir, 'hub_calendar_store.json');
94 const store = JSON.parse(fs.readFileSync(storePath, 'utf8'));
95 store.vaults.default.source_calendars[0].enabled_for_display = false;
96 fs.writeFileSync(storePath, JSON.stringify(store));
97
98 const timeline = buildCalendarTimeline({
99 dataDir,
100 vaultId,
101 vaultPath,
102 from: '2026-06-01',
103 to: '2026-06-30',
104 layers: 'events',
105 });
106 assert.equal(timeline.items.length, 0);
107 });
108 });
109
110 describe('noteCalendarDayKey', () => {
111 it('uses frontmatter date when present', () => {
112 assert.equal(noteCalendarDayKey({ frontmatter: { date: '2026-06-18' } }), '2026-06-18');
113 });
114 });
File History 1 commit
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago