taskgenius_taskgenius-plugin/src/__tests__/autoCompleteParent.test.ts
Quorafind f28ea8e9c0 refactor(v10): modularize entry + gantt year-view and project grouping fixes
Decompose the monolithic plugin entry into bootstrap modules and extracted
pure functions (src/bootstrap, src/modules), advance dataflow project
resolution, and reconcile the Phase 0 contract tests.

Fixes bundled in:
- gantt: fix year-zoom timeline so year/month labels render and a full year
  fits on screen. Major-label gate no longer keyed on single-day width; add
  a Year minor-label branch; formatMinorTick('Year') returns a real month
  name; lower MIN_DAY_WIDTH to 2; drop per-render logging; register the
  offscreen-indicator click handlers once to stop a listener leak.
- project: stop the Projects view fragmenting into one task per file.
  determineTgProject() gains an applyDefaultNaming option (default true,
  preserving the public API / File Source / unit tests); the dataflow cache
  and worker-sync paths pass false so they match ProjectData.worker.
- build: restore RegExpCursor in progress-bar-widget (the SearchCursor
  migration was incomplete -- SearchCursor cannot do regex search).

Pre-commit hook (npm run build) verified manually; hook could not spawn on
this Windows Git Bash setup. Tests: 1718 passing; production build green.
2026-06-28 16:09:40 +08:00

821 lines
25 KiB
TypeScript

