← All repositories

listless

A simple list app for Apple platforms

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


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    // MARK: - deleteMultiple
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    // MARK: - normalizeSortOrders
76
77    @Test("Normalize assigns evenly spaced sort orders")
78    func normalizeBasic() async throws {
79        let (store, ids) = try makeTestStoreWithItems(count: 3)
80        // Move items around to create irregular spacing.
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        // Completed items should still exist.
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        // After normalize: sort orders are 0, 1000, 2000.
137        // Creating at beginning should use minSortOrder - 1000 = -1000.
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}