Tests/Unit/ItemStoreDeleteAndNormalizeTests.swift (6516B)
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 Delete Multiple & Normalize", .serialized)
11@MainActor
12struct ItemStoreDeleteAndNormalizeTests {
13
14
15
16 @Test("Delete multiple items removes all specified items")
17 func deleteMultipleBasic() async throws {
18 let (store, ids) = try makeTestStoreWithItems(count: 4)
19
20 try store.deleteMultiple(itemIDs: [ids[0], ids[2]])
21
22 let remaining = try store.fetchItems()
23 let remainingIDs = remaining.map(\.id)
24 #expect(remainingIDs.contains(ids[1]))
25 #expect(remainingIDs.contains(ids[3]))
26 #expect(!remainingIDs.contains(ids[0]))
27 #expect(!remainingIDs.contains(ids[2]))
28 #expect(remaining.count == 2)
29 }
30
31 @Test("Delete multiple with empty array does nothing")
32 func deleteMultipleEmpty() async throws {
33 let (store, _) = try makeTestStoreWithItems(count: 3)
34
35 try store.deleteMultiple(itemIDs: [])
36
37 let items = try store.fetchItems()
38 #expect(items.count == 3)
39 }
40
41 @Test("Delete multiple skips unknown IDs without error")
42 func deleteMultipleWithUnknownIDs() async throws {
43 let (store, ids) = try makeTestStoreWithItems(count: 3)
44
45 try store.deleteMultiple(itemIDs: [ids[0], UUID(), ids[2]])
46
47 let remaining = try store.fetchItems()
48 #expect(remaining.count == 1)
49 #expect(remaining[0].id == ids[1])
50 }
51
52 @Test("Delete multiple handles mix of active and completed items")
53 func deleteMultipleMixedState() async throws {
54 let (store, ids) = try makeTestStoreWithItems(count: 4)
55 try store.complete(itemID: ids[1])
56 try store.complete(itemID: ids[3])
57
58 try store.deleteMultiple(itemIDs: [ids[0], ids[1]])
59
60 let remaining = try store.fetchItems()
61 let remainingIDs = Set(remaining.map(\.id))
62 #expect(remainingIDs == Set([ids[2], ids[3]]))
63 }
64
65 @Test("Delete multiple with all items leaves store empty")
66 func deleteMultipleAll() async throws {
67 let (store, ids) = try makeTestStoreWithItems(count: 3)
68
69 try store.deleteMultiple(itemIDs: ids)
70
71 let items = try store.fetchItems()
72 #expect(items.isEmpty)
73 }
74
75
76
77 @Test("Normalize assigns evenly spaced sort orders")
78 func normalizeBasic() async throws {
79 let (store, ids) = try makeTestStoreWithItems(count: 3)
80
81 try store.moveItem(itemID: ids[2], toIndex: 0)
82
83 try store.normalizeSortOrders()
84
85 let items = try store.fetchItems().filter { !$0.isCompleted }
86 #expect(items.count == 3)
87 for (index, item) in items.enumerated() {
88 #expect(item.sortOrder == Int64(index) * 1000)
89 }
90 }
91
92 @Test("Normalize preserves existing order")
93 func normalizePreservesOrder() async throws {
94 let (store, _) = try makeTestStoreWithItems(
95 count: 3, titles: ["First", "Second", "Third"])
96
97 try store.normalizeSortOrders()
98
99 let items = try store.fetchItems().filter { !$0.isCompleted }
100 #expect(items.map(\.title) == ["First", "Second", "Third"])
101 }
102
103 @Test("Normalize on empty store does nothing")
104 func normalizeEmpty() async throws {
105 let store = makeTestStore()
106
107 try store.normalizeSortOrders()
108
109 let items = try store.fetchItems()
110 #expect(items.isEmpty)
111 }
112
113 @Test("Normalize ignores completed items")
114 func normalizeIgnoresCompleted() async throws {
115 let (store, ids) = try makeTestStoreWithItems(count: 4)
116 try store.complete(itemID: ids[1])
117 try store.complete(itemID: ids[3])
118
119 try store.normalizeSortOrders()
120
121 let active = try store.fetchItems().filter { !$0.isCompleted }
122 #expect(active.count == 2)
123 #expect(active[0].sortOrder == 0)
124 #expect(active[1].sortOrder == 1000)
125
126
127 let all = try store.fetchItems()
128 #expect(all.count == 4)
129 }
130
131 @Test("Normalize then create at beginning uses correct offset")
132 func normalizeFollowedByPrepend() async throws {
133 let (store, _) = try makeTestStoreWithItems(count: 3)
134 try store.normalizeSortOrders()
135
136
137
138 _ = try store.createItem(title: "Prepended", atBeginning: true)
139 try store.save()
140
141 let items = try store.fetchItems().filter { !$0.isCompleted }
142 #expect(items.first?.title == "Prepended")
143 #expect(items.first?.sortOrder == -1000)
144 }
145
146 @Test("Normalize with single item sets sort order to zero")
147 func normalizeSingleItem() async throws {
148 let store = makeTestStore()
149 _ = try store.createItem(title: "Only", atBeginning: true)
150 try store.save()
151
152 try store.normalizeSortOrders()
153
154 let items = try store.fetchItems().filter { !$0.isCompleted }
155 #expect(items.count == 1)
156 #expect(items[0].sortOrder == 0)
157 }
158
159 @Test("Double normalize is idempotent")
160 func doubleNormalize() async throws {
161 let (store, _) = try makeTestStoreWithItems(count: 4)
162 try store.moveItem(itemID: try store.fetchItems()[3].id, toIndex: 0)
163
164 try store.normalizeSortOrders()
165 let afterFirst = try store.fetchItems().filter { !$0.isCompleted }.map(\.sortOrder)
166
167 try store.normalizeSortOrders()
168 let afterSecond = try store.fetchItems().filter { !$0.isCompleted }.map(\.sortOrder)
169
170 #expect(afterFirst == afterSecond)
171 }
172
173 @Test("Normalize fixes negative sort orders from prepend operations")
174 func normalizeFixesNegativeOrders() async throws {
175 let store = makeTestStore()
176 _ = try store.createItem(title: "Original")
177 try store.save()
178 _ = try store.createItem(title: "Prepended", atBeginning: true)
179 try store.save()
180
181 let beforeNormalize = try store.fetchItems().filter { !$0.isCompleted }
182 #expect(beforeNormalize[0].sortOrder < 0)
183
184 try store.normalizeSortOrders()
185
186 let afterNormalize = try store.fetchItems().filter { !$0.isCompleted }
187 #expect(afterNormalize[0].sortOrder == 0)
188 #expect(afterNormalize[1].sortOrder == 1000)
189 #expect(afterNormalize[0].title == "Prepended")
190 }
191}