Files
Flaro/Packages/FlixyKit/Tests/MediaKitTests/HeroSelectionTests.swift
T
ScarriffleandClaude Opus 5 2a2afa8573 Der Aufmacher bleibt stehen, und Folgen bekommen ein Bild
Drei Sachen am Karussell.

Die Vorschlaege wechselten bei jedem Start, obwohl es dafuer laengst ein
Zeitfenster von sechs Stunden gab. Der Grund: das Los hing an der Position
in der Liste, nicht am Titel. Kam einer dazu, verschwand einer, oder
aenderte sich ein Fortschritt, bekamen alle dahinter ein anderes Los — und
„Weiterschauen\" waechst und schrumpft staendig. Jetzt haengt das Los an der
Kennung; wer dazukommt, stoert niemanden. Als eigener Typ mit elf Tests,
weil „es wechselt zu oft\" kein Befund ist, den man debuggen kann.

Folgen zeigten kein Bild. Die Kette Folge → Serie gab es zwar, aber sie
hoerte beim ersten Glied auf, das sich **zusammensetzen** liess — und ob es
das Bild dahinter gibt, stellt sich erst beim Abruf heraus. Die Reihenfolge
wandert deshalb ins Laden: Folge, Staffel, Serie, der Reihe nach, bis eine
Adresse antwortet.

Und auf dem Mac gibt es jetzt Pfeile links und rechts, sobald der Zeiger
ueber dem Aufmacher liegt, sowie Punkte, die unter dem Zeiger wachsen und
auf die man springen kann. Nur dort: auf dem iPad und dem iPhone wischt man
ohnehin, und zwei Knoepfe staenden nur im Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:46:34 +02:00

112 lines
4.5 KiB
Swift

import Foundation
import Testing
@testable import MediaKit
@Suite("Auswahl für den Aufmacher")
struct HeroSelectionTests {
private func titel(_ id: String, backdrop: Bool = true) -> MediaItem {
MediaItem(id: id, kind: .movie, title: "Film \(id)", art: backdrop ? "/art/\(id)" : nil)
}
private var zwanzig: [MediaItem] { (1...20).map { titel(String($0)) } }
/// **Der Fehler, wegen dem es diesen Typ gibt.** Dieselbe Liste, dasselbe
/// Zeitfenster, zweimal gefragt — und zweimal dasselbe. Vorher kam das
/// nur heraus, solange die Liste sich nicht um einen einzigen Eintrag
/// unterschied.
@Test("Gleiche Eingabe, gleiches Ergebnis")
func stableWithinWindow() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
#expect(
HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
== HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
)
}
/// Und der eigentliche Punkt: ein Titel mehr darf die übrigen nicht
/// durcheinanderbringen. Genau das ist bei jedem Neustart passiert —
/// „Weiterschauen" wächst und schrumpft ständig.
@Test("Ein zusätzlicher Titel verschiebt die anderen nicht")
func addingOneKeepsTheRest() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
let vorher = HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
let nachher = HeroSelection.ordered(zwanzig + [titel("99")], at: jetzt).map(\.id)
// Der Neue darf irgendwo stehen; die übrigen müssen in derselben
// Reihenfolge zueinander stehen wie vorher.
#expect(nachher.filter { $0 != "99" } == vorher)
}
@Test("Ein fehlender Titel hinterlässt nur eine Lücke")
func removingOneKeepsTheRest() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
let vorher = HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
let ohne = HeroSelection.ordered(zwanzig.filter { $0.id != "7" }, at: jetzt).map(\.id)
#expect(ohne == vorher.filter { $0 != "7" })
}
/// Sechs Stunden später eine andere Auswahl — sonst wäre es keine
/// Abwechslung, sondern eine feste Reihenfolge.
@Test("Im nächsten Zeitfenster steht es anders")
func changesAcrossWindows() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
let später = jetzt.addingTimeInterval(7 * 3_600)
#expect(
HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
!= HeroSelection.ordered(zwanzig, at: später).map(\.id)
)
}
@Test("Eine Stunde später steht es noch gleich")
func stableAcrossAnHour() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
#expect(
HeroSelection.ordered(zwanzig, at: jetzt).map(\.id)
== HeroSelection.ordered(zwanzig, at: jetzt.addingTimeInterval(1_800)).map(\.id)
)
}
/// Ohne Hintergrundbild ist der Aufmacher eine graue Fläche mit Text.
/// Ausschliessen wäre zu hart — schlechter losen ist genug.
@Test("Titel ohne Hintergrundbild landen meistens hinten")
func backdropsWin() {
let jetzt = Date(timeIntervalSince1970: 1_800_000_000)
let gemischt = (1...10).map { titel("mit\($0)") }
+ (1...10).map { titel("ohne\($0)", backdrop: false) }
let ersteFünf = HeroSelection.ordered(gemischt, at: jetzt).prefix(5).map(\.id)
#expect(ersteFünf.allSatisfy { $0.hasPrefix("mit") })
}
/// Gäbe es nur Titel ohne Bild, dürfte nicht einfach nichts kommen.
@Test("Gibt es nur Titel ohne Bild, kommen sie trotzdem")
func withoutBackdropsStillReturns() {
let ohne = (1...5).map { titel(String($0), backdrop: false) }
#expect(HeroSelection.ordered(ohne).count == 5)
}
@Test("Eine leere Liste bleibt leer")
func emptyStaysEmpty() {
#expect(HeroSelection.ordered([]).isEmpty)
}
/// **Nicht `hashValue`.** Swift streut Hashes je Programmlauf zufällig —
/// damit wäre die Auswahl nach jedem Start eine andere, also genau der
/// Fehler, der hier behoben werden soll.
@Test("Der Streuwert hängt nur am Text")
func hashIsStable() {
#expect(HeroSelection.stableHash("abc") == HeroSelection.stableHash("abc"))
#expect(HeroSelection.stableHash("abc") != HeroSelection.stableHash("abd"))
}
@Test("Die Lose liegen zwischen null und eins")
func lotsAreInRange() {
for id in 1...200 {
let los = HeroSelection.unitRandom(seed: UInt64(id))
#expect(los >= 0 && los < 1)
}
}
}