Skip to content

Commit 75893b5

Browse files
authored
Merge pull request #3126 from ilandikov/fix-list-item-rerendering
fix: rerender search results when child list items are edited
2 parents b0b1320 + 208599b commit 75893b5

File tree

6 files changed

+174
-41
lines changed

6 files changed

+174
-41
lines changed

src/Obsidian/Cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ export class Cache {
377377

378378
// If there are no changes in any of the tasks, there's
379379
// nothing to do, so just return.
380-
if (Task.tasksListsIdentical(oldTasks, newTasks)) {
380+
if (ListItem.listsAreIdentical(oldTasks, newTasks)) {
381381
// This code kept for now, to allow for debugging during development.
382382
// It is too verbose to release to users.
383383
// if (this.getState() == State.Warm) {

src/Task/ListItem.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,46 @@ export class ListItem {
4848
get isRoot(): boolean {
4949
return this.parent === null;
5050
}
51+
52+
/**
53+
* Compare all the fields in another ListItem, to detect any differences from this one.
54+
*
55+
* If any field is different in any way, it will return false.
56+
*
57+
* @note Use {@link Task.identicalTo} to compare {@link Task} objects.
58+
*
59+
* @param other - if this is in fact a {@link Task}, the result of false.
60+
*/
61+
identicalTo(other: ListItem) {
62+
if (this.constructor.name !== other.constructor.name) {
63+
return false;
64+
}
65+
66+
if (this.originalMarkdown !== other.originalMarkdown) {
67+
return false;
68+
}
69+
70+
return ListItem.listsAreIdentical(this.children, other.children);
71+
}
72+
73+
/**
74+
* Compare two lists of ListItem objects, and report whether their
75+
* contents, including any children, are identical and in the same order.
76+
*
77+
* This can be useful for optimising code if it is guaranteed that
78+
* there are no possible differences in the tasks in a file
79+
* after an edit, for example.
80+
*
81+
* If any field is different in any task or list item, it will return false.
82+
*
83+
* @param list1
84+
* @param list2
85+
*/
86+
static listsAreIdentical(list1: ListItem[], list2: ListItem[]): boolean {
87+
if (list1.length !== list2.length) {
88+
return false;
89+
}
90+
91+
return list1.every((item, index) => item.identicalTo(list2[index]));
92+
}
5193
}

src/Task/Task.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -784,26 +784,6 @@ export class Task extends ListItem {
784784
return linkText;
785785
}
786786

787-
/**
788-
* Compare two lists of Task objects, and report whether their
789-
* tasks are identical in the same order.
790-
*
791-
* This can be useful for optimising code if it is guaranteed that
792-
* there are no possible differences in the tasks in a file
793-
* after an edit, for example.
794-
*
795-
* If any field is different in any task, it will return false.
796-
*
797-
* @param oldTasks
798-
* @param newTasks
799-
*/
800-
static tasksListsIdentical(oldTasks: Task[], newTasks: Task[]): boolean {
801-
if (oldTasks.length !== newTasks.length) {
802-
return false;
803-
}
804-
return oldTasks.every((oldTask, index) => oldTask.identicalTo(newTasks[index]));
805-
}
806-
807787
/**
808788
* Compare all the fields in another Task, to detect any differences from this one.
809789
*
@@ -816,6 +796,11 @@ export class Task extends ListItem {
816796
* @param other
817797
*/
818798
public identicalTo(other: Task) {
799+
// First compare child Task and ListItem objects, and any other data in ListItem:
800+
if (!super.identicalTo(other)) {
801+
return false;
802+
}
803+
819804
// NEW_TASK_FIELD_EDIT_REQUIRED
820805

821806
// Based on ideas from koala. AquaCat and javalent in Discord:

tests/Task/ListItem.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { TasksFile } from '../../src/Scripting/TasksFile';
66
import { Task } from '../../src/Task/Task';
77
import { TaskLocation } from '../../src/Task/TaskLocation';
88
import { ListItem } from '../../src/Task/ListItem';
9+
import { TaskBuilder } from '../TestingTools/TaskBuilder';
10+
import { fromLine } from '../TestingTools/TestHelpers';
11+
import { createChildListItem } from './ListItemHelpers';
912

1013
window.moment = moment;
1114

@@ -96,3 +99,107 @@ describe('list item tests', () => {
9699
}
97100
});
98101
});
102+
103+
describe('identicalTo', () => {
104+
it('should test same markdown', () => {
105+
const listItem1 = new ListItem('- same description', null);
106+
const listItem2 = new ListItem('- same description', null);
107+
expect(listItem1.identicalTo(listItem2)).toEqual(true);
108+
});
109+
110+
it('should test different markdown', () => {
111+
const listItem1 = new ListItem('- description', null);
112+
const listItem2 = new ListItem('- description two', null);
113+
expect(listItem1.identicalTo(listItem2)).toEqual(false);
114+
});
115+
116+
it('should recognise list items with different number of children', () => {
117+
const item1 = new ListItem('- item', null);
118+
createChildListItem('- child of item1', item1);
119+
120+
const item2 = new ListItem('- item', null);
121+
122+
expect(item2.identicalTo(item1)).toEqual(false);
123+
});
124+
125+
it('should recognise list items with different children', () => {
126+
const item1 = new ListItem('- item', null);
127+
createChildListItem('- child of item1', item1);
128+
129+
const item2 = new ListItem('- item', null);
130+
createChildListItem('- child of item2', item2);
131+
132+
expect(item2.identicalTo(item1)).toEqual(false);
133+
});
134+
135+
it('should recognise ListItem and Task as different', () => {
136+
const listItem = new ListItem('- [ ] description', null);
137+
const task = fromLine({ line: '- [ ] description' });
138+
139+
expect(listItem.identicalTo(task)).toEqual(false);
140+
});
141+
});
142+
143+
describe('checking if list item lists are identical', () => {
144+
it('should treat empty lists as identical', () => {
145+
const list1: ListItem[] = [];
146+
const list2: ListItem[] = [];
147+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(true);
148+
});
149+
150+
it('should treat different sized lists as different', () => {
151+
const list1: ListItem[] = [];
152+
const list2: ListItem[] = [new ListItem('- x', null)];
153+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(false);
154+
});
155+
156+
it('should detect matching list items as same', () => {
157+
const list1: ListItem[] = [new ListItem('- 1', null)];
158+
const list2: ListItem[] = [new ListItem('- 1', null)];
159+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(true);
160+
});
161+
162+
it('- should detect non-matching list items as different', () => {
163+
const list1: ListItem[] = [new ListItem('- 1', null)];
164+
const list2: ListItem[] = [new ListItem('- 2', null)];
165+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(false);
166+
});
167+
});
168+
169+
describe('checking if task lists are identical', () => {
170+
it('should treat empty lists as identical', () => {
171+
const list1: Task[] = [];
172+
const list2: Task[] = [];
173+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(true);
174+
});
175+
176+
it('should treat different sized lists as different', () => {
177+
const list1: Task[] = [];
178+
const list2: Task[] = [new TaskBuilder().build()];
179+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(false);
180+
});
181+
182+
it('should detect matching tasks as same', () => {
183+
const list1: Task[] = [new TaskBuilder().description('1').build()];
184+
const list2: Task[] = [new TaskBuilder().description('1').build()];
185+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(true);
186+
});
187+
188+
it('should detect non-matching tasks as different', () => {
189+
const list1: Task[] = [new TaskBuilder().description('1').build()];
190+
const list2: Task[] = [new TaskBuilder().description('2').build()];
191+
expect(ListItem.listsAreIdentical(list1, list2)).toBe(false);
192+
});
193+
});
194+
195+
describe('checking if mixed lists are identical', () => {
196+
it('should recognise mixed lists as unequal', () => {
197+
const list1 = [new ListItem('- [ ] description', null)];
198+
const list2 = [fromLine({ line: '- [ ] description' })];
199+
200+
expect(ListItem.listsAreIdentical(list1, list1)).toEqual(true);
201+
expect(ListItem.listsAreIdentical(list1, list2)).toEqual(false);
202+
expect(ListItem.listsAreIdentical(list2, list1)).toEqual(false);
203+
expect(ListItem.listsAreIdentical(list2, list2)).toEqual(true);
204+
});
205+
});

tests/Task/ListItemHelpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ListItem } from '../../src/Task/ListItem';
2+
3+
export function createChildListItem(originalMarkdown: string, parent: ListItem) {
4+
// This exists purely to silence WebStorm about typescript:S1848
5+
// See https://sonarcloud.io/organizations/obsidian-tasks-group/rules?open=typescript%3AS1848&rule_key=typescript%3AS1848
6+
new ListItem(originalMarkdown, parent);
7+
}

