From e88ded8c975501a09f3f306d2f2d7c732f20388f Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Sat, 25 Nov 2023 13:54:58 +0200 Subject: [PATCH] [ #60 , extract tasks ] --- __mocks__/content/index.mdx | 6 +++ src/lib/parseFile.ts | 37 +++++++++++++++++ src/tests/extractTasks.spec.ts | 72 ++++++++++++++++++++++++++++++++++ src/tests/parseFile.spec.ts | 10 +++++ src/tests/process.spec.ts | 14 +++++++ 5 files changed, 139 insertions(+) create mode 100644 src/tests/extractTasks.spec.ts diff --git a/__mocks__/content/index.mdx b/__mocks__/content/index.mdx index 56d9c80..fa8bb6b 100644 --- a/__mocks__/content/index.mdx +++ b/__mocks__/content/index.mdx @@ -6,3 +6,9 @@ tags: tag1, tag2, tag3 # Welcome [link](blog0.mdx) + +- [] uncompleted task 1 +- [ ] uncompleted task 2 + +- [x] completed task 1 +- [X] completed task 2 \ No newline at end of file diff --git a/src/lib/parseFile.ts b/src/lib/parseFile.ts index 3a26eee..fddc1f3 100644 --- a/src/lib/parseFile.ts +++ b/src/lib/parseFile.ts @@ -24,6 +24,9 @@ export function parseFile(source: string, options?: ParsingOptions) { // Links const links = extractWikiLinks(ast, options); + const tasks = extractTasks(ast); + metadata.tasks = tasks; + return { metadata, links, @@ -155,6 +158,40 @@ export const extractWikiLinks = (ast: Root, options?: ParsingOptions) => { return wikiLinks; }; +export interface Task { + description: string; + checked: boolean; +} + +export const extractTasks = (ast: Root) => { + const nodes = selectAll("*", ast); + const tasks: Task[] = []; + nodes.map((node: any) => { + if (node.type === "listItem") { + const description = recursivelyExtractText(node).trim(); + const checked = node.checked; + if (checked !== null) { + tasks.push({ + description, + checked, + }); + } + } + }); + + return tasks; +}; + +function recursivelyExtractText(node) { + if (node.value) { + return node.value; + } else if (node.children) { + return node.children.map(recursivelyExtractText).join(" "); + } else { + return ""; + } +} + // links = extractWikiLinks({ // source, // // TODO pass slug instead of file path as hrefs/srcs are sluggified too diff --git a/src/tests/extractTasks.spec.ts b/src/tests/extractTasks.spec.ts new file mode 100644 index 0000000..cc3de8e --- /dev/null +++ b/src/tests/extractTasks.spec.ts @@ -0,0 +1,72 @@ +import { extractTasks, processAST } from "../lib/parseFile"; + +const getTasksFromSource = (source: string) => { + const ast = processAST(source, {}); + const tasks = extractTasks(ast); + return tasks; +}; + +describe("extractTasks", () => { + test("should extract uncompleted tasks from body", () => { + const tasks = getTasksFromSource( + "- [] uncompleted task 1\n- [ ] uncompleted task 2" + ); + const expectedTasks = [ + { description: "uncompleted task 2", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should extract completed tasks from body", () => { + const tasks = getTasksFromSource( + "- [x] completed task 1\n- [X] completed task 2" + ); + const expectedTasks = [ + { description: "completed task 1", checked: true }, + { description: "completed task 2", checked: true }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle mixed completed and uncompleted tasks", () => { + const tasks = getTasksFromSource( + "- [x] completed task\n- [ ] uncompleted task" + ); + const expectedTasks = [ + { description: "completed task", checked: true }, + { description: "uncompleted task", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with leading and trailing spaces", () => { + const tasks = getTasksFromSource( + "- [x] completed task \n- [ ] uncompleted task " + ); + const expectedTasks = [ + { description: "completed task", checked: true }, + { description: "uncompleted task", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with different checkbox formats", () => { + const tasks = getTasksFromSource( + "- [x] task 1\n- [X] task 2\n- [ ] task 3" + ); + const expectedTasks = [ + { description: "task 1", checked: true }, + { description: "task 2", checked: true }, + { description: "task 3", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with special characters", () => { + const tasks = getTasksFromSource("- [x] task with $pecial character$"); + const expectedTasks = [ + { description: "task with $pecial character$", checked: true }, + ]; + expect(tasks).toEqual(expectedTasks); + }); +}); diff --git a/src/tests/parseFile.spec.ts b/src/tests/parseFile.spec.ts index 482aaa0..fd77369 100644 --- a/src/tests/parseFile.spec.ts +++ b/src/tests/parseFile.spec.ts @@ -10,6 +10,8 @@ tags: a, b, c [[blog/Some Other Link]] [[blog/Some Other Link|Page Alias]] ![[Some Image.png]] +- [ ] uncompleted task +- [x] completed task `; describe("parseFile", () => { @@ -18,6 +20,10 @@ describe("parseFile", () => { title: "Hello World", authors: ["John Doe", "Jane Doe"], tags: ["a", "b", "c"], + tasks: [ + { description: "uncompleted task", checked: false }, + { description: "completed task", checked: true }, + ], }; const expectedLinks = [ { @@ -63,6 +69,10 @@ describe("parseFile", () => { title: "Hello World", authors: ["John Doe", "Jane Doe"], tags: ["a", "b", "c"], + tasks: [ + { description: "uncompleted task", checked: false }, + { description: "completed task", checked: true }, + ], }; const expectedLinks = [ { diff --git a/src/tests/process.spec.ts b/src/tests/process.spec.ts index 936195f..43fd910 100644 --- a/src/tests/process.spec.ts +++ b/src/tests/process.spec.ts @@ -21,6 +21,20 @@ describe("Can parse a file and get file info", () => { expect(fileInfo.metadata).toEqual({ title: "Homepage", tags: ["tag1", "tag2", "tag3"], + tasks: [ + { + checked: false, + description: "uncompleted task 2", + }, + { + checked: true, + description: "completed task 1", + }, + { + checked: true, + description: "completed task 2", + }, + ], }); expect(fileInfo.links).toEqual([ {