ItemStoreOrderingTests.swift (6412B)
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 Task Reordering", .serialized) 11 @MainActor 12 struct ItemStoreOrderingTests { 13 14 // MARK: - Initial State Tests 15 16 @Test("Initial sortOrder has 1000-point gaps") 17 func initialSortOrderHasThousandPointGaps() async throws { 18 let store = makeTestStore() 19 20 let item1 = try store.createItem(title: "Item 1") 21 let item2 = try store.createItem(title: "Item 2") 22 let item3 = try store.createItem(title: "Item 3") 23 24 let items = try store.fetchItems() 25 26 // All items are active, so they should be the first 3 27 #expect(items.count == 3) 28 29 // Verify items are in ascending order 30 #expect(items[0].sortOrder < items[1].sortOrder) 31 #expect(items[1].sortOrder < items[2].sortOrder) 32 33 // Verify 1000-point gaps between items 34 #expect(items[1].sortOrder - items[0].sortOrder == 1000) 35 #expect(items[2].sortOrder - items[1].sortOrder == 1000) 36 } 37 38 // MARK: - Move Tests (Parameterized) 39 40 @Test("Move item to different positions", arguments: [ 41 (from: 0, to: 2), 42 (from: 2, to: 0), 43 (from: 0, to: 1), 44 (from: 1, to: 0), 45 (from: 1, to: 2), 46 (from: 2, to: 1), 47 ]) 48 func moveItemToDifferentPositions(from: Int, to: Int) async throws { 49 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 50 let itemToMove = itemIDs[from] 51 52 try store.moveItem(itemID: itemToMove, toIndex: to) 53 54 let items = try store.fetchItems().filter { !$0.isCompleted } 55 #expect(items[to].id == itemToMove) 56 } 57 58 // MARK: - Order Preservation Tests 59 60 @Test("Moving maintains 1000-point gaps") 61 func movingMaintainsThousandPointGaps() async throws { 62 let (store, itemIDs) = try makeTestStoreWithItems(count: 4) 63 64 try store.moveItem(itemID: itemIDs[0], toIndex: 2) 65 66 let items = try store.fetchItems().filter { !$0.isCompleted } 67 #expect(items[0].sortOrder == 0) 68 #expect(items[1].sortOrder == 1000) 69 #expect(items[2].sortOrder == 2000) 70 #expect(items[3].sortOrder == 3000) 71 } 72 73 @Test("Move item to same index does nothing") 74 func moveItemToSameIndexDoesNothing() async throws { 75 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 76 let originalItems = try store.fetchItems().filter { !$0.isCompleted } 77 78 try store.moveItem(itemID: itemIDs[1], toIndex: 1) 79 80 let items = try store.fetchItems().filter { !$0.isCompleted } 81 #expect(items[0].id == originalItems[0].id) 82 #expect(items[1].id == originalItems[1].id) 83 #expect(items[2].id == originalItems[2].id) 84 } 85 86 // MARK: - Invalid Input Tests 87 88 @Test("Move with invalid ID does nothing") 89 func moveWithInvalidIDDoesNothing() async throws { 90 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 91 let originalItems = try store.fetchItems().filter { !$0.isCompleted } 92 let invalidID = UUID() 93 94 try store.moveItem(itemID: invalidID, toIndex: 0) 95 96 let items = try store.fetchItems().filter { !$0.isCompleted } 97 #expect(items[0].id == originalItems[0].id) 98 #expect(items[1].id == originalItems[1].id) 99 #expect(items[2].id == originalItems[2].id) 100 } 101 102 @Test("Move to negative index clamps to 0") 103 func moveToNegativeIndexClampsToZero() async throws { 104 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 105 106 try store.moveItem(itemID: itemIDs[2], toIndex: -5) 107 108 let items = try store.fetchItems().filter { !$0.isCompleted } 109 #expect(items[0].id == itemIDs[2]) 110 } 111 112 @Test("Move to out-of-bounds index clamps to end") 113 func moveToOutOfBoundsIndexClampsToEnd() async throws { 114 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 115 116 try store.moveItem(itemID: itemIDs[0], toIndex: 999) 117 118 let items = try store.fetchItems().filter { !$0.isCompleted } 119 #expect(items[2].id == itemIDs[0]) 120 } 121 122 // MARK: - Completed Task Tests 123 124 @Test("Moving only affects active items") 125 func movingOnlyAffectsActiveItems() async throws { 126 let (store, itemIDs) = try makeTestStoreWithItems(count: 4) 127 try store.complete(itemID: itemIDs[3]) 128 129 try store.moveItem(itemID: itemIDs[0], toIndex: 2) 130 131 let allItems = try store.fetchItems() 132 let activeItems = allItems.filter { !$0.isCompleted } 133 let completedItems = allItems.filter { $0.isCompleted } 134 135 #expect(activeItems.count == 3) 136 #expect(completedItems.count == 1) 137 #expect(completedItems[0].id == itemIDs[3]) 138 } 139 140 @Test("Moving completed item does nothing") 141 func movingCompletedItemDoesNothing() async throws { 142 let (store, itemIDs) = try makeTestStoreWithItems(count: 3) 143 try store.complete(itemID: itemIDs[0]) 144 let originalItems = try store.fetchItems() 145 146 try store.moveItem(itemID: itemIDs[0], toIndex: 1) 147 148 let items = try store.fetchItems() 149 #expect(items[0].id == originalItems[0].id) 150 #expect(items[1].id == originalItems[1].id) 151 #expect(items[2].id == originalItems[2].id) 152 } 153 154 // MARK: - Edge Cases 155 156 @Test("Move single item does nothing") 157 func moveSingleItemDoesNothing() async throws { 158 let store = makeTestStore() 159 let item = try store.createItem(title: "Only item") 160 161 try store.moveItem(itemID: item.id, toIndex: 0) 162 163 let items = try store.fetchItems() 164 #expect(items.count == 1) 165 #expect(items[0].id == item.id) 166 } 167 168 @Test("Move in empty store does nothing") 169 func moveInEmptyStoreDoesNothing() async throws { 170 let store = makeTestStore() 171 let randomID = UUID() 172 173 try store.moveItem(itemID: randomID, toIndex: 0) 174 175 let items = try store.fetchItems() 176 #expect(items.isEmpty) 177 } 178 179 @Test("Multiple moves maintain order") 180 func multipleMoveMaintainOrder() async throws { 181 let (store, itemIDs) = try makeTestStoreWithItems(count: 4) 182 183 try store.moveItem(itemID: itemIDs[0], toIndex: 3) 184 try store.moveItem(itemID: itemIDs[2], toIndex: 0) 185 186 let items = try store.fetchItems().filter { !$0.isCompleted } 187 #expect(items[0].id == itemIDs[2]) 188 #expect(items[3].id == itemIDs[0]) 189 } 190 }