Skip to content

Commit 8d47d46

Browse files
committed
feat: support multi line capture
1 parent b15d457 commit 8d47d46

4 files changed

Lines changed: 559 additions & 65 deletions

File tree

src/__tests__/QuickCaptureModal.integration.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,153 @@ describe("QuickCaptureModal Time Parsing Integration", () => {
189189
});
190190
});
191191

192+
describe("Multiline Processing Integration", () => {
193+
test("should preserve line structure in multiline content", () => {
194+
const content = "Task 1 tomorrow\nTask 2 next week\nTask 3 no date";
195+
const result = modal.processContentWithMetadata(content);
196+
197+
// Should split into separate lines
198+
const lines = result.split("\n");
199+
expect(lines).toHaveLength(3);
200+
201+
// Each line should be a task
202+
lines.forEach((line) => {
203+
expect(line).toMatch(/^- \[ \]/);
204+
});
205+
});
206+
207+
test("should handle different dates per line", () => {
208+
const content = "Task 1 tomorrow\nTask 2 next week\nTask 3";
209+
const result = modal.processContentWithMetadata(content);
210+
211+
const lines = result.split("\n");
212+
expect(lines).toHaveLength(3);
213+
214+
// First line should have a date
215+
expect(lines[0]).toContain("📅");
216+
expect(lines[0]).not.toContain("tomorrow");
217+
218+
// Second line should have a different date
219+
expect(lines[1]).toContain("📅");
220+
expect(lines[1]).not.toContain("next week");
221+
222+
// Third line should have no date
223+
expect(lines[2]).not.toContain("📅");
224+
expect(lines[2]).toContain("Task 3");
225+
});
226+
227+
test("should handle mixed Chinese and English time expressions", () => {
228+
const content = "任务1 明天\nTask 2 tomorrow\n任务3";
229+
const result = modal.processContentWithMetadata(content);
230+
231+
const lines = result.split("\n");
232+
expect(lines).toHaveLength(3);
233+
234+
// First line (Chinese)
235+
expect(lines[0]).toContain("📅");
236+
expect(lines[0]).not.toContain("明天");
237+
expect(lines[0]).toContain("任务1");
238+
239+
// Second line (English)
240+
expect(lines[1]).toContain("📅");
241+
expect(lines[1]).not.toContain("tomorrow");
242+
expect(lines[1]).toContain("Task 2");
243+
244+
// Third line (no date)
245+
expect(lines[2]).not.toContain("📅");
246+
expect(lines[2]).toContain("任务3");
247+
});
248+
249+
test("should handle existing task format with different dates", () => {
250+
const content =
251+
"- [ ] Task 1 tomorrow\n- [x] Task 2 next week\n- Task 3";
252+
const result = modal.processContentWithMetadata(content);
253+
254+
const lines = result.split("\n");
255+
expect(lines).toHaveLength(3);
256+
257+
// First line should preserve checkbox and add date
258+
expect(lines[0]).toMatch(/^- \[ \]/);
259+
expect(lines[0]).toContain("📅");
260+
expect(lines[0]).not.toContain("tomorrow");
261+
262+
// Second line should preserve completed status and add date
263+
expect(lines[1]).toMatch(/^- \[x\]/);
264+
expect(lines[1]).toContain("📅");
265+
expect(lines[1]).not.toContain("next week");
266+
267+
// Third line should be converted to task format
268+
expect(lines[2]).toMatch(/^- \[ \]/);
269+
expect(lines[2]).not.toContain("📅");
270+
});
271+
272+
test("should handle indented subtasks correctly", () => {
273+
const content =
274+
"Main task tomorrow\n Subtask 1 next week\n Subtask 2";
275+
const result = modal.processContentWithMetadata(content);
276+
277+
const lines = result.split("\n");
278+
expect(lines).toHaveLength(3);
279+
280+
// Main task should have date
281+
expect(lines[0]).toContain("📅");
282+
expect(lines[0]).not.toContain("tomorrow");
283+
284+
// Subtasks should preserve indentation but still clean time expressions
285+
expect(lines[1]).toMatch(/^\s+/); // Should start with whitespace
286+
expect(lines[1]).not.toContain("next week");
287+
288+
expect(lines[2]).toMatch(/^\s+/); // Should start with whitespace
289+
expect(lines[2]).toContain("Subtask 2");
290+
});
291+
292+
test("should handle empty lines in multiline content", () => {
293+
const content = "Task 1 tomorrow\n\nTask 2 next week\n\n";
294+
const result = modal.processContentWithMetadata(content);
295+
296+
const lines = result.split("\n");
297+
expect(lines).toHaveLength(5);
298+
299+
// First line should be a task with date
300+
expect(lines[0]).toMatch(/^- \[ \]/);
301+
expect(lines[0]).toContain("📅");
302+
303+
// Second line should be empty
304+
expect(lines[1]).toBe("");
305+
306+
// Third line should be a task with date
307+
expect(lines[2]).toMatch(/^- \[ \]/);
308+
expect(lines[2]).toContain("📅");
309+
310+
// Fourth and fifth lines should be empty
311+
expect(lines[3]).toBe("");
312+
expect(lines[4]).toBe("");
313+
});
314+
315+
test("should handle global metadata combined with line-specific dates", () => {
316+
// Set global metadata
317+
modal.taskMetadata.priority = 3;
318+
modal.taskMetadata.project = "TestProject";
319+
320+
const content = "Task 1 tomorrow\nTask 2 next week";
321+
const result = modal.processContentWithMetadata(content);
322+
323+
const lines = result.split("\n");
324+
expect(lines).toHaveLength(2);
325+
326+
// Both lines should have global metadata (priority, project) plus line-specific dates
327+
lines.forEach((line) => {
328+
expect(line).toContain("🔼"); // Priority medium
329+
expect(line).toContain("#project/TestProject");
330+
expect(line).toContain("📅"); // Line-specific date
331+
});
332+
333+
// Clean up
334+
modal.taskMetadata.priority = undefined;
335+
modal.taskMetadata.project = undefined;
336+
});
337+
});
338+
192339
describe("Manual Override Functionality", () => {
193340
test("should track manually set dates", () => {
194341
modal.markAsManuallySet("dueDate");

src/__tests__/TimeParsingService.test.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
TimeParsingService,
33
DEFAULT_TIME_PARSING_CONFIG,
4+
LineParseResult,
45
} from "../utils/TimeParsingService";
56

67
describe("TimeParsingService", () => {
@@ -121,7 +122,7 @@ describe("TimeParsingService", () => {
121122
});
122123

123124
test('should parse "下周"', () => {
124-
const result = service.parseTimeExpressions("下周开始新项目");
125+
const result = service.parseTimeExpressions("下周完成项目");
125126

126127
expect(result.parsedExpressions).toHaveLength(1);
127128
expect(result.parsedExpressions[0].text).toBe("下周");
@@ -357,4 +358,93 @@ describe("TimeParsingService", () => {
357358
expect(result.parsedExpressions[0].type).toBe("scheduled");
358359
});
359360
});
361+
362+
describe("Per-Line Processing", () => {
363+
test("should parse single line correctly", () => {
364+
const result = service.parseTimeExpressionsForLine("task tomorrow");
365+
366+
expect(result.originalLine).toBe("task tomorrow");
367+
expect(result.cleanedLine).toBe("task");
368+
expect(result.dueDate).toBeDefined();
369+
expect(result.parsedExpressions).toHaveLength(1);
370+
});
371+
372+
test("should parse multiple lines independently", () => {
373+
const lines = [
374+
"task 1 tomorrow",
375+
"task 2 next week",
376+
"task 3 no date",
377+
];
378+
const results = service.parseTimeExpressionsPerLine(lines);
379+
380+
expect(results).toHaveLength(3);
381+
382+
// First line
383+
expect(results[0].originalLine).toBe("task 1 tomorrow");
384+
expect(results[0].cleanedLine).toBe("task 1");
385+
expect(results[0].dueDate).toBeDefined();
386+
387+
// Second line
388+
expect(results[1].originalLine).toBe("task 2 next week");
389+
expect(results[1].cleanedLine).toBe("task 2");
390+
expect(results[1].dueDate).toBeDefined();
391+
392+
// Third line
393+
expect(results[2].originalLine).toBe("task 3 no date");
394+
expect(results[2].cleanedLine).toBe("task 3 no date");
395+
expect(results[2].dueDate).toBeUndefined();
396+
});
397+
398+
test("should handle different date types per line", () => {
399+
const lines = [
400+
"start project tomorrow",
401+
"meeting scheduled for next week",
402+
"deadline by Friday",
403+
];
404+
const results = service.parseTimeExpressionsPerLine(lines);
405+
406+
expect(results).toHaveLength(3);
407+
expect(results[0].startDate).toBeDefined();
408+
expect(results[1].scheduledDate).toBeDefined();
409+
expect(results[2].dueDate).toBeDefined();
410+
});
411+
412+
test("should preserve line structure in multiline content", () => {
413+
const content = "task 1 tomorrow\ntask 2 next week\ntask 3";
414+
const lines = content.split("\n");
415+
const results = service.parseTimeExpressionsPerLine(lines);
416+
417+
expect(results).toHaveLength(3);
418+
419+
// Verify each line is processed independently
420+
const cleanedLines = results.map((r) => r.cleanedLine);
421+
const reconstructed = cleanedLines.join("\n");
422+
423+
expect(reconstructed).toBe("task 1\ntask 2\ntask 3");
424+
});
425+
426+
test("should handle empty lines", () => {
427+
const lines = ["task tomorrow", "", "another task"];
428+
const results = service.parseTimeExpressionsPerLine(lines);
429+
430+
expect(results).toHaveLength(3);
431+
expect(results[0].dueDate).toBeDefined();
432+
expect(results[1].dueDate).toBeUndefined();
433+
expect(results[1].cleanedLine).toBe("");
434+
expect(results[2].dueDate).toBeUndefined();
435+
});
436+
437+
test("should handle Chinese time expressions per line", () => {
438+
const lines = ["任务1 明天", "任务2 下周", "任务3"];
439+
const results = service.parseTimeExpressionsPerLine(lines);
440+
441+
expect(results).toHaveLength(3);
442+
expect(results[0].cleanedLine).toBe("任务1");
443+
expect(results[0].dueDate).toBeDefined();
444+
expect(results[1].cleanedLine).toBe("任务2");
445+
expect(results[1].dueDate).toBeDefined();
446+
expect(results[2].cleanedLine).toBe("任务3");
447+
expect(results[2].dueDate).toBeUndefined();
448+
});
449+
});
360450
});

0 commit comments

Comments
 (0)