import {
handleParentTaskUpdateTransaction,
findTaskStatusChange,
findParentTask,
areAllSiblingsCompleted,
anySiblingWithStatus,
getParentTaskStatus,
hasAnyChildTasksAtLevel,
} from "../editor-extensions/autocomplete/parent-task-updater";
import { taskStatusChangeAnnotation } from "@/editor-extensions/task-operations/status-switcher"; // Adjust the import path as necessary
import { buildIndentString } from "../utils";
import {
createMockTransaction,
createMockApp,
createMockPlugin,
createMockText,
} from "./mockUtils";
// --- Mock Setup ---
// Mock Annotation Type
// --- Tests ---
describe("autoCompleteParent Helpers", () => {
describe("findTaskStatusChange", () => {
it("should return null if doc did not change (though handleParentTaskUpdateTransaction checks this first)", () => {
const tr = createMockTransaction({ docChanged: false });
expect(findTaskStatusChange(tr)).toBeNull();
});
it("should return null if no task-related change occurred", () => {
const tr = createMockTransaction({
startStateDocContent: "Some text",
newDocContent: "Some other text",
changes: [
{
fromA: 5,
toA: 9,
fromB: 5,
toB: 10,
insertedText: "other",
},
],
});
expect(findTaskStatusChange(tr)).toBeNull();
});
it("should detect a task status change from [ ] to [x]", () => {
const tr = createMockTransaction({
startStateDocContent: "- [ ] Task 1",
newDocContent: "- [x] Task 1",
changes: [
{ fromA: 3, toA: 4, fromB: 3, toB: 4, insertedText: "x" },
],
});
const result = findTaskStatusChange(tr);
expect(result).not.toBeNull();
expect(result?.lineNumber).toBe(1);
});
it("should detect a task status change from [ ] to [/]", () => {
const tr = createMockTransaction({
startStateDocContent: " - [ ] Task 1",
newDocContent: " - [/] Task 1",
changes: [
{ fromA: 5, toA: 6, fromB: 5, toB: 6, insertedText: "/" },
],
});
const result = findTaskStatusChange(tr);
expect(result).not.toBeNull();
expect(result?.lineNumber).toBe(1);
});
it("should detect a new task added", () => {
const tr = createMockTransaction({
startStateDocContent: "Some text",
newDocContent: "Some text\n- [ ] New Task",
changes: [
{
fromA: 9,
toA: 9,
fromB: 9,
toB: 23,
insertedText: "\n- [ ] New Task",
},
],
});
const result = findTaskStatusChange(tr);
expect(result).not.toBeNull();
expect(result?.lineNumber).toBe(2); // Line number where the new task is
});
it("should detect a new task added at the beginning", () => {
const tr = createMockTransaction({
startStateDocContent: "Some text",
newDocContent: "- [ ] New Task\nSome text",
// Indices need careful calculation
changes: [
{
fromA: 0,
toA: 0,
fromB: 0,
toB: 14,
insertedText: "- [ ] New Task\n",
},
],
});
const result = findTaskStatusChange(tr);
expect(result).not.toBeNull();
expect(result?.lineNumber).toBe(1);
});
});
describe("findParentTask", () => {
const indent = buildIndentString(createMockApp());
const doc = createMockText(
"- [ ] Parent 1\n" + // 1
`${indent}- [ ] Child 1.1\n` + // 2
`${indent} - [ ] Child 1.2\n` + // 3
"- [ ] Parent 2\n" + // 4
`${indent}- [ ] Child 2.1\n` + // 5
`${indent}${indent}- [ ] Grandchild 2.1.1\n` + // 6
`${indent}- [ ] Child 2.2` // 7
);
const mockApp = createMockApp();
it("should return null for a top-level task", () => {
expect(findParentTask(doc, 1)).toBeNull();
expect(findParentTask(doc, 4)).toBeNull();
});
it("should find the parent of a child task", () => {
const parent1 = findParentTask(doc, 2);
expect(parent1).not.toBeNull();
expect(parent1?.lineNumber).toBe(1);
const parent2 = findParentTask(doc, 5);
expect(parent2).not.toBeNull();
expect(parent2?.lineNumber).toBe(4);
});
it("should find the parent of a grandchild task", () => {
const parent = findParentTask(doc, 6);
expect(parent).not.toBeNull();
expect(parent?.lineNumber).toBe(5); // Direct parent, not grandparent
});
it("should handle different indentation levels", () => {
const docWithTabs = createMockText(
"- [ ] Parent\n" +
"\t- [ ] Child with tab\n" +
"\t\t- [ ] Grandchild with tabs"
);
const parent = findParentTask(docWithTabs, 3);
expect(parent).not.toBeNull();
expect(parent?.lineNumber).toBe(2);
});
it("should handle mixed indentation", () => {
const docWithMixedIndent = createMockText(
"- [ ] Parent\n" +
" - [ ] Child with spaces\n" +
"\t- [ ] Child with tab"
);
const parent1 = findParentTask(docWithMixedIndent, 2);
expect(parent1).not.toBeNull();
expect(parent1?.lineNumber).toBe(1);
const parent2 = findParentTask(docWithMixedIndent, 3);
expect(parent2).not.toBeNull();
expect(parent2?.lineNumber).toBe(1);
});
});
describe("areAllSiblingsCompleted", () => {
const mockPlugin = createMockPlugin();
const indent = buildIndentString(createMockApp());
it("should return true if all siblings are completed", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [x] Child 2`
);
expect(areAllSiblingsCompleted(doc, 1, 0, mockPlugin)).toBe(true);
});
it("should return false if any sibling is not completed", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [ ] Child 2`
);
expect(areAllSiblingsCompleted(doc, 1, 0, mockPlugin)).toBe(false);
});
it("should return false if any sibling is in progress", () => {
const doc = createMockText(
"- [ ] Parent\n" + " - [x] Child 1\n" + " - [/] Child 2"
);
expect(areAllSiblingsCompleted(doc, 1, 0, mockPlugin)).toBe(false);
});
it("should return true if there are no siblings", () => {
const doc = createMockText("- [ ] Parent");
expect(areAllSiblingsCompleted(doc, 1, 0, mockPlugin)).toBe(false);
});
it("should ignore grandchildren", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}${indent}- [ ] Grandchild 1.1\n` + // Grandchild not completed
`${indent}- [x] Child 2`
);
expect(areAllSiblingsCompleted(doc, 1, 0, mockPlugin)).toBe(true); // Only checks Child 1 & 2
});
});
describe("anySiblingWithStatus", () => {
const mockApp = createMockApp();
const indent = buildIndentString(createMockApp());
it("should return true if any sibling has status [/]", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}- [/] Child 2`
);
expect(anySiblingWithStatus(doc, 1, 0, mockApp)).toBe(true);
});
it("should return true if any sibling has status [x]", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}- [x] Child 2`
);
expect(anySiblingWithStatus(doc, 1, 0, mockApp)).toBe(true);
});
it("should return false if all siblings are [ ]", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}- [ ] Child 2`
);
expect(anySiblingWithStatus(doc, 1, 0, mockApp)).toBe(false);
});
it("should return false if there are no siblings", () => {
const doc = createMockText("- [ ] Parent");
expect(anySiblingWithStatus(doc, 1, 0, mockApp)).toBe(false);
});
it("should ignore grandchildren", () => {
const doc = createMockText(
"- [ ] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}${indent}- [/] Grandchild 1.1\n` + // Grandchild has status
`${indent}- [ ] Child 2`
);
expect(anySiblingWithStatus(doc, 1, 0, mockApp)).toBe(false); // Checks only Child 1 & 2
});
});
describe("getParentTaskStatus", () => {
it("should return the status character for [ ]", () => {
const doc = createMockText("- [ ] Parent Task");
expect(getParentTaskStatus(doc, 1)).toBe(" ");
});
it("should return the status character for [x]", () => {
const doc = createMockText(" - [x] Parent Task");
expect(getParentTaskStatus(doc, 1)).toBe("x");
});
it("should return the status character for [/]", () => {
const doc = createMockText(" - [/] Parent Task");
expect(getParentTaskStatus(doc, 1)).toBe("/");
});
it("should return empty string if not a task", () => {
const doc = createMockText("Just text");
expect(getParentTaskStatus(doc, 1)).toBe("");
});
});
});
describe("handleParentTaskUpdateTransaction (Integration)", () => {
const mockApp = createMockApp();
it("should return original transaction if docChanged is false", () => {
const mockPlugin = createMockPlugin();
const tr = createMockTransaction({ docChanged: false });
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr);
});
it("should return original transaction for paste events", () => {
const mockPlugin = createMockPlugin();
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n - [ ] Child",
newDocContent: "- [ ] Parent\n - [x] Child",
changes: [
{ fromA: 18, toA: 19, fromB: 18, toB: 19, insertedText: "x" },
],
isUserEvent: "input.paste",
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr);
});
it("should return original transaction if no task status change detected", () => {
const mockPlugin = createMockPlugin();
const tr = createMockTransaction({
startStateDocContent: "Hello",
newDocContent: "Hello World",
changes: [
{ fromA: 5, toA: 5, fromB: 5, toB: 11, insertedText: " World" },
],
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr);
});
it("should return original transaction if changed task has no parent", () => {
const mockPlugin = createMockPlugin();
const tr = createMockTransaction({
startStateDocContent: "- [ ] Task",
newDocContent: "- [x] Task",
changes: [
{ fromA: 3, toA: 4, fromB: 3, toB: 4, insertedText: "x" },
],
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr);
});
it("should complete parent when last child is completed", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n" + `${indent}- [ ] Child`,
newDocContent: "- [ ] Parent\n" + `${indent}- [x] Child`, // Doc content *before* parent update
changes: [
{ fromA: 18, toA: 19, fromB: 18, toB: 19, insertedText: "x" },
], // Change in child
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).not.toBe(tr);
expect(result.changes).toHaveLength(2); // Original change + parent change
// @ts-ignore - Accessing internal structure for test validation
const parentChange = result.changes[1];
expect(parentChange.from).toBe(3); // Position of space in parent: '- [ ]'
expect(parentChange.to).toBe(4);
expect(parentChange.insert).toBe("x");
expect(result.annotations).toEqual([
taskStatusChangeAnnotation.of("autoCompleteParent.DONE"),
]);
});
it("should NOT complete parent if it is already complete", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent:
"- [x] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [ ] Child 2`,
newDocContent:
"- [x] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [x] Child 2`, // Doc content *before* potential update
changes: [
{ fromA: 18, toA: 19, fromB: 18, toB: 19, insertedText: "x" },
], // Change in Child 1
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// Parent is already 'x', no change should happen even if Child 1 is completed
expect(result).toBe(tr);
});
it("should mark parent as in progress when a child is unchecked (if setting enabled)", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: true,
taskStatuses: {
inProgress: "/",
completed: "x",
abandoned: "-",
planned: "?",
notStarted: " ",
},
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [x] Parent\n" + `${indent}- [x] Child`,
newDocContent: "- [x] Parent\n" + `${indent}- [ ] Child`, // Doc content *before* parent update
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: " " },
], // Child uncompleted - position adjusted for 4-space indent
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).not.toBe(tr);
expect(result.changes).toHaveLength(2);
// @ts-ignore
const parentChange = result.changes[1];
expect(parentChange.from).toBe(3); // Position of 'x' in parent: '- [x]'
expect(parentChange.to).toBe(4);
expect(parentChange.insert).toBe("/"); // Should be in progress marker
expect(result.annotations).toEqual([
taskStatusChangeAnnotation.of(
"autoCompleteParent.IN_PROGRESS"
),
]);
});
it("should NOT mark parent as in progress when a child is unchecked (if setting disabled)", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: false,
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [x] Parent\n" + `${indent}- [x] Child`,
newDocContent: "- [x] Parent\n" + `${indent}- [ ] Child`, // Doc content *before* parent update
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: " " },
], // Child uncompleted - position adjusted for 4-space indent
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr); // No change expected
});
it("should mark parent as in progress when first child gets a status (if setting enabled)", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: true,
taskStatuses: {
inProgress: "/",
completed: "x",
abandoned: "-",
planned: "?",
notStarted: " ",
},
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n" + `${indent}- [ ] Child`,
newDocContent: "- [ ] Parent\n" + `${indent}- [/] Child`,
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "/" },
], // Child marked in progress - position adjusted for 4-space indent
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).not.toBe(tr);
expect(result.changes).toHaveLength(2);
// @ts-ignore
const parentChange = result.changes[1];
expect(parentChange.from).toBe(3); // Position of ' ' in parent: '- [ ]'
expect(parentChange.to).toBe(4);
expect(parentChange.insert).toBe("/");
expect(result.annotations).toEqual([
taskStatusChangeAnnotation.of(
"autoCompleteParent.IN_PROGRESS"
),
]);
});
it("should NOT mark parent as in progress when first child gets a status (if setting disabled)", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: false,
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n" + `${indent}- [ ] Child`,
newDocContent: "- [ ] Parent\n" + `${indent}- [/] Child`,
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "/" },
], // Child marked in progress - position adjusted for 4-space indent
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).toBe(tr);
});
it("should NOT mark parent as in progress if parent already has a status", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: true,
taskStatuses: {
inProgress: "/",
completed: "x",
abandoned: "-",
planned: "?",
notStarted: " ",
},
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent:
"- [/] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}- [ ] Child 2`,
newDocContent:
"- [/] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [ ] Child 2`,
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "x" },
], // Child 1 completed - position adjusted for 4-space indent
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// Parent already '/' and markParentInProgress only triggers if parent is ' ', so no change.
expect(result).toBe(tr);
});
it("should ignore changes triggered by its own annotation (complete)", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n" + `${indent}- [ ] Child`,
newDocContent: "- [ ] Parent\n" + `${indent}- [x] Child`, // Doc content *before* potential parent update
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "x" },
], // Change in child - position adjusted for 4-space indent
annotations: [
taskStatusChangeAnnotation.of(
"autoCompleteParent.SOME_OTHER_ACTION"
),
], // Simulate annotation present
});
// Add a specific annotation value that includes 'autoCompleteParent'
// @ts-ignore
tr.annotation = jest.fn((type) => {
if (type === taskStatusChangeAnnotation) {
return "autoCompleteParent.DONE"; // Simulate this transaction was caused by auto-complete
}
return undefined;
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// Even though child is completed, the annotation should prevent parent completion
expect(result).toBe(tr);
});
it("should ignore changes triggered by its own annotation (in progress)", () => {
const indent = buildIndentString(createMockApp());
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: true,
taskStatuses: {
inProgress: "/",
completed: "x",
abandoned: "-",
planned: "?",
notStarted: " ",
},
});
const tr = createMockTransaction({
startStateDocContent: "- [ ] Parent\n" + `${indent}- [ ] Child`,
newDocContent: "- [ ] Parent\n" + `${indent}- [/] Child`, // Doc content *before* potential parent update
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "/" },
], // Child marked in progress
annotations: [
taskStatusChangeAnnotation.of(
"autoCompleteParent.SOME_OTHER_ACTION"
),
], // Simulate annotation present
});
// @ts-ignore
tr.annotation = jest.fn((type) => {
if (type === taskStatusChangeAnnotation) {
return "autoCompleteParent.IN_PROGRESS"; // Simulate this transaction was caused by auto-complete
}
return undefined;
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// Even though child got status, the annotation should prevent parent update
expect(result).toBe(tr);
});
it("should mark parent as in progress when one child is completed but others remain incomplete", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
markParentInProgressWhenPartiallyComplete: true,
taskStatuses: {
inProgress: "/",
completed: "x",
abandoned: "-",
planned: "?",
notStarted: " ",
},
});
const indent = buildIndentString(createMockApp());
const tr = createMockTransaction({
startStateDocContent:
"- [ ] Parent\n" +
`${indent}- [ ] Child 1\n` +
`${indent}- [ ] Child 2`,
newDocContent:
"- [ ] Parent\n" +
`${indent}- [x] Child 1\n` +
`${indent}- [ ] Child 2`, // Doc content *before* parent update
changes: [
{ fromA: 21, toA: 22, fromB: 21, toB: 22, insertedText: "x" },
], // Change in Child 1
});
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
expect(result).not.toBe(tr);
expect(result.changes).toHaveLength(2);
// @ts-ignore
const parentChange = result.changes[1];
expect(parentChange.from).toBe(3); // Position of ' ' in parent: '- [ ]'
expect(parentChange.to).toBe(4);
expect(parentChange.insert).toBe("/"); // Should be in progress marker
expect(result.annotations).toEqual([
taskStatusChangeAnnotation.of(
"autoCompleteParent.IN_PROGRESS"
),
]);
});
it("should NOT change parent task status when deleting a dash with backspace", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
}); // Defaults: ' ', '/', 'x'
// Set up a complete task and an incomplete task line below (just a dash)
const startContent = "- [ ] Task 1\n- ";
// After pressing Backspace to delete the dash on the second line, the first line task should not become [/]
const newContent = "- [ ] Task 1";
// Simulate pressing Backspace to delete the dash at the beginning of the second line
const tr = createMockTransaction({
startStateDocContent: startContent,
newDocContent: newContent,
changes: [
{
fromA: 15, // Position of the dash on the second line
toA: 15, // End position of the dash
fromB: 12, // Same position in the new content
toB: 12, // Position after deletion
insertedText: "", // Delete operation, no inserted text
},
],
docChanged: true,
});
// The function should detect this is a deletion operation, not a task status change
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// Expect the original transaction to be returned (no modification)
expect(result).toBe(tr);
expect(result.changes).toEqual(tr.changes);
expect(result.selection).toEqual(tr.selection);
});
it("should NOT change parent task status when deleting an indented dash", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
}); // Defaults: ' ', '/', 'x'
const indent = buildIndentString(createMockApp());
// Test with indentation
const startContentIndented = "- [ ] Task 1\n" + indent + "- ";
const newContentIndented = "- [ ] Task 1\n" + indent; // Delete the dash after indentation
const trIndented = createMockTransaction({
startStateDocContent: startContentIndented,
newDocContent: newContentIndented,
changes: [
{
fromA: 15, // Position of the dash after indentation
toA: 16, // End position of the dash
fromB: 15, // Same position in the new content
toB: 14, // Position after deletion
insertedText: "", // Delete operation, no inserted text
},
],
docChanged: true,
});
const resultIndented = handleParentTaskUpdateTransaction(
trIndented,
mockApp,
mockPlugin
);
// The function should not change parent task status when deleting a dash
expect(resultIndented).toBe(trIndented);
expect((resultIndented as any).changes).toEqual(
(trIndented as any).changes
);
expect(resultIndented.selection).toEqual(trIndented.selection);
// Verify no parent task status change annotation was added
expect(resultIndented.annotations).not.toEqual(
taskStatusChangeAnnotation.of(
"autoCompleteParent.COMPLETED"
)
);
expect((resultIndented as any).annotations).not.toEqual(
taskStatusChangeAnnotation.of(
"autoCompleteParent.IN_PROGRESS"
)
);
});
it("should prevent accidental parent status changes when deleting a dash and newline marker", () => {
const mockPlugin = createMockPlugin({
autoCompleteParent: true,
}); // Defaults: ' ', '/', 'x'
// Test erroneous behavior: deleting a dash incorrectly changes the status of the previous task
const startContent = "- [ ] Task 1\n- ";
const newContent = "- [ ] Task 1"; // Status incorrectly changed
const tr = createMockTransaction({
startStateDocContent: startContent,
newDocContent: newContent,
changes: [
{
fromA: 15, // Position of the dash on the second line
toA: 15, // End position of the dash
fromB: 12, // Position of the task status
toB: 12, // End position of the status
insertedText: "", // Incorrectly inserted new status
},
],
docChanged: true,
});
// Even when receiving such a transaction, the function should detect this is not a valid status change
const result = handleParentTaskUpdateTransaction(
tr,
mockApp,
mockPlugin
);
// The function should identify and prevent such accidental parent status changes
expect(result).toBe(tr);
expect(result.changes).toEqual(tr.changes);
});
});
// Add more tests for edge cases, different indentation levels, workflow interactions etc.