diff --git a/resources/sample_vaults/Tasks-Demo/Test Data/inheritance_rendering_sample.md b/resources/sample_vaults/Tasks-Demo/Test Data/inheritance_rendering_sample.md new file mode 100644 index 0000000000..6753633e3a --- /dev/null +++ b/resources/sample_vaults/Tasks-Demo/Test Data/inheritance_rendering_sample.md @@ -0,0 +1,9 @@ +- grandparent list item + - [ ] parent 1 + - [ ] child 1 + - [ ] grandchild 1 + - list item grand grand child + - [ ] child 2 + - non task grandchild + - [ ] grand grand child + - [ ] parent 2 diff --git a/src/Renderer/QueryResultsRenderer.ts b/src/Renderer/QueryResultsRenderer.ts index 6f541c8bb5..338e4f33a0 100644 --- a/src/Renderer/QueryResultsRenderer.ts +++ b/src/Renderer/QueryResultsRenderer.ts @@ -14,7 +14,7 @@ import { postponeButtonTitle, shouldShowPostponeButton } from '../DateTime/Postp import type { TasksFile } from '../Scripting/TasksFile'; import type { Task } from '../Task/Task'; import { PostponeMenu } from '../ui/Menus/PostponeMenu'; -import { TaskLineRenderer, createAndAppendElement } from './TaskLineRenderer'; +import { TaskLineRenderer, type TextRenderer, createAndAppendElement } from './TaskLineRenderer'; type BacklinksEventHandler = (ev: MouseEvent, task: Task) => Promise; type EditButtonClickHandler = (event: MouseEvent, task: Task, allTasks: Task[]) => void; @@ -46,6 +46,9 @@ export class QueryResultsRenderer { public query: IQuery; protected queryType: string; // whilst there is only one query type, there is no point logging this value + // Renders the description in TaskLineRenderer: + private readonly textRenderer; + // Renders the group heading in this class: private readonly renderMarkdown; private readonly obsidianComponent: Component | null; @@ -55,11 +58,13 @@ export class QueryResultsRenderer { tasksFile: TasksFile, renderMarkdown: (markdown: string, el: HTMLElement, sourcePath: string, component: Component) => Promise, obsidianComponent: Component | null, + textRenderer: TextRenderer = TaskLineRenderer.obsidianMarkdownRenderer, ) { this.source = source; this.tasksFile = tasksFile; this.renderMarkdown = renderMarkdown; this.obsidianComponent = obsidianComponent; + this.textRenderer = textRenderer; // The engine is chosen on the basis of the code block language. Currently, // there is only the main engine for the plugin, this allows others to be @@ -204,6 +209,7 @@ export class QueryResultsRenderer { if (groupingAttribute && groupingAttribute.length > 0) taskList.dataset.taskGroupBy = groupingAttribute; const taskLineRenderer = new TaskLineRenderer({ + textRenderer: this.textRenderer, obsidianComponent: this.obsidianComponent, parentUlElement: taskList, taskLayoutOptions: this.query.taskLayoutOptions, diff --git a/src/Renderer/TaskLineRenderer.ts b/src/Renderer/TaskLineRenderer.ts index 78b34f41f7..c0854f7050 100644 --- a/src/Renderer/TaskLineRenderer.ts +++ b/src/Renderer/TaskLineRenderer.ts @@ -59,7 +59,7 @@ export class TaskLineRenderer { private readonly taskLayoutOptions: TaskLayoutOptions; private readonly queryLayoutOptions: QueryLayoutOptions; - private static async obsidianMarkdownRenderer( + public static async obsidianMarkdownRenderer( text: string, element: HTMLSpanElement, path: string, diff --git a/tests/Obsidian/AllCacheSampleData.ts b/tests/Obsidian/AllCacheSampleData.ts index 05c8ddaac3..e91e4d9e77 100644 --- a/tests/Obsidian/AllCacheSampleData.ts +++ b/tests/Obsidian/AllCacheSampleData.ts @@ -26,6 +26,7 @@ import { inheritance_2siblings } from './__test_data__/inheritance_2siblings'; import { inheritance_listitem_listitem_task } from './__test_data__/inheritance_listitem_listitem_task'; import { inheritance_listitem_task } from './__test_data__/inheritance_listitem_task'; import { inheritance_listitem_task_siblings } from './__test_data__/inheritance_listitem_task_siblings'; +import { inheritance_rendering_sample } from './__test_data__/inheritance_rendering_sample'; import { inheritance_task_2listitem_3task } from './__test_data__/inheritance_task_2listitem_3task'; import { inheritance_task_listitem } from './__test_data__/inheritance_task_listitem'; import { inheritance_task_listitem_mixed_grandchildren } from './__test_data__/inheritance_task_listitem_mixed_grandchildren'; @@ -89,6 +90,7 @@ export function allCacheSampleData() { inheritance_listitem_listitem_task, inheritance_listitem_task, inheritance_listitem_task_siblings, + inheritance_rendering_sample, inheritance_task_2listitem_3task, inheritance_task_listitem, inheritance_task_listitem_mixed_grandchildren, diff --git a/tests/Obsidian/__test_data__/inheritance_rendering_sample.ts b/tests/Obsidian/__test_data__/inheritance_rendering_sample.ts new file mode 100644 index 0000000000..d051a35341 --- /dev/null +++ b/tests/Obsidian/__test_data__/inheritance_rendering_sample.ts @@ -0,0 +1,178 @@ +export const inheritance_rendering_sample = { + filePath: 'Test Data/inheritance_rendering_sample.md', + fileContents: + '- grandparent list item\n' + + ' - [ ] parent 1\n' + + ' - [ ] child 1\n' + + ' - [ ] grandchild 1\n' + + ' - list item grand grand child\n' + + ' - [ ] child 2\n' + + ' - non task grandchild\n' + + ' - [ ] grand grand child\n' + + ' - [ ] parent 2\n', + cachedMetadata: { + sections: [ + { + type: 'list', + position: { + start: { + line: 0, + col: 0, + offset: 0, + }, + end: { + line: 8, + col: 18, + offset: 257, + }, + }, + }, + ], + listItems: [ + { + position: { + start: { + line: 0, + col: 0, + offset: 0, + }, + end: { + line: 0, + col: 24, + offset: 24, + }, + }, + parent: -1, + }, + { + position: { + start: { + line: 1, + col: 3, + offset: 28, + }, + end: { + line: 1, + col: 18, + offset: 43, + }, + }, + parent: 0, + task: ' ', + }, + { + position: { + start: { + line: 2, + col: 6, + offset: 50, + }, + end: { + line: 2, + col: 21, + offset: 65, + }, + }, + parent: 1, + task: ' ', + }, + { + position: { + start: { + line: 3, + col: 10, + offset: 76, + }, + end: { + line: 3, + col: 30, + offset: 96, + }, + }, + parent: 2, + task: ' ', + }, + { + position: { + start: { + line: 4, + col: 14, + offset: 111, + }, + end: { + line: 4, + col: 45, + offset: 142, + }, + }, + parent: 3, + }, + { + position: { + start: { + line: 5, + col: 6, + offset: 149, + }, + end: { + line: 5, + col: 21, + offset: 164, + }, + }, + parent: 1, + task: ' ', + }, + { + position: { + start: { + line: 6, + col: 10, + offset: 175, + }, + end: { + line: 6, + col: 33, + offset: 198, + }, + }, + parent: 5, + }, + { + position: { + start: { + line: 7, + col: 14, + offset: 213, + }, + end: { + line: 7, + col: 39, + offset: 238, + }, + }, + parent: 6, + task: ' ', + }, + { + position: { + start: { + line: 8, + col: 3, + offset: 242, + }, + end: { + line: 8, + col: 18, + offset: 257, + }, + }, + parent: 0, + task: ' ', + }, + ], + }, + obsidianApiVersion: '1.7.3', + getAllTags: [], + parseFrontMatterTags: null, +}; diff --git a/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html index cf0580980c..8693b924e3 100644 --- a/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html +++ b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_fully_populated_task.approved.html @@ -15,7 +15,7 @@ data-task-status-type="TODO"> - + Do exercises #todo #health 🆔 abcdef ⛔ 123456,abc123 🔼 diff --git a/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_parent-child_items.approved.html b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_parent-child_items.approved.html new file mode 100644 index 0000000000..78627a4aff --- /dev/null +++ b/tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_parent-child_items.approved.html @@ -0,0 +1,125 @@ +
+ +
6 tasks
+
diff --git a/tests/Renderer/QueryResultsRenderer.test.ts b/tests/Renderer/QueryResultsRenderer.test.ts index 9865b7db64..70eed7204a 100644 --- a/tests/Renderer/QueryResultsRenderer.test.ts +++ b/tests/Renderer/QueryResultsRenderer.test.ts @@ -2,12 +2,16 @@ * @jest-environment jsdom */ import moment from 'moment'; +import type { Task } from 'Task/Task'; import { State } from '../../src/Obsidian/Cache'; import { QueryResultsRenderer } from '../../src/Renderer/QueryResultsRenderer'; import { TasksFile } from '../../src/Scripting/TasksFile'; +import { inheritance_rendering_sample } from '../Obsidian/__test_data__/inheritance_rendering_sample'; +import { readTasksFromSimulatedFile } from '../Obsidian/SimulatedFile'; import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers'; import { prettifyHTML } from '../TestingTools/HTMLHelpers'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; +import { mockHTMLRenderer } from './RenderingTestHelpers'; window.moment = moment; @@ -21,15 +25,15 @@ afterEach(() => { }); describe('QueryResultsRenderer tests', () => { - it('fully populated task', async () => { + async function verifyRenderedTasksHTML(allTasks: Task[]) { const renderer = new QueryResultsRenderer( 'block-language-tasks', '', new TasksFile('query.md'), () => Promise.resolve(), null, + mockHTMLRenderer, ); - const allTasks = [TaskBuilder.createFullyPopulatedTask()]; const queryRendererParameters = { allTasks, allMarkdownFiles: [], @@ -43,5 +47,15 @@ describe('QueryResultsRenderer tests', () => { const prettyHTML = prettifyHTML(container.outerHTML); verifyWithFileExtension(prettyHTML, 'html'); + } + + it('fully populated task', async () => { + const allTasks = [TaskBuilder.createFullyPopulatedTask()]; + await verifyRenderedTasksHTML(allTasks); + }); + + it('parent-child items', async () => { + const allTasks = readTasksFromSimulatedFile(inheritance_rendering_sample); + await verifyRenderedTasksHTML(allTasks); }); }); diff --git a/tests/Renderer/RenderingTestHelpers.ts b/tests/Renderer/RenderingTestHelpers.ts new file mode 100644 index 0000000000..25d6df40cd --- /dev/null +++ b/tests/Renderer/RenderingTestHelpers.ts @@ -0,0 +1,11 @@ +export const mockHTMLRenderer = async (text: string, element: HTMLSpanElement, _path: string) => { + // Contrary to the default mockTextRenderer(), + // instead of the rendered HTMLSpanElement.innerText, + // we need the plain HTML here like in TaskLineRenderer.renderComponentText(), + // to ensure that description and tags are retained. + element.innerHTML = text; +}; + +export const mockTextRenderer = async (text: string, element: HTMLSpanElement, _path: string) => { + element.innerText = text; +}; diff --git a/tests/Renderer/TaskLineRenderer.test.ts b/tests/Renderer/TaskLineRenderer.test.ts index 85824acd05..acbdaf2929 100644 --- a/tests/Renderer/TaskLineRenderer.test.ts +++ b/tests/Renderer/TaskLineRenderer.test.ts @@ -17,6 +17,7 @@ import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers'; import { prettifyHTML } from '../TestingTools/HTMLHelpers'; import { TaskBuilder } from '../TestingTools/TaskBuilder'; import { fromLine } from '../TestingTools/TestHelpers'; +import { mockHTMLRenderer, mockTextRenderer } from './RenderingTestHelpers'; jest.mock('obsidian'); window.moment = moment; @@ -48,18 +49,6 @@ async function renderListItem( return await taskLineRenderer.renderTaskLine(task, 0); } -const mockTextRenderer = async (text: string, element: HTMLSpanElement, _path: string) => { - element.innerText = text; -}; - -const mockHTMLRenderer = async (text: string, element: HTMLSpanElement, _path: string) => { - // Contrary to the default mockTextRenderer(), - // instead of the rendered HTMLSpanElement.innerText, - // we need the plain HTML here like in TaskLineRenderer.renderComponentText(), - // to ensure that description and tags are retained. - element.innerHTML = text; -}; - function getTextSpan(listItem: HTMLElement) { return listItem.children[1] as HTMLSpanElement; }