← All repositories

listless

A simple list app for Apple platforms

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


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    // MARK: - Basic Completion Tests
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    // MARK: - Timestamp Tests
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        // Small delay to ensure timestamp difference
73        try await Task.sleep(nanoseconds: 10_000_000) // 10ms
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    // MARK: - Sorting Tests
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        // Complete in specific order
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        // All completed, should be sorted by completedOrder (most recently completed first)
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}