Fix subtask completion auto-completing parent task (#792)

This commit is contained in:
Chris 2026-01-23 20:04:58 +02:00 committed by GitHub
parent 5dbf12be98
commit d2a9a6dc5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 30 additions and 59 deletions

View file

@ -128,25 +128,7 @@ async function handleParentChildOnStatusChange(
task.Subtasks = directSubtasksQuery;
}
if (task.parent_task_id) {
if (newStatus === Task.STATUS.DONE || newStatus === 'done') {
const parentUpdated = await checkAndUpdateParentTaskCompletion(
task.parent_task_id,
userId
);
if (parentUpdated) {
parentChildLogicExecuted = true;
}
} else if (oldStatus === Task.STATUS.DONE || oldStatus === 'done') {
const parentUpdated = await undoneParentTaskIfNeeded(
task.parent_task_id,
userId
);
if (parentUpdated) {
parentChildLogicExecuted = true;
}
}
} else if (task.Subtasks && task.Subtasks.length > 0) {
if (task.Subtasks && task.Subtasks.length > 0) {
if (newStatus === Task.STATUS.DONE) {
const subtasksUpdated = await completeAllSubtasks(task.id, userId);
if (subtasksUpdated) {

View file

@ -140,8 +140,8 @@ describe('Subtasks Completion Logic Integration', () => {
});
});
describe('Subtask Completion Affects Parent Task', () => {
it('should complete parent task when all subtasks are completed', async () => {
describe('Subtask Completion Does Not Affect Parent Task', () => {
it('should NOT auto-complete parent task when all subtasks are completed', async () => {
// Create parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -179,13 +179,13 @@ describe('Subtasks Completion Logic Integration', () => {
const res2 = await toggleTaskCompletion(subtask2.id);
expect(res2.status).toBe(200);
// Parent should now be completed
// Parent should still be incomplete - user must manually complete it
updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.completed_at).not.toBeNull();
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
expect(updatedParent.completed_at).toBeNull();
});
it('should undone parent task when any subtask is undone', async () => {
it('should NOT auto-undone parent task when a subtask is undone', async () => {
// Create completed parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -218,10 +218,10 @@ describe('Subtasks Completion Logic Integration', () => {
let res = await toggleTaskCompletion(subtask1.id);
expect(res.status).toBe(200);
// Parent should be undone
// Parent should remain completed - user must manually undone it
const updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
expect(updatedParent.completed_at).toBeNull();
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.completed_at).not.toBeNull();
// Other subtask should remain done
const updatedSubtask2 = await Task.findByPk(subtask2.id);
@ -249,7 +249,7 @@ describe('Subtasks Completion Logic Integration', () => {
});
describe('Complex Completion Scenarios', () => {
it('should handle mixed subtask states correctly', async () => {
it('should handle mixed subtask states correctly without affecting parent', async () => {
// Create parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -296,12 +296,12 @@ describe('Subtasks Completion Logic Integration', () => {
const res2 = await toggleTaskCompletion(subtask3.id);
expect(res2.status).toBe(200);
// Parent should now be completed
// Parent should still be incomplete - user must manually complete it
updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
});
it('should handle rapid completion toggles correctly', async () => {
it('should handle rapid completion toggles without affecting parent', async () => {
// Create parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -329,20 +329,9 @@ describe('Subtasks Completion Logic Integration', () => {
const res3 = await toggleTaskCompletion(subtask.id);
expect(res3.status).toBe(200);
// Final state should be consistent
const updatedSubtask = await Task.findByPk(subtask.id);
// Parent should remain unchanged regardless of subtask toggles
const updatedParent = await Task.findByPk(parentTask.id);
// Final state should be consistent
expect(
updatedSubtask.status === Task.STATUS.DONE
? updatedParent.status
: updatedParent.status
).toBe(
updatedSubtask.status === Task.STATUS.DONE
? Task.STATUS.DONE
: Task.STATUS.NOT_STARTED
);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
});
});
@ -377,7 +366,7 @@ describe('Subtasks Completion Logic Integration', () => {
expect(deletedSubtask).toBeNull();
});
it('should handle concurrent completion updates', async () => {
it('should handle concurrent completion updates without affecting parent', async () => {
// Create parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -414,9 +403,9 @@ describe('Subtasks Completion Logic Integration', () => {
expect(result.status).toBe(200);
});
// Final state should be consistent
// Parent should remain unchanged - user must manually complete it
const updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
});
it('should handle deleted parent task gracefully (FK constraints disabled)', async () => {
@ -451,7 +440,7 @@ describe('Subtasks Completion Logic Integration', () => {
});
describe('Performance Considerations', () => {
it('should handle many subtasks efficiently', async () => {
it('should handle many subtasks efficiently without affecting parent', async () => {
// Create parent task
const parentTask = await Task.create({
name: 'Parent Task',
@ -488,9 +477,9 @@ describe('Subtasks Completion Logic Integration', () => {
// Should complete within reasonable time (adjust threshold as needed)
expect(endTime - startTime).toBeLessThan(10000); // 10 seconds
// Parent should be completed
// Parent should remain unchanged - user must manually complete it
const updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
});
});
});

View file

@ -403,7 +403,7 @@ describe('Subtasks API', () => {
});
});
it('should complete parent when all subtasks are done', async () => {
it('should NOT auto-complete parent when all subtasks are done', async () => {
const parentTask = await Task.create({
name: 'Parent Task',
user_id: testUser.id,
@ -439,13 +439,13 @@ describe('Subtasks API', () => {
const res2 = await toggleTaskCompletion(subtask2.id);
expect(res2.status).toBe(200);
// Parent should now be completed
// Parent should still be not started - user must manually complete it
updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.completed_at).not.toBeNull();
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
expect(updatedParent.completed_at).toBeNull();
});
it('should undone parent when subtask is undone', async () => {
it('should NOT auto-undone parent when subtask is undone', async () => {
const parentTask = await Task.create({
name: 'Parent Task',
user_id: testUser.id,
@ -476,10 +476,10 @@ describe('Subtasks API', () => {
let res = await toggleTaskCompletion(subtask1.id);
expect(res.status).toBe(200);
// Parent should be undone
// Parent should remain completed - user must manually undone it
const updatedParent = await Task.findByPk(parentTask.id);
expect(updatedParent.status).toBe(Task.STATUS.NOT_STARTED);
expect(updatedParent.completed_at).toBeNull();
expect(updatedParent.status).toBe(Task.STATUS.DONE);
expect(updatedParent.completed_at).not.toBeNull();
});
});