import Testing import Foundation import MediaKit @testable import FlixyPlayback @Suite("Sprachkürzel") struct LanguageCodeTests { /// Der Fall, an dem die alte Prüfung lautlos scheiterte: `ger` ist die /// ISO-639-2/B-Form von Deutsch, und ffmpeg schreibt sie in viele Dateien. @Test("ger, deu und de-DE meinen alle Deutsch") func germanHasThreeSpellings() { #expect(LanguageCode.matches("ger", "de")) #expect(LanguageCode.matches("deu", "de")) #expect(LanguageCode.matches("de-DE", "de")) #expect(LanguageCode.matches("GER", "de")) } /// Schweizerdeutsch ist keine Schreibweise von Deutsch. Es gleichzusetzen /// wäre bequem und falsch — wer Hochdeutsch will, bekäme sonst /// Schweizerdeutsch untergeschoben. @Test("Schweizerdeutsch ist nicht Deutsch") func swissGermanStandsAlone() { #expect(!LanguageCode.matches("gsw", "de")) #expect(LanguageCode.matches("gsw", "gsw")) } @Test("Auch die anderen Doppelformen") func otherDoubleForms() { #expect(LanguageCode.matches("fre", "fra")) #expect(LanguageCode.matches("dut", "nl")) #expect(LanguageCode.matches("cze", "cs")) #expect(LanguageCode.matches("chi", "zh")) } @Test("Ohne Angabe kein Treffer") func missingIsNoMatch() { #expect(!LanguageCode.matches(nil, "de")) #expect(!LanguageCode.matches("de", nil)) } } @Suite("Wahl der Tonspur") struct AudioTrackChooserTests { private func spur( _ index: Int, _ sprache: String?, title: String? = nil, channels: Int = 2, isDefault: Bool = false ) -> MediaStream { MediaStream( id: String(index), kind: .audio, codec: "aac", language: sprache, languageCode: sprache, displayTitle: title, channels: channels, title: title, isDefault: isDefault ) } // MARK: - Der ZDF-Fall /// Wörtlich der Ärger, wegen dem es den Typ gibt: die als deutsch /// ausgezeichnete Spur ist die mit Audiodeskription, und die als englisch /// ausgezeichnete ist in Wahrheit die deutsche ohne. private var zdf: [MediaStream] { [ spur(0, "ger", title: "Deutsch (Audiodeskription)", isDefault: true), spur(1, "eng", title: "Deutsch"), ] } @Test("Audiodeskription wird gemieden, auch wenn sie Standard ist") func avoidsAudioDescription() { let gewählt = AudioTrackChooser.choose( from: zdf, wish: .init(language: "de") ) #expect(gewählt?.id == "1") } /// Ohne das Meiden gewinnt die ausgezeichnete Sprache — der Beweis, dass /// der Schalter tatsächlich das Ruder herumreißt und nicht nur dabeisteht. @Test("Ohne das Meiden gewinnt das Kürzel") func withoutAvoidanceTheCodeWins() { let gewählt = AudioTrackChooser.choose( from: zdf, wish: .init(language: "de", avoidsAudioDescription: false) ) #expect(gewählt?.id == "0") } /// Und wenn der Nutzer einmal gewählt hat, gilt das — auch gegen die /// Sprache. @Test("Die gemerkte Spur schlägt den Sprachwunsch") func rememberedBeatsLanguage() { let gemerkt = AudioTrackChooser.TrackSignature(zdf[1]) let gewählt = AudioTrackChooser.choose( from: zdf, wish: .init(language: "de", remembered: gemerkt, avoidsAudioDescription: false) ) #expect(gewählt?.id == "1") } // MARK: - Die Rangliste @Test("Fehlt die Wunschsprache, greift die Rangliste") func fallbackListApplies() { let spuren = [spur(0, "fre"), spur(1, "eng"), spur(2, "ita")] let gewählt = AudioTrackChooser.choose( from: spuren, wish: .init(language: "de", fallbacks: ["gsw", "en", "fr"]) ) #expect(gewählt?.id == "1") } @Test("Die Reihenfolge der Rangliste zählt") func fallbackOrderMatters() { let spuren = [spur(0, "fre"), spur(1, "eng")] let zuerstFranzösisch = AudioTrackChooser.choose( from: spuren, wish: .init(language: "de", fallbacks: ["fr", "en"]) ) #expect(zuerstFranzösisch?.id == "0") } @Test("Schweizerdeutsch lässt sich in die Rangliste stellen") func swissGermanInTheList() { let spuren = [spur(0, "eng"), spur(1, "gsw")] let gewählt = AudioTrackChooser.choose( from: spuren, wish: .init(language: "de", fallbacks: ["gsw", "en"]) ) #expect(gewählt?.id == "1") } // MARK: - Der Boden darunter @Test("Ohne jede Übereinstimmung kommt trotzdem eine Spur") func alwaysAnswers() { let spuren = [spur(0, "jpn"), spur(1, "kor", isDefault: true)] let gewählt = AudioTrackChooser.choose( from: spuren, wish: .init(language: "de", fallbacks: ["en"]) ) // Die Standardspur — nachvollziehbar statt beliebig. #expect(gewählt?.id == "1") } @Test("Gibt es die Sprache nur mit Audiodeskription, ist sie besser als nichts") func audioDescriptionBeatsSilence() { let spuren = [spur(0, "ger", title: "Deutsch (Audiodeskription)")] let gewählt = AudioTrackChooser.choose(from: spuren, wish: .init(language: "de")) #expect(gewählt?.id == "0") } @Test("Ohne Tonspuren gibt es nichts zu wählen") func noAudioNoChoice() { let nurBild = [MediaStream(id: "0", kind: .video)] #expect(AudioTrackChooser.choose(from: nurBild, wish: .init()) == nil) } @Test("Kommentarspuren werden gemieden") func avoidsCommentary() { let spuren = [ spur(0, "ger", title: "Audiokommentar des Regisseurs", isDefault: true), spur(1, "ger", title: "Deutsch"), ] let gewählt = AudioTrackChooser.choose(from: spuren, wish: .init(language: "de")) #expect(gewählt?.id == "1") } /// „AD" als eigenes Wort, nicht als Teil von „Broadcast". @Test("AD erkennt nur das freistehende Wort") func adOnlyAsWholeWord() { let spuren = [ spur(0, "ger", title: "Deutsch Broadcast Mix"), spur(1, "ger", title: "Deutsch AD"), ] let gewählt = AudioTrackChooser.choose(from: spuren, wish: .init(language: "de")) #expect(gewählt?.id == "0") } @Test("Mehr Kanäle gewinnen bei sonst gleichem Stand") func moreChannelsWin() { let spuren = [spur(0, "ger", title: "Deutsch", channels: 2), spur(1, "ger", title: "Deutsch", channels: 6)] let gewählt = AudioTrackChooser.choose(from: spuren, wish: .init(language: "de")) #expect(gewählt?.id == "1") } /// Zweimal dieselbe Datei muss zweimal dasselbe ergeben — sonst ist ein /// Fehler nicht wiederholbar. @Test("Gleichstand entscheidet die Nummer") func tiesAreStable() { let spuren = [spur(1, "ger", title: "Deutsch"), spur(0, "ger", title: "Deutsch")] #expect(AudioTrackChooser.choose(from: spuren, wish: .init(language: "de"))?.id == "0") } } @Suite("Rangfolge der drei Ebenen") struct WishResolutionTests { /// Serie schlägt Mediathek schlägt allgemein. Das ist der ganze Zweck der /// Abstufung — und der einzige Ort, an dem sie steht. @Test("Die Serie gewinnt") func seriesWins() { let wunsch = AudioTrackChooser.resolve(series: "en", library: "fr", general: "de") #expect(wunsch.language == "en") } @Test("Ohne Serie gewinnt die Mediathek") func libraryWinsWithoutSeries() { #expect(AudioTrackChooser.resolve(series: nil, library: "fr", general: "de").language == "fr") } @Test("Ohne beides gilt die allgemeine") func generalIsTheFloor() { #expect(AudioTrackChooser.resolve(series: nil, library: nil, general: "de").language == "de") } /// In den Einstellungen steht eine leere Zeichenkette für „soll der Server /// entscheiden". Sie als Sprache zu behandeln hiesse, die Ebene darüber /// stillschweigend abzuschalten — genau der Fehler, den man erst bemerkt, /// wenn eine Einstellung scheinbar wirkungslos ist. @Test("Leer heisst nichts gesagt, nicht eine Sprache namens leer") func emptyIsNotAnAnswer() { #expect(AudioTrackChooser.resolve(series: "", library: "fr", general: "de").language == "fr") #expect(AudioTrackChooser.resolve(series: " ", library: "", general: "de").language == "de") #expect(AudioTrackChooser.resolve(series: nil, library: nil, general: "").language == nil) } /// Die Rangliste ist keine vierte Ebene: sie gilt überall gleich und wird /// von keiner Ebene überstimmt. @Test("Rangliste und Schalter gehen unverändert durch") func theRestPassesThrough() { let wunsch = AudioTrackChooser.resolve( series: nil, library: nil, general: "de", fallbacks: ["gsw", "en"], avoidsAudioDescription: false ) #expect(wunsch.fallbacks == ["gsw", "en"]) #expect(wunsch.avoidsAudioDescription == false) } /// Der ZDF-Fall durch die ganze Kette: die Mediathek sagt Deutsch, die /// gemerkte Spur sagt etwas anderes — und behält recht. @Test("Die gemerkte Spur überlebt die Rangfolge") func rememberedSurvives() { let spuren = [ MediaStream(id: "0", kind: .audio, language: "ger", languageCode: "ger", channels: 2, title: "Deutsch (Audiodeskription)", isDefault: true), MediaStream(id: "1", kind: .audio, language: "eng", languageCode: "eng", channels: 2, title: "Deutsch"), ] let wunsch = AudioTrackChooser.resolve( series: nil, library: "de", general: "en", remembered: AudioTrackChooser.TrackSignature(spuren[1]) ) #expect(AudioTrackChooser.choose(from: spuren, wish: wunsch)?.id == "1") } }