From 3fada881befa04e7b526ed2850977e439f47896c Mon Sep 17 00:00:00 2001 From: kavemang <66108693+kavemang@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:58:46 -0500 Subject: [PATCH] Test workout catalog icons through the production resolver Refs #2708 --- .../Sources/StrandDesign/SportIcon.swift | 107 ------------------ .../WorkoutTypeIconTests.swift | 24 ++-- StrandTests/WorkoutCatalogTests.swift | 4 +- 3 files changed, 16 insertions(+), 119 deletions(-) diff --git a/Packages/StrandDesign/Sources/StrandDesign/SportIcon.swift b/Packages/StrandDesign/Sources/StrandDesign/SportIcon.swift index 2ecb3548a1..b49bff90e5 100644 --- a/Packages/StrandDesign/Sources/StrandDesign/SportIcon.swift +++ b/Packages/StrandDesign/Sources/StrandDesign/SportIcon.swift @@ -394,113 +394,6 @@ public enum WorkoutTypeIconography { } } - /// Preferred glyph identity ignoring OS availability — used to assert catalogue uniqueness. - public static func preferredIdentity(for type: KnownWorkoutType) -> String { - switch type { - case .running: return "system:figure.run" - case .walking: return "system:figure.walk" - case .nordicWalking: return "system:figure.walk.motion" - case .ballet: return "system:figure.barre" - case .billiards: return "system:8.circle.fill" - case .breakdancing: return "system:figure.socialdance" - case .cheerleading: return "system:megaphone.fill" - case .darts: return "system:target" - case .discGolf: return "system:flag.fill" - case .hurlingCamogie: return "system:sportscourt.fill" - case .jiuJitsu: return "system:figure.wrestling" - case .judo: return "system:figure.taichi" - case .kiteboarding: return "system:wind" - case .motocross: return "system:fuelpump.fill" - case .muayThai: return "system:hand.raised.fill" - case .paintball: return "system:scope" - case .parkour: return "system:figure.play" - case .polo: return "system:flag.2.crossed.fill" - case .skydiving: return "system:airplane.departure" - case .hiking: return "system:figure.hiking" - case .cycling: return "system:figure.outdoor.cycle" - case .openWaterSwim: return "system:figure.open.water.swim" - case .rowing: return "system:figure.outdoor.rowing" - case .treadmillRun: return "system:figure.run.treadmill" - case .treadmillWalk: return "system:figure.walk.treadmill" - case .indoorCycle: return "system:figure.indoor.cycle" - case .poolSwim: return "system:figure.pool.swim" - case .rowMachine: return "system:figure.indoor.rowing" - case .elliptical: return "system:figure.elliptical" - case .strength: return "system:dumbbell.fill" - case .bodybuilding: return "system:figure.strengthtraining.traditional" - case .weightlifting: return "system:figure.strengthtraining.functional" - case .hiit: return "system:figure.highintensity.intervaltraining" - case .yoga: return "system:figure.yoga" - case .pilates: return "system:figure.pilates" - case .boxing: return "system:figure.boxing" - case .basketball: return "system:figure.basketball" - case .soccer: return "system:figure.outdoor.soccer" - case .baseball: return "system:figure.baseball" - case .badminton: return "system:figure.badminton" - case .tennis: return "system:figure.tennis" - case .squash: return "system:figure.squash" - case .racquetball: return "system:figure.racquetball" - case .tableTennis: return "system:figure.table.tennis" - case .volleyball: return "system:figure.volleyball" - case .martialArts: return "system:figure.martial.arts" - case .dancing: return "system:figure.dance" - case .golf: return "system:figure.golf" - case .climbing: return "system:figure.climbing" - case .stretching: return "system:figure.flexibility" - case .skiing: return "system:figure.skiing.downhill" - case .snowboarding: return "system:figure.snowboarding" - case .padel: return "custom:padelRacket" - case .pickleball: return "system:figure.pickleball" - case .bowling: return "system:figure.bowling" - case .iceHockey: return "system:figure.hockey" - case .americanFootball: return "system:figure.american.football" - case .australianFootball: return "system:figure.australian.football" - case .rugby: return "system:figure.rugby" - case .cricket: return "system:figure.cricket" - case .softball: return "system:figure.softball" - case .handball: return "system:figure.handball" - case .waterPolo: return "system:figure.waterpolo" - case .frisbee: return "system:figure.disc.sports" - case .surfing: return "system:figure.surfing" - case .kayaking: return "system:oar.2.crossed" - case .sailing: return "system:figure.sailing" - case .scubaDiving: return "system:figure.water.fitness" - case .iceSkating: return "system:figure.ice.skating" - case .inlineSkating: return "system:figure.skating" - case .snowshoeing: return "system:figure.skiing.crosscountry" - case .gymnastics: return "system:figure.gymnastics" - case .fencing: return "system:figure.fencing" - case .calisthenics: return "system:figure.core.training" - case .stairClimber: return "system:figure.stair.stepper" - case .bootCamp: return "system:figure.step.training" - case .lacrosse: return "system:figure.lacrosse" - case .fieldHockey: return "system:figure.field.hockey" - case .crossfit: return "system:figure.cross.training" - case .kickboxing: return "system:figure.kickboxing" - case .mountainBiking: return "system:bicycle" - case .skateboarding: return "system:skateboard" - case .standUpPaddleboard: return "system:water.waves" - case .spinning: return "system:bicycle.circle" - case .jumpRope: return "system:figure.jumprope" - case .powerlifting: return "system:dumbbell" - case .rucking: return "system:backpack" - case .sandVolleyball: return "system:beach.umbrella" - case .archery: return "system:figure.archery" - case .fishing: return "system:figure.fishing" - case .hunting: return "system:figure.hunting" - case .curling: return "system:figure.curling" - case .netball: return "system:basketball" - case .gaelicFootball: return "system:soccerball" - case .spikeball: return "system:volleyball" - case .meditation: return "system:figure.mind.and.body" - case .horsebackRiding: return "system:figure.equestrian.sports" - case .wheelchair: return "system:figure.roll" - case .gaming: return "system:gamecontroller.fill" - case .motorRacing: return "system:steeringwheel" - case .other: return "system:figure.mixed.cardio" - } - } - /// Runtime glyph identity (after availability resolution). public static func identity(for type: KnownWorkoutType) -> String { switch glyph(for: type) { diff --git a/Packages/StrandDesign/Tests/StrandDesignTests/WorkoutTypeIconTests.swift b/Packages/StrandDesign/Tests/StrandDesignTests/WorkoutTypeIconTests.swift index 24efc9791e..281e8556b4 100644 --- a/Packages/StrandDesign/Tests/StrandDesignTests/WorkoutTypeIconTests.swift +++ b/Packages/StrandDesign/Tests/StrandDesignTests/WorkoutTypeIconTests.swift @@ -26,16 +26,6 @@ final class WorkoutTypeIconTests: XCTestCase { XCTAssertEqual(KnownWorkoutType.resolving("evening dancing class"), .dancing) } - func testPreferredIdentitiesAreUnique() { - var seen = Set() - for type in KnownWorkoutType.allCases { - let id = WorkoutTypeIconography.preferredIdentity(for: type) - XCTAssertFalse(seen.contains(id), "Duplicate preferred icon identity \(id) for \(type.rawValue)") - seen.insert(id) - } - XCTAssertEqual(seen.count, KnownWorkoutType.allCases.count) - } - func testRuntimeIdentitiesAreUnique() { var seen = Set() for type in KnownWorkoutType.allCases { @@ -67,6 +57,20 @@ final class WorkoutTypeIconTests: XCTestCase { .custom(.padelRacket)) } + func testSportSymbolBridgeUsesRuntimeSystemGlyphs() { + for type in KnownWorkoutType.allCases { + if case .system(let name) = WorkoutTypeIconography.glyph(for: type) { + XCTAssertEqual(sportSymbol(type.rawValue), name, type.rawValue) + } + } + } + + func testSportSymbolBridgeHandlesCustomAndUnknownWorkouts() { + XCTAssertEqual(sportSymbol("Padel"), "figure.tennis") + XCTAssertEqual(sportSymbol("unrecognised workout"), "figure.mixed.cardio") + XCTAssertEqual(sportSymbol(""), "figure.mixed.cardio") + } + func testSportSymbolBridgeNonEmpty() { for type in KnownWorkoutType.allCases { XCTAssertFalse(sportSymbol(type.rawValue).isEmpty) diff --git a/StrandTests/WorkoutCatalogTests.swift b/StrandTests/WorkoutCatalogTests.swift index 9db6cd57d9..8507775731 100644 --- a/StrandTests/WorkoutCatalogTests.swift +++ b/StrandTests/WorkoutCatalogTests.swift @@ -91,7 +91,7 @@ final class WorkoutCatalogTests: XCTestCase { } /// Iconography lockstep: every catalogue sport must resolve to a `KnownWorkoutType` with a unique - /// preferred glyph so the live-workout Liquid Glass control never shares icons across types. + /// runtime glyph on the current OS, matching the live-workout Liquid Glass control. func testCatalogueSportsHaveUniqueWorkoutTypeIcons() { let catalogNames = WorkoutCatalog.all.map(\.name) let knownNames = KnownWorkoutType.allCases.map(\.rawValue) @@ -103,7 +103,7 @@ final class WorkoutCatalogTests: XCTestCase { guard let type = KnownWorkoutType.exact(matching: sport.name) else { return XCTFail("No KnownWorkoutType for catalogue sport \(sport.name)") } - let id = WorkoutTypeIconography.preferredIdentity(for: type) + let id = WorkoutTypeIconography.identity(for: type) XCTAssertFalse(identities.contains(id), "Duplicate icon \(id) for \(sport.name)") identities.insert(id) }