Tests/Unit/ItemStoreCompletionTests.swift (6404B)
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 Completion Behavior", .serialized)
11@MainActor
12struct ItemStoreCompletionTests {
13
14
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
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
73 try await Task.sleep(nanoseconds: 10_000_000)
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
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
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
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}