tests/Task/Task.test.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { TasksDate } from '../../src/DateTime/TasksDate';
2222
import { example_kanban } from '../Obsidian/__test_data__/example_kanban';
2323
import { jason_properties } from '../Obsidian/__test_data__/jason_properties';
2424
import { OnCompletion } from '../../src/Task/OnCompletion';
25+
import { createChildListItem } from './ListItemHelpers';
2526

2627
window.moment = moment;
2728

@@ -1730,30 +1731,21 @@ describe('identicalTo', () => {
17301731
expect(task2.status.identicalTo(task1.status)).toEqual(true);
17311732
expect(task2.identicalTo(task1)).toEqual(true);
17321733
});
1733-
});
17341734

1735-
describe('checking if task lists are identical', () => {
1736-
it('should treat empty lists as identical', () => {
1737-
const list1: Task[] = [];
1738-
const list2: Task[] = [];
1739-
expect(Task.tasksListsIdentical(list1, list2)).toBe(true);
1740-
});
1735+
it('should recognise different numbers of child items', () => {
1736+
const task1 = new TaskBuilder().build();
1737+
const task2 = new TaskBuilder().build();
1738+
createChildListItem('- child of task2', task2);
17411739

1742-
it('should treat different sized lists as different', () => {
1743-
const list1: Task[] = [];
1744-
const list2: Task[] = [new TaskBuilder().build()];
1745-
expect(Task.tasksListsIdentical(list1, list2)).toBe(false);
1740+
expect(task2.identicalTo(task1)).toEqual(false);
17461741
});
17471742

1748-
it('should detect matching tasks as same', () => {
1749-
const list1: Task[] = [new TaskBuilder().description('1').build()];
1750-
const list2: Task[] = [new TaskBuilder().description('1').build()];
1751-
expect(Task.tasksListsIdentical(list1, list2)).toBe(true);
1752-
});
1743+
it('should recognise different description in child list items', () => {
1744+
const task1 = new TaskBuilder().build();
1745+
const task2 = new TaskBuilder().build();
1746+
createChildListItem('- child of task1', task1);
1747+
createChildListItem('- child of task2', task2);
17531748

1754-
it('should detect non-matching tasks as different', () => {
1755-
const list1: Task[] = [new TaskBuilder().description('1').build()];
1756-
const list2: Task[] = [new TaskBuilder().description('2').build()];
1757-
expect(Task.tasksListsIdentical(list1, list2)).toBe(false);
1749+
expect(task2.identicalTo(task1)).toEqual(false);
17581750
});
17591751
});

0 commit comments

Comments
 (0)