Flixy heißt ab hier Halo
Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit, damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind. 712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25 Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit `HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`, `JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben. `docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben hiesse, die Vergangenheit zu glätten. **Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis: `com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container; Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen. **Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter `…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet. Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht statt umbenannt — sie steht ohnehin nicht im Repo. Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist ein Handgriff ausserhalb des Projekts. 368 Tests grün. iOS und tvOS bauen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2fcf6821c8
commit
4b9a1e7b67
@@ -0,0 +1,115 @@
|
||||
import Testing
|
||||
import MediaKit
|
||||
@testable import HaloPlayback
|
||||
|
||||
@Suite("Was im Spurenmenü steht")
|
||||
struct TrackMenuTests {
|
||||
|
||||
private func spur(
|
||||
_ id: String,
|
||||
kind: MediaStream.Kind = .audio,
|
||||
sprache: String? = "Deutsch",
|
||||
code: String? = "deu",
|
||||
name: String? = nil,
|
||||
codec: String? = "AC3"
|
||||
) -> MediaStream {
|
||||
MediaStream(
|
||||
id: id, kind: kind, codec: codec, language: sprache, languageCode: code,
|
||||
displayTitle: name, channels: nil, isSelected: false, isForced: false,
|
||||
title: nil, isDefault: false, isHearingImpaired: false, isOriginal: false
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Wann es einen Neuaufbau kostet
|
||||
|
||||
/// **Der Fall, der bisher nirgends stand.** Rechnet der Server um, liegt
|
||||
/// im Stream genau eine Tonspur — jede andere Wahl verlangt eine neue
|
||||
/// Bestellung und damit Sekunden mit schwarzem Bild.
|
||||
@Test("Beim Umrechnen kostet jede Wahl einen Neuaufbau")
|
||||
func transcodeAlwaysRebuilds() {
|
||||
let einträge = TrackMenu.audio(
|
||||
from: [spur("1"), spur("2")],
|
||||
selected: nil, isDirectPlay: false, playerTrackCount: 1
|
||||
)
|
||||
#expect(einträge.allSatisfy { $0.rebuilds })
|
||||
}
|
||||
|
||||
@Test("Unverändert gespielt und gleiche Anzahl: kein Neuaufbau")
|
||||
func directPlayMatchingCountIsCheap() {
|
||||
let einträge = TrackMenu.audio(
|
||||
from: [spur("1"), spur("2")],
|
||||
selected: nil, isDirectPlay: true, playerTrackCount: 2
|
||||
)
|
||||
#expect(einträge.allSatisfy { !$0.rebuilds })
|
||||
}
|
||||
|
||||
/// **Die Regel aus `PlayerModel`, zum ersten Mal prüfbar.** Die Zuordnung
|
||||
/// geht über die Reihenfolge; stimmt die Anzahl nicht, ist die Annahme
|
||||
/// falsch, und es wäre die falsche Sprache statt einer Unterbrechung.
|
||||
/// Auslösen liess sich das bisher nur an einem echten Server.
|
||||
@Test("Abweichende Anzahl heißt Neuaufbau, auch unverändert gespielt")
|
||||
func directPlayMismatchRebuilds() {
|
||||
let einträge = TrackMenu.audio(
|
||||
from: [spur("1"), spur("2"), spur("3")],
|
||||
selected: nil, isDirectPlay: true, playerTrackCount: 2
|
||||
)
|
||||
#expect(einträge.allSatisfy { $0.rebuilds })
|
||||
}
|
||||
|
||||
// MARK: - Untertitel
|
||||
|
||||
@Test("„Aus“ steht vorn und kostet nie einen Neuaufbau")
|
||||
func offComesFirstAndIsFree() {
|
||||
let einträge = TrackMenu.subtitles(
|
||||
from: [spur("1", kind: .subtitle)],
|
||||
selected: nil, isDirectPlay: false, offLabel: "Aus"
|
||||
)
|
||||
#expect(einträge.first?.id == nil)
|
||||
#expect(einträge.first?.title == "Aus")
|
||||
#expect(einträge.first?.isSelected == true)
|
||||
#expect(einträge.first?.rebuilds == false)
|
||||
}
|
||||
|
||||
/// Nur was der Player wirklich hat, lässt sich ohne Neuaufbau schalten.
|
||||
@Test("Eine Untertitelspur, die der Player nicht hat, kostet einen Neuaufbau")
|
||||
func unmappableSubtitleRebuilds() {
|
||||
let deutsch = spur("1", kind: .subtitle, sprache: "Deutsch", code: "deu")
|
||||
let englisch = spur("2", kind: .subtitle, sprache: "English", code: "eng")
|
||||
let einträge = TrackMenu.subtitles(
|
||||
from: [deutsch, englisch],
|
||||
selected: nil, isDirectPlay: true,
|
||||
playerTrackLanguages: ["de"], offLabel: "Aus"
|
||||
)
|
||||
// „deu" und „de" sind dieselbe Sprache — das entscheidet LanguageCode.
|
||||
#expect(einträge[1].rebuilds == false)
|
||||
#expect(einträge[2].rebuilds == true)
|
||||
}
|
||||
|
||||
// MARK: - Beschriftung
|
||||
|
||||
@Test("Der Name des Servers gewinnt, sonst Sprache und Codec")
|
||||
func fallsBackToLanguageAndCodec() {
|
||||
let mitNamen = spur("1", name: "Deutsch (AC3 5.1)")
|
||||
let ohneNamen = spur("2", sprache: "English", codec: "eac3")
|
||||
let einträge = TrackMenu.audio(
|
||||
from: [mitNamen, ohneNamen],
|
||||
selected: mitNamen, isDirectPlay: true, playerTrackCount: 2
|
||||
)
|
||||
#expect(einträge[0].title == "Deutsch (AC3 5.1)")
|
||||
#expect(einträge[1].title == "English · EAC3")
|
||||
#expect(einträge[0].isSelected)
|
||||
#expect(!einträge[1].isSelected)
|
||||
}
|
||||
|
||||
/// Ohne alles bleibt ein Strich — besser als eine leere Zeile, bei der
|
||||
/// niemand weiss, ob sie anklickbar ist.
|
||||
@Test("Ohne Sprache und Codec bleibt ein Strich")
|
||||
func namelessStreamStillHasALabel() {
|
||||
let leer = spur("7", sprache: nil, code: nil, codec: nil)
|
||||
let einträge = TrackMenu.audio(
|
||||
from: [leer], selected: nil, isDirectPlay: true, playerTrackCount: 1
|
||||
)
|
||||
#expect(einträge[0].title == "—")
|
||||
#expect(einträge[0].id == 7)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user