listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

ItemStoreOrderingTests.swift (6412B)


      1 import Foundation
      2 import 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
     12 struct 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 }