import Foundation import Testing import AVFoundation @testable import Workouts /// Locks the voice-resolution contract: the picker offers English voices, and the /// announcer speaks with the user's stored choice when set, falling back to a valid /// English voice otherwise. struct SpeechSettingsTests { @Test func availableVoicesAreEnglish() { let voices = SpeechSettings.availableVoices() #expect(!voices.isEmpty) #expect(voices.allSatisfy { $0.language.hasPrefix("en") }) } @Test func resolvedVoiceHonorsStoredIdentifierThenFallsBack() { let key = SpeechSettings.voiceIdentifierKey let original = UserDefaults.standard.string(forKey: key) defer { if let original { UserDefaults.standard.set(original, forKey: key) } else { UserDefaults.standard.removeObject(forKey: key) } } // Automatic (empty) resolves to some installed English voice. UserDefaults.standard.set("", forKey: key) let auto = SpeechSettings.resolvedVoice() #expect(auto != nil) #expect(auto?.language.hasPrefix("en") == true) // A specific installed voice resolves to exactly that voice. let target = try? #require(SpeechSettings.availableVoices().first) if let target { UserDefaults.standard.set(target.identifier, forKey: key) #expect(SpeechSettings.resolvedVoice()?.identifier == target.identifier) } } @Test func displayLabelAvoidsRedundantQualitySuffix() { // Default voices: name only, no "— Default" noise. #expect(SpeechSettings.displayLabel(name: "Samantha", quality: .default) == "Samantha") // Enhanced/premium voices get the tier appended when the name lacks it. #expect(SpeechSettings.displayLabel(name: "Samantha", quality: .enhanced) == "Samantha — Enhanced") // …but not when the name already carries it (e.g. "Ava (Premium)"). #expect(SpeechSettings.displayLabel(name: "Ava (Premium)", quality: .premium) == "Ava (Premium)") #expect(SpeechSettings.displayLabel(name: "Daniel (Enhanced)", quality: .enhanced) == "Daniel (Enhanced)") } @Test func prosodyValuesClampToValidRanges() { let rateKey = SpeechSettings.rateKey let originalRate = UserDefaults.standard.object(forKey: rateKey) as? Double defer { if let originalRate { UserDefaults.standard.set(originalRate, forKey: rateKey) } else { UserDefaults.standard.removeObject(forKey: rateKey) } } // An out-of-range stored value is clamped, never passed raw to the utterance. UserDefaults.standard.set(99.0, forKey: rateKey) #expect(Double(SpeechSettings.rate) <= SpeechSettings.rateRange.upperBound) // A missing value resolves to the shared default. UserDefaults.standard.removeObject(forKey: rateKey) #expect(SpeechSettings.rate == Float(SpeechSettings.rateDefault)) } }