← All repositories

listless

A simple list app for Apple platforms

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


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    // MARK: - Initial State Tests
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        // All items are active, so they should be the first 3
27        #expect(items.count == 3)
28
29        // Verify items are in ascending order
30        #expect(items[0].sortOrder < items[1].sortOrder)
31        #expect(items[1].sortOrder < items[2].sortOrder)
32
33        // Verify 1000-point gaps between items
34        #expect(items[1].sortOrder - items[0].sortOrder == 1000)
35        #expect(items[2].sortOrder - items[1].sortOrder == 1000)
36    }
37
38    // MARK: - Move Tests (Parameterized)
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    // MARK: - Order Preservation Tests
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    // MARK: - Invalid Input Tests
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    // MARK: - Completed Task Tests
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    // MARK: - Edge Cases
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}