Tests/Unit/ItemStoreOrderingTests.swift (6412B)
1import Foundation
2import 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
12struct ItemStoreOrderingTests {
13
14
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
27 #expect(items.count == 3)
28
29
30 #expect(items[0].sortOrder < items[1].sortOrder)
31 #expect(items[1].sortOrder < items[2].sortOrder)
32
33
34 #expect(items[1].sortOrder - items[0].sortOrder == 1000)
35 #expect(items[2].sortOrder - items[1].sortOrder == 1000)
36 }
37
38
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
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
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
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
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}