Christopher Hotchkiss Christopher Hotchkiss
Login

Code

remindwall / RWPureSwift / Tests / DashboardTests / CardModelTests.swift

Back to remindwall — Current RemindWall

Download this file

import Foundation
import Testing

@testable import Dashboard

@Suite("Card Model Tests")
struct CardModelTests {
    private func card(
        _ id: String,
        _ priority: RailPriority,
        eta: TimeInterval? = nil
    ) -> RailCard {
        RailCard(
            id: id,
            priority: priority,
            etaSeconds: eta,
            content: .errorChip(message: id)
        )
    }

    @Test("Priority classes order late/degraded first, chrome last")
    func priorityClassOrdering() {
        let shuffled = [
            card("error", .errorChip),
            card("ontime-route", .onTimeRoute, eta: 600),
            card("upnext", .upNext, eta: 1500),
            card("late-bus", .lateBus, eta: 240),
            card("ontime-bus", .onTimeBus, eta: 840),
            card("slow-route", .slowRoute, eta: 1080),
        ]
        let rail = DashboardRail.assemble(shuffled, capacity: 10)
        #expect(rail.visible.map(\.id) == [
            "late-bus", "slow-route", "ontime-bus", "ontime-route", "upnext", "error",
        ])
        #expect(rail.overflowCount == 0)
    }

    @Test("Within a class the soonest ETA wins")
    func etaTiebreaker() {
        let rail = DashboardRail.assemble([
            card("bus-later", .onTimeBus, eta: 840),
            card("bus-sooner", .onTimeBus, eta: 240),
        ])
        #expect(rail.visible.map(\.id) == ["bus-sooner", "bus-later"])
    }

    @Test("A nil ETA sorts after every numbered ETA in its class")
    func nilEtaSortsLast() {
        let rail = DashboardRail.assemble([
            card("bus-no-eta", .onTimeBus),
            card("bus-timed", .onTimeBus, eta: 3600),
        ])
        #expect(rail.visible.map(\.id) == ["bus-timed", "bus-no-eta"])
    }

    @Test("Full ties fall back to id so refreshes never reshuffle")
    func deterministicTies() {
        let cards = [
            card("b", .onTimeBus, eta: 300),
            card("a", .onTimeBus, eta: 300),
            card("c", .onTimeBus, eta: 300),
        ]
        let first = DashboardRail.assemble(cards)
        let second = DashboardRail.assemble(cards.reversed())
        #expect(first.visible.map(\.id) == ["a", "b", "c"])
        #expect(first == second)
    }

    @Test("Capacity caps the stack and reports the cut count")
    func capacityAndOverflow() {
        let cards = (0..<6).map { card("bus-\($0)", .onTimeBus, eta: TimeInterval($0 * 60)) }
        let rail = DashboardRail.assemble(cards, capacity: DashboardRail.wallCapacity)
        #expect(rail.visible.count == 4)
        #expect(rail.overflowCount == 2)
        // The cut cards are the LOWEST priority ones, never the urgent ones.
        #expect(rail.visible.map(\.id) == ["bus-0", "bus-1", "bus-2", "bus-3"])
    }

    @Test("Under capacity nothing is cut")
    func underCapacity() {
        let rail = DashboardRail.assemble(
            [card("only", .lateBus, eta: 60)],
            capacity: DashboardRail.padCapacity
        )
        #expect(rail.visible.count == 1)
        #expect(rail.overflowCount == 0)
    }

    @Test("Empty input assembles the empty rail")
    func emptyInput() {
        #expect(DashboardRail.assemble([]) == .empty)
    }
}