Skip to content

Commit 8bf064c

Browse files
committed
fix: tree view in projects' view should not show multi instance of children tasks
1 parent e3f187f commit 8bf064c

6 files changed

Lines changed: 489 additions & 205 deletions

File tree

src/__tests__/edge-case-tests.js

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* Edge case tests for the tree view fix
3+
* Tests various boundary conditions to ensure robustness
4+
*/
5+
6+
// Test the fixed logic with various edge cases
7+
function testTreeViewLogic(sectionTasks, allTasksMap) {
8+
const sectionTaskIds = new Set(sectionTasks.map(t => t.id));
9+
10+
// Helper function to mark subtree as processed
11+
const markSubtreeAsProcessed = (rootTask, sectionTaskIds, processedTaskIds) => {
12+
if (sectionTaskIds.has(rootTask.id)) {
13+
processedTaskIds.add(rootTask.id);
14+
}
15+
16+
if (rootTask.metadata.children) {
17+
rootTask.metadata.children.forEach(childId => {
18+
const childTask = allTasksMap.get(childId);
19+
if (childTask) {
20+
markSubtreeAsProcessed(childTask, sectionTaskIds, processedTaskIds);
21+
}
22+
});
23+
}
24+
};
25+
26+
// Identify true root tasks to avoid duplicate rendering
27+
const rootTasksToRender = [];
28+
const processedTaskIds = new Set();
29+
30+
for (const task of sectionTasks) {
31+
// Skip already processed tasks
32+
if (processedTaskIds.has(task.id)) {
33+
continue;
34+
}
35+
36+
// Check if this is a root task (no parent or parent not in current section)
37+
if (!task.metadata.parent || !sectionTaskIds.has(task.metadata.parent)) {
38+
// This is a root task
39+
let actualRoot = task;
40+
41+
// If has parent but parent not in current section, find the complete root
42+
if (task.metadata.parent) {
43+
let currentTask = task;
44+
while (currentTask.metadata.parent && !sectionTaskIds.has(currentTask.metadata.parent)) {
45+
const parentTask = allTasksMap.get(currentTask.metadata.parent);
46+
if (!parentTask) {
47+
console.warn(`Parent task ${currentTask.metadata.parent} not found in allTasksMap.`);
48+
break;
49+
}
50+
actualRoot = parentTask;
51+
currentTask = parentTask;
52+
}
53+
}
54+
55+
// Add root task to render list if not already added
56+
if (!rootTasksToRender.some(t => t.id === actualRoot.id)) {
57+
rootTasksToRender.push(actualRoot);
58+
}
59+
60+
// Mark entire subtree as processed to avoid duplicate rendering
61+
markSubtreeAsProcessed(actualRoot, sectionTaskIds, processedTaskIds);
62+
}
63+
}
64+
65+
return rootTasksToRender;
66+
}
67+
68+
// Edge Case 1: Empty task list
69+
console.log("=== Edge Case 1: Empty Task List ===");
70+
const emptyTasks = [];
71+
const emptyMap = new Map();
72+
const result1 = testTreeViewLogic(emptyTasks, emptyMap);
73+
console.log("Result:", result1);
74+
console.log("Expected: Empty array");
75+
console.log("Test:", result1.length === 0 ? "PASS" : "FAIL");
76+
77+
// Edge Case 2: Only child tasks (parent not in section)
78+
console.log("\n=== Edge Case 2: Only Child Tasks ===");
79+
const childOnlyTasks = [
80+
{
81+
id: "child-1",
82+
content: "Child 1",
83+
metadata: { parent: "external-parent", children: [], project: "test" },
84+
line: 1
85+
},
86+
{
87+
id: "child-2",
88+
content: "Child 2",
89+
metadata: { parent: "external-parent", children: [], project: "test" },
90+
line: 2
91+
}
92+
];
93+
const childOnlyMap = new Map();
94+
childOnlyTasks.forEach(task => childOnlyMap.set(task.id, task));
95+
// Add external parent to map
96+
childOnlyMap.set("external-parent", {
97+
id: "external-parent",
98+
content: "External Parent",
99+
metadata: { parent: null, children: ["child-1", "child-2"], project: "other" },
100+
line: 0
101+
});
102+
103+
const result2 = testTreeViewLogic(childOnlyTasks, childOnlyMap);
104+
console.log("Result:", result2.map(t => ({ id: t.id, content: t.content })));
105+
console.log("Expected: External parent should be root");
106+
console.log("Test:", result2.length === 1 && result2[0].id === "external-parent" ? "PASS" : "FAIL");
107+
108+
// Edge Case 3: Only parent tasks (no children in section)
109+
console.log("\n=== Edge Case 3: Only Parent Tasks ===");
110+
const parentOnlyTasks = [
111+
{
112+
id: "parent-1",
113+
content: "Parent 1",
114+
metadata: { parent: null, children: ["external-child-1"], project: "test" },
115+
line: 1
116+
},
117+
{
118+
id: "parent-2",
119+
content: "Parent 2",
120+
metadata: { parent: null, children: ["external-child-2"], project: "test" },
121+
line: 2
122+
}
123+
];
124+
const parentOnlyMap = new Map();
125+
parentOnlyTasks.forEach(task => parentOnlyMap.set(task.id, task));
126+
127+
const result3 = testTreeViewLogic(parentOnlyTasks, parentOnlyMap);
128+
console.log("Result:", result3.map(t => ({ id: t.id, content: t.content })));
129+
console.log("Expected: Both parents should be roots");
130+
console.log("Test:", result3.length === 2 ? "PASS" : "FAIL");
131+
132+
// Edge Case 4: Cross-project hierarchy
133+
console.log("\n=== Edge Case 4: Cross-Project Hierarchy ===");
134+
const crossProjectTasks = [
135+
{
136+
id: "project-a-child",
137+
content: "Project A Child",
138+
metadata: { parent: "project-b-parent", children: [], project: "project-a" },
139+
line: 2
140+
}
141+
];
142+
const crossProjectMap = new Map();
143+
crossProjectTasks.forEach(task => crossProjectMap.set(task.id, task));
144+
crossProjectMap.set("project-b-parent", {
145+
id: "project-b-parent",
146+
content: "Project B Parent",
147+
metadata: { parent: null, children: ["project-a-child"], project: "project-b" },
148+
line: 1
149+
});
150+
151+
const result4 = testTreeViewLogic(crossProjectTasks, crossProjectMap);
152+
console.log("Result:", result4.map(t => ({ id: t.id, content: t.content })));
153+
console.log("Expected: Should find project-b-parent as root");
154+
console.log("Test:", result4.length === 1 && result4[0].id === "project-b-parent" ? "PASS" : "FAIL");
155+
156+
// Edge Case 5: Circular reference protection
157+
console.log("\n=== Edge Case 5: Missing Parent Task ===");
158+
const missingParentTasks = [
159+
{
160+
id: "orphan-child",
161+
content: "Orphan Child",
162+
metadata: { parent: "non-existent-parent", children: [], project: "test" },
163+
line: 1
164+
}
165+
];
166+
const missingParentMap = new Map();
167+
missingParentTasks.forEach(task => missingParentMap.set(task.id, task));
168+
169+
const result5 = testTreeViewLogic(missingParentTasks, missingParentMap);
170+
console.log("Result:", result5.map(t => ({ id: t.id, content: t.content })));
171+
console.log("Expected: Should treat orphan as root task");
172+
console.log("Test:", result5.length === 1 && result5[0].id === "orphan-child" ? "PASS" : "FAIL");
173+
174+
// Edge Case 6: Deep nesting
175+
console.log("\n=== Edge Case 6: Deep Nesting ===");
176+
const deepTasks = [
177+
{
178+
id: "level-3",
179+
content: "Level 3",
180+
metadata: { parent: "level-2", children: [], project: "test" },
181+
line: 3
182+
},
183+
{
184+
id: "level-2",
185+
content: "Level 2",
186+
metadata: { parent: "level-1", children: ["level-3"], project: "test" },
187+
line: 2
188+
},
189+
{
190+
id: "level-1",
191+
content: "Level 1",
192+
metadata: { parent: null, children: ["level-2"], project: "test" },
193+
line: 1
194+
}
195+
];
196+
const deepMap = new Map();
197+
deepTasks.forEach(task => deepMap.set(task.id, task));
198+
199+
const result6 = testTreeViewLogic(deepTasks, deepMap);
200+
console.log("Result:", result6.map(t => ({ id: t.id, content: t.content })));
201+
console.log("Expected: Only level-1 should be root");
202+
console.log("Test:", result6.length === 1 && result6[0].id === "level-1" ? "PASS" : "FAIL");
203+
204+
console.log("\n=== Summary ===");
205+
const allTests = [result1.length === 0,
206+
result2.length === 1 && result2[0].id === "external-parent",
207+
result3.length === 2,
208+
result4.length === 1 && result4[0].id === "project-b-parent",
209+
result5.length === 1 && result5[0].id === "orphan-child",
210+
result6.length === 1 && result6[0].id === "level-1"];
211+
const passCount = allTests.filter(Boolean).length;
212+
console.log(`${passCount}/6 tests passed`);
213+
console.log(passCount === 6 ? "✅ All edge case tests PASS" : "❌ Some edge case tests FAIL");
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
projectName: myproject-2
3+
tags:
4+
- important
5+
- new
6+
---
7+
8+
# Test Project Tree View
9+
10+
This file is used to test the fix for duplicate task display in Project view tree mode.
11+
12+
## Test Case 1: Basic Parent-Child Tasks
13+
14+
- [ ] 测试🔽 📅 2025-06-17
15+
- [ ] 子任务1
16+
- [ ] 子任务2
17+
- [ ] 子任务3
18+
- [ ] 子任务4
19+
20+
## Test Case 2: Multiple Parent Tasks
21+
22+
- [ ] 父任务A 🔽
23+
- [ ] A的子任务1
24+
- [ ] A的子任务2
25+
26+
- [ ] 父任务B 🔽
27+
- [ ] B的子任务1
28+
- [ ] B的子任务2
29+
30+
## Test Case 3: Nested Tasks (Grandchildren)
31+
32+
- [ ] 顶级任务 🔽
33+
- [ ] 二级任务1
34+
- [ ] 三级任务1
35+
- [ ] 三级任务2
36+
- [ ] 二级任务2
37+
- [ ] 三级任务3
38+
39+
## Test Case 4: Mixed Independent and Hierarchical Tasks
40+
41+
- [ ] 独立任务1
42+
- [ ] 独立任务2
43+
44+
- [ ] 有子任务的父任务 🔽
45+
- [ ] 子任务A
46+
- [ ] 子任务B
47+
48+
- [ ] 另一个独立任务
49+
50+
## Expected Behavior
51+
52+
In Project view tree mode, each task should appear only once:
53+
- Parent tasks should be displayed as expandable items
54+
- Child tasks should only appear under their parent tasks
55+
- No task should be duplicated as both a child and an independent item
56+
57+
## Test Instructions
58+
59+
1. Open Task Genius plugin
60+
2. Navigate to Project view
61+
3. Select "myproject-2" project
62+
4. Switch to tree view mode
63+
5. Verify that:
64+
- "测试🔽" appears only once as a parent task
65+
- "子任务1-4" appear only as children of "测试🔽"
66+
- No child tasks appear as independent root tasks
67+
- All parent-child relationships are preserved

0 commit comments

Comments
 (0)