Christopher Hotchkiss Christopher Hotchkiss
Login

Code

remindwall / RWPureSwift / Tests / DashboardTests / SurfaceCardTests.swift

Back to remindwall — Current RemindWall

Download this file

import Dao
import DependenciesTestSupport
import Foundation
import Testing

@testable import Dashboard

/// D1.4: the migrated surfaces express themselves as rail cards.
@MainActor
@Suite("Surface Card Mapping Tests", .dependencies {
    // Feature States hold .syncedSetting keys, which read the database at init.
    $0.defaultDatabase = try! $0.appDatabase()
    $0.uuid = .incrementing
})
struct SurfaceCardTests {
    private func arrival(
        _ label: String,
        isLate: Bool,
        etaSeconds: TimeInterval,
        isLive: Bool = true
    ) -> DisplayArrival {
        DisplayArrival(
            id: MonitoredStop.ID(UUID()),
            label: label,
            routeShortName: "12",
            etaText: "4 min",
            etaSeconds: etaSeconds,
            isLate: isLate,
            isLive: isLive
        )
    }

    @Test("A late arrival maps to the lateBus priority class")
    func lateArrivalPriority() {
        var state = BusArrivalsFeature.State()
        state.arrivals = [
            arrival("School bus", isLate: true, etaSeconds: 240),
            arrival("Commute", isLate: false, etaSeconds: 840),
        ]

        let cards = state.railCards
        #expect(cards.count == 2)
        #expect(cards[0].priority == .lateBus)
        #expect(cards[0].etaSeconds == 240)
        #expect(cards[1].priority == .onTimeBus)
        guard case let .transit(route, label, etaText, isLate, isLive) = cards[0].content else {
            Issue.record("expected transit content")
            return
        }
        #expect(route == "12")
        #expect(label == "School bus")
        #expect(etaText == "4 min")
        #expect(isLate)
        #expect(isLive)
    }

    @Test("A fetch failure with nothing to show collapses to one error chip")
    func errorChipWhenEmpty() {
        var state = BusArrivalsFeature.State()
        state.arrivals = []
        state.lastError = "unauthorized"

        let cards = state.railCards
        #expect(cards.count == 1)
        #expect(cards[0].priority == .errorChip)
        #expect(cards[0].content == .errorChip(message: "Cannot reach transit API: unauthorized"))
    }

    @Test("An error with arrivals still showing adds no chip - stale beats noise")
    func noErrorChipWithArrivals() {
        var state = BusArrivalsFeature.State()
        state.arrivals = [arrival("School bus", isLate: false, etaSeconds: 240)]
        state.lastError = "timeout"

        #expect(state.railCards.count == 1)
        #expect(state.railCards[0].priority == .onTimeBus)
    }

    @Test("No next event means no up-next card")
    func noUpNextCard() {
        let state = CalendarEventsFeature.State()
        #expect(state.railCards.isEmpty)
    }

    @Test("The next event maps to an upNext card with the ETA tiebreaker")
    func upNextCard() {
        var state = CalendarEventsFeature.State()
        state.nextEventTitle = "Dentist"
        state.nextEventTimeUntil = "25min"
        state.nextEventLeadingEmoji = "🦷"
        state.nextEventStartsInSeconds = 1500

        let cards = state.railCards
        #expect(cards.count == 1)
        #expect(cards[0].priority == .upNext)
        #expect(cards[0].etaSeconds == 1500)
        #expect(cards[0].content == .upNext(title: "Dentist", timeUntil: "in 25min", emoji: "🦷"))
    }

    @Test("The dashboard rail orders bus and calendar cards across features")
    func dashboardAssemblesAcrossFeatures() {
        var state = DashboardFeature.State()
        state.busArrivalsState.arrivals = [
            arrival("Commute", isLate: false, etaSeconds: 840),
            arrival("School bus", isLate: true, etaSeconds: 240),
        ]
        state.calendarEventsState.nextEventTitle = "Dentist"
        state.calendarEventsState.nextEventTimeUntil = "25min"
        state.calendarEventsState.nextEventStartsInSeconds = 1500

        let rail = state.rail
        #expect(rail.visible.map(\.priority) == [.lateBus, .onTimeBus, .upNext])
        #expect(rail.overflowCount == 0)
    }
}