diff --git a/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_html.md b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_html.md new file mode 100644 index 0000000000..3c779a4e0a --- /dev/null +++ b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_html.md @@ -0,0 +1,3 @@ +# link_in_task_html + +- [ ] #task Task in 'link_in_task_html' - see the [Tasks Documentation](https://publish.obsidian.md/tasks/Introduction) diff --git a/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_markdown_link.md b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_markdown_link.md new file mode 100644 index 0000000000..731a3aa9c5 --- /dev/null +++ b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_markdown_link.md @@ -0,0 +1,3 @@ +# link_in_task_markdown_link + +- [ ] #task Task in 'link_in_task_markdown_link' [jason_properties](jason_properties) [multiple_headings](multiple_headings) diff --git a/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_wikilink.md b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_wikilink.md new file mode 100644 index 0000000000..48d1cf5a66 --- /dev/null +++ b/resources/sample_vaults/Tasks-Demo/Test Data/link_in_task_wikilink.md @@ -0,0 +1,3 @@ +# link_in_task_wikilink + +- [ ] #task Task in 'link_in_task_wikilink' [[link_in_task_wikilink]] [[multiple_headings]] diff --git a/resources/sample_vaults/Tasks-Demo/Test Data/links_everywhere.md b/resources/sample_vaults/Tasks-Demo/Test Data/links_everywhere.md new file mode 100644 index 0000000000..d4bc2f08b0 --- /dev/null +++ b/resources/sample_vaults/Tasks-Demo/Test Data/links_everywhere.md @@ -0,0 +1,10 @@ +--- +link-in-frontmatter: "[[link_in_yaml]]" +--- +# links_everywhere + +A link in the file body: [[link_in_file_body]] + +## A link in a [[link_in_heading]] + +- [ ] #task Task in 'links_everywhere' - a link on the task: [[link_in_task_wikilink]] diff --git a/tests/Obsidian/AllCacheSampleData.ts b/tests/Obsidian/AllCacheSampleData.ts index 03bd504c60..3f9f7590ef 100644 --- a/tests/Obsidian/AllCacheSampleData.ts +++ b/tests/Obsidian/AllCacheSampleData.ts @@ -36,8 +36,12 @@ import jason_properties from './__test_data__/jason_properties.json'; import link_in_file_body from './__test_data__/link_in_file_body.json'; import link_in_file_body_with_custom_display_text from './__test_data__/link_in_file_body_with_custom_display_text.json'; import link_in_heading from './__test_data__/link_in_heading.json'; +import link_in_task_html from './__test_data__/link_in_task_html.json'; +import link_in_task_markdown_link from './__test_data__/link_in_task_markdown_link.json'; +import link_in_task_wikilink from './__test_data__/link_in_task_wikilink.json'; import link_in_yaml from './__test_data__/link_in_yaml.json'; import link_is_broken from './__test_data__/link_is_broken.json'; +import links_everywhere from './__test_data__/links_everywhere.json'; import list_statuses from './__test_data__/list_statuses.json'; import list_styles from './__test_data__/list_styles.json'; import multi_line_task_and_list_item from './__test_data__/multi_line_task_and_list_item.json'; @@ -101,8 +105,12 @@ export function allCacheSampleData() { link_in_file_body, link_in_file_body_with_custom_display_text, link_in_heading, + link_in_task_html, + link_in_task_markdown_link, + link_in_task_wikilink, link_in_yaml, link_is_broken, + links_everywhere, list_statuses, list_styles, multi_line_task_and_list_item, diff --git a/tests/Obsidian/Cache.test.ts b/tests/Obsidian/Cache.test.ts index 34516b1e1e..60e72e8a62 100644 --- a/tests/Obsidian/Cache.test.ts +++ b/tests/Obsidian/Cache.test.ts @@ -2,6 +2,7 @@ * @jest-environment jsdom */ import moment from 'moment/moment'; +import type { CachedMetadata } from 'obsidian'; import type { ListItem } from '../../src/Task/ListItem'; import { getTasksFileFromMockData, listPathAndData } from '../TestingTools/MockDataHelpers'; import inheritance_1parent1child from './__test_data__/inheritance_1parent1child.json'; @@ -29,8 +30,9 @@ import callout from './__test_data__/callout.json'; import callout_labelled from './__test_data__/callout_labelled.json'; import callout_custom from './__test_data__/callout_custom.json'; import callouts_nested_issue_2890_unlabelled from './__test_data__/callouts_nested_issue_2890_unlabelled.json'; +import links_everywhere from './__test_data__/links_everywhere.json'; import { allCacheSampleData } from './AllCacheSampleData'; -import { readTasksFromSimulatedFile } from './SimulatedFile'; +import { type SimulatedFile, readTasksFromSimulatedFile } from './SimulatedFile'; window.moment = moment; @@ -604,6 +606,130 @@ describe('cache', () => { }); }); +describe('accessing links in file', function () { + describe('explore accessing links in file "links_everywhere.md"', () => { + const data = links_everywhere as unknown as SimulatedFile; + + const tasks = readTasksFromSimulatedFile(data); + expect(tasks.length).toEqual(1); + const task = tasks[0]; + + const cachedMetadata: CachedMetadata = task.file.cachedMetadata; + + /** + * I am thinking of the following, to evantually make links accessible to users. + * 1. Provide a class or interface called Link, with fields: + * - displayText, e.g. "link_in_yaml" + * - link, e.g. "link_in_yaml" + * - original, e.g. "[[link_in_yaml]]" + * 2. Add some getters that construct the relevant Link objects from cached metadata on demand, such as: + * - task.file.linksInBody + * - task.file.linksInFrontMatter + * - task.file.allLinks + * - task.links + * 3. Consider the vocabulary - some dataview users talk about inlines and outlinks. + * The above are all outlinks - but do we want to name them as such, to prepare + * for if or when inlinks are also supported? + */ + + it('see source', () => { + expect(data.fileContents).toMatchInlineSnapshot(` + "--- + link-in-frontmatter: "[[link_in_yaml]]" + --- + # links_everywhere + + A link in the file body: [[link_in_file_body]] + + ## A link in a [[link_in_heading]] + + - [ ] #task Task in 'links_everywhere' - a link on the task: [[link_in_task_wikilink]] + " + `); + }); + + it('should access links in frontmatter', () => { + // Update to Obsidian API 1.4.0 to access cachedMetadata.frontmatterLinks + // @ts-expect-error TS2551: Property frontmatterLinks does not exist on type CachedMetadata + const frontMatterLinks = cachedMetadata['frontmatterLinks']; + expect(frontMatterLinks).toBeDefined(); + + const firstFrontMatterLink = frontMatterLinks![0]; + expect(firstFrontMatterLink.original).toEqual('[[link_in_yaml]]'); + expect(firstFrontMatterLink).toMatchInlineSnapshot(` + { + "displayText": "link_in_yaml", + "key": "link-in-frontmatter", + "link": "link_in_yaml", + "original": "[[link_in_yaml]]", + } + `); + }); + + it('should access links in file body', () => { + const fileBodyLinks = cachedMetadata.links; + + const originalLinkText = fileBodyLinks?.map((link) => link.original).join('\n'); + expect(originalLinkText).toMatchInlineSnapshot(` + "[[link_in_file_body]] + [[link_in_heading]] + [[link_in_task_wikilink]]" + `); + + const firstFileBodyLink = fileBodyLinks![0]; + expect(firstFileBodyLink).toMatchInlineSnapshot(` + { + "displayText": "link_in_file_body", + "link": "link_in_file_body", + "original": "[[link_in_file_body]]", + "position": { + "end": { + "col": 46, + "line": 5, + "offset": 114, + }, + "start": { + "col": 25, + "line": 5, + "offset": 93, + }, + }, + } + `); + }); + + it('should access links in task line', () => { + const fileBodyLinks = cachedMetadata.links; + const linksOnTask = fileBodyLinks?.filter((link) => link.position.start.line === task.lineNumber); + + expect(linksOnTask).toBeDefined(); + expect(linksOnTask?.length).toEqual(1); + + const firstLinkOnTask = linksOnTask![0]; + expect(firstLinkOnTask.original).toEqual('[[link_in_task_wikilink]]'); + expect(firstLinkOnTask).toMatchInlineSnapshot(` + { + "displayText": "link_in_task_wikilink", + "link": "link_in_task_wikilink", + "original": "[[link_in_task_wikilink]]", + "position": { + "end": { + "col": 86, + "line": 9, + "offset": 238, + }, + "start": { + "col": 61, + "line": 9, + "offset": 213, + }, + }, + } + `); + }); + }); +}); + describe('all mock files', () => { const files: any = allCacheSampleData(); diff --git a/tests/Obsidian/__test_data__/link_in_task_html.json b/tests/Obsidian/__test_data__/link_in_task_html.json new file mode 100644 index 0000000000..7af57e5814 --- /dev/null +++ b/tests/Obsidian/__test_data__/link_in_task_html.json @@ -0,0 +1,96 @@ +{ + "cachedMetadata": { + "headings": [ + { + "heading": "link_in_task_html", + "level": 1, + "position": { + "end": { + "col": 19, + "line": 0, + "offset": 19 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + } + } + ], + "listItems": [ + { + "parent": -2, + "position": { + "end": { + "col": 119, + "line": 2, + "offset": 140 + }, + "start": { + "col": 0, + "line": 2, + "offset": 21 + } + }, + "task": " " + } + ], + "sections": [ + { + "position": { + "end": { + "col": 19, + "line": 0, + "offset": 19 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + }, + "type": "heading" + }, + { + "position": { + "end": { + "col": 119, + "line": 2, + "offset": 140 + }, + "start": { + "col": 0, + "line": 2, + "offset": 21 + } + }, + "type": "list" + } + ], + "tags": [ + { + "position": { + "end": { + "col": 11, + "line": 2, + "offset": 32 + }, + "start": { + "col": 6, + "line": 2, + "offset": 27 + } + }, + "tag": "#task" + } + ] + }, + "fileContents": "# link_in_task_html\n\n- [ ] #task Task in 'link_in_task_html' - see the [Tasks Documentation](https://publish.obsidian.md/tasks/Introduction)\n", + "filePath": "Test Data/link_in_task_html.md", + "getAllTags": [ + "#task" + ], + "obsidianApiVersion": "1.7.7", + "parseFrontMatterTags": null +} \ No newline at end of file diff --git a/tests/Obsidian/__test_data__/link_in_task_markdown_link.json b/tests/Obsidian/__test_data__/link_in_task_markdown_link.json new file mode 100644 index 0000000000..347df2eb24 --- /dev/null +++ b/tests/Obsidian/__test_data__/link_in_task_markdown_link.json @@ -0,0 +1,132 @@ +{ + "cachedMetadata": { + "headings": [ + { + "heading": "link_in_task_markdown_link", + "level": 1, + "position": { + "end": { + "col": 28, + "line": 0, + "offset": 28 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + } + } + ], + "links": [ + { + "displayText": "jason_properties", + "link": "jason_properties", + "original": "[jason_properties](jason_properties)", + "position": { + "end": { + "col": 85, + "line": 2, + "offset": 115 + }, + "start": { + "col": 49, + "line": 2, + "offset": 79 + } + } + }, + { + "displayText": "multiple_headings", + "link": "multiple_headings", + "original": "[multiple_headings](multiple_headings)", + "position": { + "end": { + "col": 124, + "line": 2, + "offset": 154 + }, + "start": { + "col": 86, + "line": 2, + "offset": 116 + } + } + } + ], + "listItems": [ + { + "parent": -2, + "position": { + "end": { + "col": 124, + "line": 2, + "offset": 154 + }, + "start": { + "col": 0, + "line": 2, + "offset": 30 + } + }, + "task": " " + } + ], + "sections": [ + { + "position": { + "end": { + "col": 28, + "line": 0, + "offset": 28 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + }, + "type": "heading" + }, + { + "position": { + "end": { + "col": 124, + "line": 2, + "offset": 154 + }, + "start": { + "col": 0, + "line": 2, + "offset": 30 + } + }, + "type": "list" + } + ], + "tags": [ + { + "position": { + "end": { + "col": 11, + "line": 2, + "offset": 41 + }, + "start": { + "col": 6, + "line": 2, + "offset": 36 + } + }, + "tag": "#task" + } + ] + }, + "fileContents": "# link_in_task_markdown_link\n\n- [ ] #task Task in 'link_in_task_markdown_link' [jason_properties](jason_properties) [multiple_headings](multiple_headings)\n", + "filePath": "Test Data/link_in_task_markdown_link.md", + "getAllTags": [ + "#task" + ], + "obsidianApiVersion": "1.7.7", + "parseFrontMatterTags": null +} \ No newline at end of file diff --git a/tests/Obsidian/__test_data__/link_in_task_wikilink.json b/tests/Obsidian/__test_data__/link_in_task_wikilink.json new file mode 100644 index 0000000000..577b223a54 --- /dev/null +++ b/tests/Obsidian/__test_data__/link_in_task_wikilink.json @@ -0,0 +1,132 @@ +{ + "cachedMetadata": { + "headings": [ + { + "heading": "link_in_task_wikilink", + "level": 1, + "position": { + "end": { + "col": 23, + "line": 0, + "offset": 23 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + } + } + ], + "links": [ + { + "displayText": "link_in_task_wikilink", + "link": "link_in_task_wikilink", + "original": "[[link_in_task_wikilink]]", + "position": { + "end": { + "col": 69, + "line": 2, + "offset": 94 + }, + "start": { + "col": 44, + "line": 2, + "offset": 69 + } + } + }, + { + "displayText": "multiple_headings", + "link": "multiple_headings", + "original": "[[multiple_headings]]", + "position": { + "end": { + "col": 91, + "line": 2, + "offset": 116 + }, + "start": { + "col": 70, + "line": 2, + "offset": 95 + } + } + } + ], + "listItems": [ + { + "parent": -2, + "position": { + "end": { + "col": 91, + "line": 2, + "offset": 116 + }, + "start": { + "col": 0, + "line": 2, + "offset": 25 + } + }, + "task": " " + } + ], + "sections": [ + { + "position": { + "end": { + "col": 23, + "line": 0, + "offset": 23 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + }, + "type": "heading" + }, + { + "position": { + "end": { + "col": 91, + "line": 2, + "offset": 116 + }, + "start": { + "col": 0, + "line": 2, + "offset": 25 + } + }, + "type": "list" + } + ], + "tags": [ + { + "position": { + "end": { + "col": 11, + "line": 2, + "offset": 36 + }, + "start": { + "col": 6, + "line": 2, + "offset": 31 + } + }, + "tag": "#task" + } + ] + }, + "fileContents": "# link_in_task_wikilink\n\n- [ ] #task Task in 'link_in_task_wikilink' [[link_in_task_wikilink]] [[multiple_headings]]\n", + "filePath": "Test Data/link_in_task_wikilink.md", + "getAllTags": [ + "#task" + ], + "obsidianApiVersion": "1.7.7", + "parseFrontMatterTags": null +} \ No newline at end of file diff --git a/tests/Obsidian/__test_data__/links_everywhere.json b/tests/Obsidian/__test_data__/links_everywhere.json new file mode 100644 index 0000000000..938a8f06c1 --- /dev/null +++ b/tests/Obsidian/__test_data__/links_everywhere.json @@ -0,0 +1,233 @@ +{ + "cachedMetadata": { + "frontmatter": { + "link-in-frontmatter": "[[link_in_yaml]]" + }, + "frontmatterLinks": [ + { + "displayText": "link_in_yaml", + "key": "link-in-frontmatter", + "link": "link_in_yaml", + "original": "[[link_in_yaml]]" + } + ], + "frontmatterPosition": { + "end": { + "col": 3, + "line": 2, + "offset": 47 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + }, + "headings": [ + { + "heading": "links_everywhere", + "level": 1, + "position": { + "end": { + "col": 18, + "line": 3, + "offset": 66 + }, + "start": { + "col": 0, + "line": 3, + "offset": 48 + } + } + }, + { + "heading": "A link in a [[link_in_heading]]", + "level": 2, + "position": { + "end": { + "col": 34, + "line": 7, + "offset": 150 + }, + "start": { + "col": 0, + "line": 7, + "offset": 116 + } + } + } + ], + "links": [ + { + "displayText": "link_in_file_body", + "link": "link_in_file_body", + "original": "[[link_in_file_body]]", + "position": { + "end": { + "col": 46, + "line": 5, + "offset": 114 + }, + "start": { + "col": 25, + "line": 5, + "offset": 93 + } + } + }, + { + "displayText": "link_in_heading", + "link": "link_in_heading", + "original": "[[link_in_heading]]", + "position": { + "end": { + "col": 34, + "line": 7, + "offset": 150 + }, + "start": { + "col": 15, + "line": 7, + "offset": 131 + } + } + }, + { + "displayText": "link_in_task_wikilink", + "link": "link_in_task_wikilink", + "original": "[[link_in_task_wikilink]]", + "position": { + "end": { + "col": 86, + "line": 9, + "offset": 238 + }, + "start": { + "col": 61, + "line": 9, + "offset": 213 + } + } + } + ], + "listItems": [ + { + "parent": -9, + "position": { + "end": { + "col": 86, + "line": 9, + "offset": 238 + }, + "start": { + "col": 0, + "line": 9, + "offset": 152 + } + }, + "task": " " + } + ], + "sections": [ + { + "position": { + "end": { + "col": 3, + "line": 2, + "offset": 47 + }, + "start": { + "col": 0, + "line": 0, + "offset": 0 + } + }, + "type": "yaml" + }, + { + "position": { + "end": { + "col": 18, + "line": 3, + "offset": 66 + }, + "start": { + "col": 0, + "line": 3, + "offset": 48 + } + }, + "type": "heading" + }, + { + "position": { + "end": { + "col": 46, + "line": 5, + "offset": 114 + }, + "start": { + "col": 0, + "line": 5, + "offset": 68 + } + }, + "type": "paragraph" + }, + { + "position": { + "end": { + "col": 34, + "line": 7, + "offset": 150 + }, + "start": { + "col": 0, + "line": 7, + "offset": 116 + } + }, + "type": "heading" + }, + { + "position": { + "end": { + "col": 86, + "line": 9, + "offset": 238 + }, + "start": { + "col": 0, + "line": 9, + "offset": 152 + } + }, + "type": "list" + } + ], + "tags": [ + { + "position": { + "end": { + "col": 11, + "line": 9, + "offset": 163 + }, + "start": { + "col": 6, + "line": 9, + "offset": 158 + } + }, + "tag": "#task" + } + ] + }, + "fileContents": "---\nlink-in-frontmatter: \"[[link_in_yaml]]\"\n---\n# links_everywhere\n\nA link in the file body: [[link_in_file_body]]\n\n## A link in a [[link_in_heading]]\n\n- [ ] #task Task in 'links_everywhere' - a link on the task: [[link_in_task_wikilink]]\n", + "filePath": "Test Data/links_everywhere.md", + "getAllTags": [ + "#task" + ], + "obsidianApiVersion": "1.7.7", + "parseFrontMatterTags": null +} \ No newline at end of file