ItemStoreCompletionTests.swift (6404B)
1 import Foundation 2 import Testing 3 4 #if os(macOS) 5 @testable import Listless 6 #else 7 @testable import Listless_iOS 8 #endif 9 10 @Suite("ItemStore Completion Behavior", .serialized) 11 @MainActor 12 struct ItemStoreCompletionTests { 13 14 // MARK: - Basic Completion Tests 15 16 @Test("Complete item") 17 func completeItem() async throws { 18 let store = makeTestStore() 19 let item = try store.createItem(title: "Task to complete") 20 21 try store.complete(itemID: item.id) 22 23 let items = try store.fetchItems() 24 #expect(items.first?.isCompleted == true) 25 } 26 27 @Test("Uncomplete item") 28 func uncompleteItem() async throws { 29 let store = makeTestStore() 30 let item = try store.createItem(title: "Item") 31 try store.complete(itemID: item.id) 32 33 try store.uncomplete(itemID: item.id) 34 35 let items = try store.fetchItems() 36 #expect(items.first?.isCompleted == false) 37 } 38 39 @Test("Complete with invalid ID does nothing") 40 func completeWithInvalidIDDoesNothing() async throws { 41 let store = makeTestStore() 42 let item = try store.createItem(title: "Item") 43 let invalidID = UUID() 44 45 try store.complete(itemID: invalidID) 46 47 let items = try store.fetchItems() 48 #expect(items.first?.isCompleted == false) 49 } 50 51 @Test("Uncomplete with invalid ID does nothing") 52 func uncompleteWithInvalidIDDoesNothing() async throws { 53 let store = makeTestStore() 54 let item = try store.createItem(title: "Item") 55 try store.complete(itemID: item.id) 56 let invalidID = UUID() 57 58 try store.uncomplete(itemID: invalidID) 59 60 let items = try store.fetchItems() 61 #expect(items.first?.isCompleted == true) 62 } 63 64 // MARK: - Timestamp Tests 65 66 @Test("Completing item updates timestamp") 67 func completingItemUpdatesTimestamp() async throws { 68 let store = makeTestStore() 69 let item = try store.createItem(title: "Item") 70 let originalUpdatedAt = item.updatedAt 71 72 // Small delay to ensure timestamp difference 73 try await Task.sleep(nanoseconds: 10_000_000) // 10ms 74 75 try store.complete(itemID: item.id) 76 77 let items = try store.fetchItems() 78 let updatedItem = items.first 79 #expect(updatedItem?.updatedAt ?? Date() > originalUpdatedAt) 80 } 81 82 // MARK: - Sorting Tests 83 84 @Test("Active items appear before completed items") 85 func activeItemsAppearBeforeCompletedItems() async throws { 86 let store = makeTestStore() 87 let item1 = try store.createItem(title: "Item 1") 88 let item2 = try store.createItem(title: "Item 2") 89 let item3 = try store.createItem(title: "Item 3") 90 91 try store.complete(itemID: item2.id) 92 93 let items = try store.fetchItems() 94 #expect(items[0].id == item1.id) 95 #expect(items[1].id == item3.id) 96 #expect(items[2].id == item2.id) 97 } 98 99 @Test("Completed items sorted by completedOrder") 100 func completedItemsSortedByCompletedOrder() async throws { 101 let store = makeTestStore() 102 let item1 = try store.createItem(title: "Item 1") 103 let item2 = try store.createItem(title: "Item 2") 104 let item3 = try store.createItem(title: "Item 3") 105 106 // Complete in specific order 107 try store.complete(itemID: item2.id) 108 try store.complete(itemID: item1.id) 109 try store.complete(itemID: item3.id) 110 111 let items = try store.fetchItems() 112 // All completed, should be sorted by completedOrder (most recently completed first) 113 #expect(items[0].id == item3.id) 114 #expect(items[1].id == item1.id) 115 #expect(items[2].id == item2.id) 116 } 117 118 @Test("Toggle completion multiple times") 119 func toggleCompletionMultipleTimes() async throws { 120 let store = makeTestStore() 121 let item = try store.createItem(title: "Item") 122 123 try store.complete(itemID: item.id) 124 var items = try store.fetchItems() 125 #expect(items.first?.isCompleted == true) 126 127 try store.uncomplete(itemID: item.id) 128 items = try store.fetchItems() 129 #expect(items.first?.isCompleted == false) 130 131 try store.complete(itemID: item.id) 132 items = try store.fetchItems() 133 #expect(items.first?.isCompleted == true) 134 } 135 136 @Test("Complete all items") 137 func completeAllItems() async throws { 138 let (store, itemIDs) = try makeTestStoreWithItems(count: 5) 139 140 for id in itemIDs { 141 try store.complete(itemID: id) 142 } 143 144 let items = try store.fetchItems() 145 #expect(items.allSatisfy { $0.isCompleted }) 146 #expect(items.count == 5) 147 } 148 149 @Test("Uncomplete restores previous sortOrder when no active conflict") 150 func uncompleteRestoresPreviousSortOrderWhenNoConflict() async throws { 151 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 152 let itemToRestoreID = itemIDs[1] 153 154 let originalSortOrder = try store.fetchItems().first { $0.id == itemToRestoreID }?.sortOrder 155 #expect(originalSortOrder != nil) 156 157 try store.complete(itemID: itemToRestoreID) 158 try store.uncomplete(itemID: itemToRestoreID) 159 160 let activeItems = try store.fetchItems().filter { !$0.isCompleted } 161 let restoredItem = activeItems.first { $0.id == itemToRestoreID } 162 163 #expect(restoredItem != nil) 164 #expect(restoredItem?.sortOrder == originalSortOrder) 165 #expect(activeItems.count == 3) 166 } 167 168 @Test("Uncomplete appends item when restored sortOrder conflicts with active item") 169 func uncompleteAppendsWhenRestoredSortOrderConflicts() async throws { 170 let store = makeTestStore() 171 let activeItem = try store.createItem(title: "Active item") 172 let completedItem = try store.createItem(title: "Completed item") 173 174 try store.complete(itemID: completedItem.id) 175 try store.moveItem(itemID: activeItem.id, toIndex: 0) 176 completedItem.sortOrder = activeItem.sortOrder 177 try store.save() 178 179 try store.uncomplete(itemID: completedItem.id) 180 181 let activeItems = try store.fetchItems().filter { !$0.isCompleted } 182 .sorted { $0.sortOrder < $1.sortOrder } 183 let lastActiveItem = activeItems.last 184 185 #expect(activeItems.count == 2) 186 #expect(lastActiveItem?.id == completedItem.id) 187 #expect(lastActiveItem?.sortOrder ?? 0 > activeItem.sortOrder) 188 } 189 }