← All repositories

listless

A simple list app for Apple platforms

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


Tests/Unit/AccentColorTests.swift (5361B)

1import Foundation
2import SwiftUI
3import Testing
4
5#if os(macOS)
6@testable import Listless
7#else
8@testable import Listless_iOS
9#endif
10
11@Suite("AccentColor")
12struct AccentColorTests {
13
14    // MARK: - ColorTheme properties
15
16    @Test("displayOrder sorts themes alphabetically by displayName")
17    func displayOrderSorting() {
18        let order = ColorTheme.displayOrder
19        let names = order.map(\.displayName)
20
21        #expect(names == names.sorted())
22    }
23
24    @Test("All themes have unique display names")
25    func uniqueDisplayNames() {
26        let names = ColorTheme.allCases.map(\.displayName)
27
28        #expect(Set(names).count == names.count)
29    }
30
31    @Test("displayOrder contains all cases")
32    func displayOrderContainsAll() {
33        #expect(Set(ColorTheme.displayOrder) == Set(ColorTheme.allCases))
34    }
35
36    // MARK: - itemColor
37
38    @Test(
39        "Single item returns top color for all themes",
40        arguments: ColorTheme.allCases
41    )
42    func singleItemReturnsTopColor(theme: ColorTheme) {
43        let singleColor = itemColor(forIndex: 0, total: 1, theme: theme)
44        // For a single item, itemColor returns the top HSB directly.
45        // We can't inspect Color internals, but we can verify it doesn't crash
46        // and returns a non-nil Color.
47        #expect(type(of: singleColor) == Color.self)
48    }
49
50    @Test("First item of many matches top color")
51    func firstItemMatchesTop() {
52        let topColor = itemColor(forIndex: 0, total: 1, theme: .pilbara)
53        let firstOfMany = itemColor(forIndex: 0, total: 10, theme: .pilbara)
54
55        // Both should be the top color since progress=0 yields top for both paths.
56        #expect(topColor.description == firstOfMany.description)
57    }
58
59    @Test("Last item of many uses bottom color")
60    func lastItemUsesBottom() {
61        // progress = 1.0 → interpolates from mid to bottom with progress=1.0 → bottom
62        let lastColor = itemColor(forIndex: 9, total: 10, theme: .pilbara)
63        #expect(type(of: lastColor) == Color.self)
64    }
65
66    @Test("Middle item of odd count uses mid color")
67    func middleItemUsesMidColor() {
68        // For total=3, index=1 → progress = 0.5 → exactly at mid
69        let midColor = itemColor(forIndex: 1, total: 3, theme: .pilbara)
70        #expect(type(of: midColor) == Color.self)
71    }
72
73    @Test(
74        "Gradient produces distinct colors for each position",
75        arguments: ColorTheme.allCases
76    )
77    func gradientProducesDistinctColors(theme: ColorTheme) {
78        let total = 5
79        let colors = (0..<total).map {
80            itemColor(forIndex: $0, total: total, theme: theme).description
81        }
82        // Adjacent colors should differ (gradient is continuous but not flat).
83        for i in 0..<(colors.count - 1) {
84            #expect(colors[i] != colors[i + 1])
85        }
86    }
87
88    @Test("Different themes produce different colors for same position")
89    func differentThemesDiffer() {
90        let pilbaraColor = itemColor(forIndex: 0, total: 5, theme: .pilbara).description
91        let collaroyColor = itemColor(forIndex: 0, total: 5, theme: .collaroy).description
92
93        #expect(pilbaraColor != collaroyColor)
94    }
95
96    // MARK: - Edge cases / out-of-range inputs
97
98    @Test("total=0 returns top color without crashing")
99    func totalZero() {
100        // total <= 1 hits the guard, so total=0 should behave like total=1.
101        let color = itemColor(forIndex: 0, total: 0, theme: .pilbara)
102        let topColor = itemColor(forIndex: 0, total: 1, theme: .pilbara)
103        #expect(color.description == topColor.description)
104    }
105
106    @Test("Negative total returns top color without crashing")
107    func negativeTotalDoesNotCrash() {
108        let color = itemColor(forIndex: 0, total: -1, theme: .pilbara)
109        let topColor = itemColor(forIndex: 0, total: 1, theme: .pilbara)
110        #expect(color.description == topColor.description)
111    }
112
113    @Test("Negative index produces a color without crashing")
114    func negativeIndex() {
115        // Extrapolates beyond the gradient — should not crash.
116        let color = itemColor(forIndex: -1, total: 5, theme: .pilbara)
117        #expect(type(of: color) == Color.self)
118    }
119
120    @Test("Index beyond total produces a color without crashing")
121    func indexBeyondTotal() {
122        // Extrapolates beyond the gradient — should not crash.
123        let color = itemColor(forIndex: 10, total: 5, theme: .pilbara)
124        #expect(type(of: color) == Color.self)
125    }
126
127    @Test("Very large total does not crash")
128    func veryLargeTotal() {
129        let color = itemColor(forIndex: 500, total: 1000, theme: .collaroy)
130        #expect(type(of: color) == Color.self)
131    }
132
133    // MARK: - cachedItemColor
134
135    @Test("Cached color returns same result as uncached")
136    @MainActor
137    func cachedMatchesUncached() {
138        let uncached = itemColor(forIndex: 2, total: 5, theme: .pilbara).description
139        let cached = cachedItemColor(forIndex: 2, total: 5, theme: .pilbara).description
140
141        #expect(cached == uncached)
142    }
143
144    @Test("Repeated cached calls return consistent results")
145    @MainActor
146    func cachedConsistency() {
147        let first = cachedItemColor(forIndex: 1, total: 4, theme: .collaroy).description
148        let second = cachedItemColor(forIndex: 1, total: 4, theme: .collaroy).description
149
150        #expect(first == second)
151    }
152}