← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit f4be9944f86dcadc865e0bbabe46af46deef2460
parent 04939b02c703e7f8d94e4e6ed30951020088d1fe
Author: Michael Camilleri <[email protected]>
Date:   Wed, 18 Feb 2026 21:45:29 +0900

Improve sort ordering on uncompletion

Co-Authored-By: Codex GPT 5.3 <[email protected]>

Diffstat

MListless/Models/TaskStore.swift | 9+++++++++
MTests/Unit/TaskStoreCompletionTests.swift | 41+++++++++++++++++++++++++++++++++++++++++
MTests/Unit/TaskStoreEdgeCaseTests.swift | 21+++++++++++++++++++++

3 files changed, 71 insertions(+), 0 deletions(-)

diff --git a/Listless/Models/TaskStore.swift b/Listless/Models/TaskStore.swift

@@ -62,6 +62,15 @@ final class TaskStore {
 
     func uncomplete(taskID: UUID) {
         guard let task = findTask(id: taskID) else { return }
+        let restoredSortOrder = task.sortOrder
+        let activeTasks = fetchTasks().filter { !$0.isCompleted && $0.id != task.id }
+        let hasSortOrderConflict = activeTasks.contains { $0.sortOrder == restoredSortOrder }
+
+        if hasSortOrderConflict {
+            let maxSortOrder = activeTasks.map(\.sortOrder).max() ?? -1000
+            task.sortOrder = maxSortOrder + 1000
+        }
+
         task.isCompleted = false
         save()
     }

diff --git a/Tests/Unit/TaskStoreCompletionTests.swift b/Tests/Unit/TaskStoreCompletionTests.swift

@@ -145,4 +145,45 @@ struct TaskStoreCompletionTests {
         #expect(tasks.allSatisfy { $0.isCompleted })
         #expect(tasks.count == 5)
     }
+
+    @Test("Uncomplete restores previous sortOrder when no active conflict")
+    func uncompleteRestoresPreviousSortOrderWhenNoConflict() async throws {
+        let (store, taskIDs) = makeTestStoreWithTasks(count: 3)
+        let taskToRestoreID = taskIDs[1]
+
+        let originalSortOrder = store.fetchTasks().first { $0.id == taskToRestoreID }?.sortOrder
+        #expect(originalSortOrder != nil)
+
+        store.complete(taskID: taskToRestoreID)
+        store.uncomplete(taskID: taskToRestoreID)
+
+        let activeTasks = store.fetchTasks().filter { !$0.isCompleted }
+        let restoredTask = activeTasks.first { $0.id == taskToRestoreID }
+
+        #expect(restoredTask != nil)
+        #expect(restoredTask?.sortOrder == originalSortOrder)
+        #expect(activeTasks.count == 3)
+    }
+
+    @Test("Uncomplete appends task when restored sortOrder conflicts with active task")
+    func uncompleteAppendsWhenRestoredSortOrderConflicts() async throws {
+        let store = makeTestStore()
+        let activeTask = store.createTask(title: "Active task")
+        let completedTask = store.createTask(title: "Completed task")
+
+        store.complete(taskID: completedTask.id)
+        store.moveTask(taskID: activeTask.id, toIndex: 0)
+        completedTask.sortOrder = activeTask.sortOrder
+        store.save()
+
+        store.uncomplete(taskID: completedTask.id)
+
+        let activeTasks = store.fetchTasks().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+        let lastActiveTask = activeTasks.last
+
+        #expect(activeTasks.count == 2)
+        #expect(lastActiveTask?.id == completedTask.id)
+        #expect(lastActiveTask?.sortOrder ?? 0 > activeTask.sortOrder)
+    }
 }

diff --git a/Tests/Unit/TaskStoreEdgeCaseTests.swift b/Tests/Unit/TaskStoreEdgeCaseTests.swift

@@ -160,4 +160,25 @@ struct TaskStoreEdgeCaseTests {
         #expect(activeTasks.count == 3)
         #expect(activeTasks.contains { $0.id == taskIDs[1] })
     }
+
+    @Test("Uncomplete legacy sortOrder zero conflict appends to end")
+    func uncompleteLegacyZeroConflictAppendsToEnd() async throws {
+        let store = makeTestStore()
+        let activeTask = store.createTask(title: "Active")
+        let completedTask = store.createTask(title: "Completed")
+
+        activeTask.sortOrder = 0
+        store.complete(taskID: completedTask.id)
+        completedTask.sortOrder = 0
+        store.save()
+
+        store.uncomplete(taskID: completedTask.id)
+
+        let activeTasks = store.fetchTasks().filter { !$0.isCompleted }
+            .sorted { $0.sortOrder < $1.sortOrder }
+        #expect(activeTasks.count == 2)
+        #expect(activeTasks[0].id == activeTask.id)
+        #expect(activeTasks[1].id == completedTask.id)
+        #expect(activeTasks[1].sortOrder > activeTasks[0].sortOrder)
+    }
 }