Files
workouts/WorkoutsTests/GoalKindTests.swift
T
rzen 6e440317c4 Restructure into a three-tab app with Progress, goals, and Meditation
The UX redesign's first landing (spec in UX-REDESIGN.md): ContentView
becomes a Today / Progress / Settings TabView, "Routine" replaces
"Split" in every user-facing string and view name (code-level types
keep their names), and workout starting moves to shared
WorkoutStarter / StartedWorkoutNavigator plumbing.

- New Progress tab: weekly goal streaks, workout trends, per-exercise
  weight progression, achievements, and the full history list
  (WorkoutLogsView -> WorkoutHistoryView).
- Goals: stable categories workouts roll up to, managed from Settings.
- New Meditation exercise + starter routine; timed sits record to
  Apple Health as Mind & Body sessions.

Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4
2026-07-11 07:53:01 -04:00

85 lines
4.1 KiB
Swift

import Foundation
import Testing
@testable import Workouts
/// Locks `GoalKind`'s persisted-order contract: the stored raw-value list drives the
/// display order, unknown raw values are dropped, and any case missing from the stored
/// list is appended in declaration order — so a newly shipped goal kind surfaces
/// automatically without a migration. A throwaway `UserDefaults` suite keeps each case
/// isolated from the real domain.
struct GoalKindTests {
/// A fresh suite scoped to one test; cleared on teardown so nothing leaks between runs.
private func makeDefaults(_ function: String = #function) -> UserDefaults {
let suite = "GoalKindTests.\(function).\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
return defaults
}
/// With nothing stored, the order is exactly declaration order (all cases present).
@Test func emptyDefaultsGivesDeclarationOrder() {
let defaults = makeDefaults()
#expect(GoalKind.orderedCases(defaults: defaults) == GoalKind.allCases)
#expect(GoalKind.orderedCases(defaults: defaults) == [.mobility, .cardio, .strength, .mindfulness])
}
/// A partial stored list keeps the stored ones first (in stored order), then appends
/// the missing cases in declaration order — the "new kind appears automatically" rule.
@Test func partialStoredListPutsStoredFirstThenMissing() {
let defaults = makeDefaults()
GoalKind.saveOrder([.mindfulness, .mobility], defaults: defaults)
let ordered = GoalKind.orderedCases(defaults: defaults)
#expect(ordered == [.mindfulness, .mobility, .cardio, .strength])
}
/// A full stored list is honored verbatim — a user-chosen reorder round-trips.
@Test func fullStoredListIsHonoredVerbatim() {
let defaults = makeDefaults()
let chosen: [GoalKind] = [.cardio, .strength, .mindfulness, .mobility]
GoalKind.saveOrder(chosen, defaults: defaults)
#expect(GoalKind.orderedCases(defaults: defaults) == chosen)
}
/// Unknown raw values in the stored list are dropped (e.g. a kind removed in a future
/// version), and the survivors still slot ahead of the missing declaration-order tail.
@Test func unknownRawValuesAreDropped() {
let defaults = makeDefaults()
defaults.set(["mindfulness", "flexibility", "strength"], forKey: GoalKind.orderDefaultsKey)
let ordered = GoalKind.orderedCases(defaults: defaults)
#expect(ordered == [.mindfulness, .strength, .mobility, .cardio])
}
/// Duplicate raw values collapse to a single entry (first wins), so a corrupt list
/// never yields a kind twice.
@Test func duplicateRawValuesCollapse() {
let defaults = makeDefaults()
defaults.set(["strength", "strength", "mobility"], forKey: GoalKind.orderDefaultsKey)
let ordered = GoalKind.orderedCases(defaults: defaults)
#expect(ordered == [.strength, .mobility, .cardio, .mindfulness])
#expect(Set(ordered).count == ordered.count) // no dupes
}
/// `saveOrder` persists `[String]` of raw values — the exact shape `orderedCases`
/// reads back.
@Test func saveOrderStoresRawValueStrings() {
let defaults = makeDefaults()
GoalKind.saveOrder([.mobility, .strength], defaults: defaults)
#expect(defaults.stringArray(forKey: GoalKind.orderDefaultsKey) == ["mobility", "strength"])
}
/// Every case carries a valid, distinct-hued color name `Color.color(from:)` maps
/// to a non-default color, and stable display metadata.
@Test func metadataIsPopulatedAndDistinct() {
#expect(Set(GoalKind.allCases.map(\.colorName)).count == GoalKind.allCases.count)
#expect(Set(GoalKind.allCases.map(\.systemImage)).count == GoalKind.allCases.count)
#expect(GoalKind.strength.displayName == "Strength")
#expect(GoalKind.mindfulness.displayName == "Mindfulness")
// Each color name is one the palette understands (falls back to indigo otherwise).
#expect(GoalKind.allCases.allSatisfy { availableColors.contains($0.colorName) })
}
}