Skip to content

Commit c670e37

Browse files
committed
test: Add skipped tests documenting Issue #1108 ICS export duration feature
Document the feature request to use task timeEstimate (duration) for calculating ICS event end time instead of using due date. Tests document current behavior and expected new behavior when feature is implemented.
1 parent 6bdc3d6 commit c670e37

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import { CalendarExportService } from '../../../src/services/CalendarExportService';
2+
import { TaskInfo } from '../../../src/types';
3+
4+
// Mock Obsidian's dependencies
5+
jest.mock('obsidian', () => ({
6+
Notice: jest.fn()
7+
}));
8+
9+
describe('Issue #1108 - ICS Export should use task duration instead of due date', () => {
10+
/**
11+
* This test documents the feature request from Issue #1108.
12+
*
13+
* Current behavior: When exporting to ICS, the service uses:
14+
* - scheduled date → DTSTART
15+
* - due date → DTEND (if present)
16+
* - fallback: scheduled + 1 hour → DTEND
17+
*
18+
* Requested behavior: Option to use:
19+
* - scheduled date → DTSTART
20+
* - scheduled + timeEstimate (duration) → DTEND
21+
*
22+
* This aligns with GTD workflow where:
23+
* - scheduled + duration = when you plan to work on the task
24+
* - due date = deadline (separate from work planning)
25+
*/
26+
27+
describe('Feature: Use timeEstimate for event duration', () => {
28+
it.skip('should use timeEstimate to calculate DTEND when option is enabled', () => {
29+
// Task scheduled for Tuesday at 10:00 with 2 hour duration
30+
// Expected: Calendar event from 10:00 to 12:00
31+
const task: TaskInfo = {
32+
title: 'Plan meeting agenda',
33+
path: 'tasks/plan-meeting.md',
34+
scheduled: '2025-01-14T10:00:00',
35+
timeEstimate: 120, // 120 minutes = 2 hours
36+
due: '2025-01-20T17:00:00', // Due date should be IGNORED when using duration
37+
status: 'todo',
38+
priority: 'medium',
39+
tags: [],
40+
projects: [],
41+
contexts: []
42+
};
43+
44+
// TODO: This method signature would need to accept an options object
45+
// with a flag like { useDurationForEnd: true }
46+
const icsContent = CalendarExportService.generateICSContent(task);
47+
48+
// Parse the ICS content
49+
const lines = icsContent.split('\r\n');
50+
const dtstart = lines.find(l => l.startsWith('DTSTART:'));
51+
const dtend = lines.find(l => l.startsWith('DTEND:'));
52+
53+
expect(dtstart).toBeDefined();
54+
expect(dtend).toBeDefined();
55+
56+
// Extract times
57+
const startTime = dtstart!.replace('DTSTART:', '');
58+
const endTime = dtend!.replace('DTEND:', '');
59+
60+
// Start should be 10:00 UTC (2025-01-14T10:00:00Z)
61+
expect(startTime).toContain('20250114T');
62+
63+
// End should be 12:00 UTC (start + 2 hours), NOT the due date
64+
// Currently fails because it uses due date as end time
65+
expect(endTime).toContain('20250114T'); // Same day as start
66+
// The end time should be 2 hours after start, not the due date
67+
68+
// Parse and compare times properly
69+
const parseICSDate = (ics: string): Date => {
70+
// YYYYMMDDTHHMMSSZ -> Date
71+
const year = parseInt(ics.substr(0, 4));
72+
const month = parseInt(ics.substr(4, 2)) - 1;
73+
const day = parseInt(ics.substr(6, 2));
74+
const hour = parseInt(ics.substr(9, 2));
75+
const minute = parseInt(ics.substr(11, 2));
76+
const second = parseInt(ics.substr(13, 2));
77+
return new Date(Date.UTC(year, month, day, hour, minute, second));
78+
};
79+
80+
const startDate = parseICSDate(startTime);
81+
const endDate = parseICSDate(endTime);
82+
const durationMinutes = (endDate.getTime() - startDate.getTime()) / (1000 * 60);
83+
84+
// Duration should be 120 minutes (2 hours)
85+
expect(durationMinutes).toBe(120);
86+
});
87+
88+
it.skip('should fall back to 1 hour when timeEstimate is not set and option is enabled', () => {
89+
const task: TaskInfo = {
90+
title: 'Quick task',
91+
path: 'tasks/quick-task.md',
92+
scheduled: '2025-01-14T14:00:00',
93+
// No timeEstimate, no due date
94+
status: 'todo',
95+
tags: [],
96+
projects: [],
97+
contexts: []
98+
};
99+
100+
const icsContent = CalendarExportService.generateICSContent(task);
101+
102+
const lines = icsContent.split('\r\n');
103+
const dtstart = lines.find(l => l.startsWith('DTSTART:'));
104+
const dtend = lines.find(l => l.startsWith('DTEND:'));
105+
106+
expect(dtstart).toBeDefined();
107+
expect(dtend).toBeDefined();
108+
109+
// With no timeEstimate, should fall back to 1 hour duration
110+
// This is existing behavior and should be preserved
111+
});
112+
113+
it.skip('should ignore due date when useDurationForEnd option is true', () => {
114+
// This is the key behavior change requested
115+
const task: TaskInfo = {
116+
title: 'Important task with deadline',
117+
path: 'tasks/important.md',
118+
scheduled: '2025-01-14T09:00:00',
119+
timeEstimate: 60, // 1 hour
120+
due: '2025-01-31T23:59:00', // Deadline far in future - should NOT be used as DTEND
121+
status: 'todo',
122+
tags: [],
123+
projects: [],
124+
contexts: []
125+
};
126+
127+
// TODO: Need to pass option to use duration instead of due date
128+
const icsContent = CalendarExportService.generateICSContent(task);
129+
130+
const lines = icsContent.split('\r\n');
131+
const dtend = lines.find(l => l.startsWith('DTEND:'));
132+
133+
expect(dtend).toBeDefined();
134+
135+
// DTEND should be 2025-01-14T10:00:00Z (start + 1 hour)
136+
// NOT 2025-01-31T23:59:00Z (due date)
137+
expect(dtend).toContain('20250114T');
138+
expect(dtend).not.toContain('20250131');
139+
});
140+
});
141+
142+
describe('Feature: Separate export for due dates', () => {
143+
it.skip('should support exporting due dates to a separate file (deadline.ics)', () => {
144+
// User suggests due dates could be exported separately as deadlines
145+
// This would allow calendars to show both:
146+
// 1. Work blocks (scheduled + duration)
147+
// 2. Deadlines (due dates as separate events or all-day reminders)
148+
149+
const tasks: TaskInfo[] = [
150+
{
151+
title: 'Task with both scheduled and due',
152+
path: 'tasks/task1.md',
153+
scheduled: '2025-01-14T10:00:00',
154+
timeEstimate: 120,
155+
due: '2025-01-20T17:00:00',
156+
status: 'todo',
157+
tags: [],
158+
projects: [],
159+
contexts: []
160+
}
161+
];
162+
163+
// TODO: New method to generate deadline-only ICS
164+
// const deadlineIcs = CalendarExportService.generateDeadlinesICSContent(tasks);
165+
166+
// The deadline ICS should contain events for due dates only
167+
// Perhaps as all-day events or as point-in-time reminders
168+
});
169+
});
170+
171+
describe('Settings integration', () => {
172+
it.skip('should respect ICSIntegrationSettings.useDurationForExport setting', () => {
173+
// TODO: ICSIntegrationSettings should have a new option:
174+
// useDurationForExport: boolean
175+
//
176+
// When true:
177+
// - DTSTART = scheduled date/time
178+
// - DTEND = scheduled + timeEstimate
179+
// - Due date is NOT used for DTEND
180+
//
181+
// When false (default for backwards compatibility):
182+
// - DTSTART = scheduled date/time
183+
// - DTEND = due date (if exists) or scheduled + 1 hour
184+
});
185+
});
186+
187+
describe('Current behavior (before fix)', () => {
188+
it('currently uses due date as DTEND when both scheduled and due are present', () => {
189+
const task: TaskInfo = {
190+
title: 'Test current behavior',
191+
path: 'tasks/test.md',
192+
scheduled: '2025-01-14T10:00:00',
193+
timeEstimate: 120, // 2 hours - currently IGNORED
194+
due: '2025-01-20T17:00:00',
195+
status: 'todo',
196+
tags: [],
197+
projects: [],
198+
contexts: []
199+
};
200+
201+
const icsContent = CalendarExportService.generateICSContent(task);
202+
const lines = icsContent.split('\r\n');
203+
204+
const dtstart = lines.find(l => l.startsWith('DTSTART:'));
205+
const dtend = lines.find(l => l.startsWith('DTEND:'));
206+
207+
expect(dtstart).toBeDefined();
208+
expect(dtend).toBeDefined();
209+
210+
// Currently: DTEND uses due date (this is the behavior we want to optionally change)
211+
expect(dtend).toContain('20250120'); // Uses due date
212+
});
213+
214+
it('currently adds timeEstimate to description but does not use it for event duration', () => {
215+
const task: TaskInfo = {
216+
title: 'Task with duration',
217+
path: 'tasks/test.md',
218+
scheduled: '2025-01-14T10:00:00',
219+
timeEstimate: 90, // 90 minutes
220+
status: 'todo',
221+
tags: [],
222+
projects: [],
223+
contexts: []
224+
};
225+
226+
const icsContent = CalendarExportService.generateICSContent(task);
227+
228+
// timeEstimate IS included in description
229+
expect(icsContent).toContain('90 minutes');
230+
231+
// But NOT used for calculating DTEND
232+
const lines = icsContent.split('\r\n');
233+
const dtstart = lines.find(l => l.startsWith('DTSTART:'));
234+
const dtend = lines.find(l => l.startsWith('DTEND:'));
235+
236+
expect(dtstart).toBeDefined();
237+
expect(dtend).toBeDefined();
238+
239+
// Current behavior: falls back to 1 hour, not 90 minutes
240+
const parseICSDate = (ics: string): Date => {
241+
const year = parseInt(ics.substr(0, 4));
242+
const month = parseInt(ics.substr(4, 2)) - 1;
243+
const day = parseInt(ics.substr(6, 2));
244+
const hour = parseInt(ics.substr(9, 2));
245+
const minute = parseInt(ics.substr(11, 2));
246+
const second = parseInt(ics.substr(13, 2));
247+
return new Date(Date.UTC(year, month, day, hour, minute, second));
248+
};
249+
250+
const startTime = dtstart!.replace('DTSTART:', '');
251+
const endTime = dtend!.replace('DTEND:', '');
252+
const startDate = parseICSDate(startTime);
253+
const endDate = parseICSDate(endTime);
254+
const durationMinutes = (endDate.getTime() - startDate.getTime()) / (1000 * 60);
255+
256+
// Currently defaults to 60 minutes (1 hour) when no due date, ignoring timeEstimate
257+
expect(durationMinutes).toBe(60);
258+
});
259+
});
260+
});

0 commit comments

Comments
 (0)