Die Ansichten sprechen nicht mehr mit Plex

Start, Suche, Mediathek, Detailseiten und Downloads arbeiten jetzt mit
`MediaItem` und dem `MediaBackend`-Vertrag. Damit sind sie fertig für
einen zweiten Server, ohne dass an ihnen noch etwas zu tun wäre.

Dabei sind ein paar Namen ehrlicher geworden: aus `grandparentTitle`
wurde `seriesTitle`, aus `viewedLeafCount` `playedEpisodeCount`. Und
`TagStrip` nahm bisher Plex-Marken mit Kennung und Rolle entgegen, um
davon nur den Namen anzuzeigen — Genres sind jetzt schlicht Zeichenketten.

`Arrangement` ist aus PlexKit heraus und generisch: dieselbe Rechnung
gilt für jede Liste, die jemand selbst zusammenstellt. Phase 3 baut die
konfigurierbare Seitenleiste darauf, statt sie ein zweites Mal — und ein
zweites Mal falsch — zu schreiben.

Die Fehlerübersetzung gibt es nur noch einmal. Es waren zwei, und sie
waren bereits auseinandergelaufen: derselbe Fehler bekam je nach
Bildschirm eine andere Erklärung.

Das Nachladen der Mediathek bleibt unangetastet: eine lange Liste, die
fünf Einträge vor dem Ende nachlädt. `MediaPage` heißt zwar Seite, ist
aber eine technische Portion — Blättern mit „weiter zu Seite 2" gehört
nicht in eine Mediathek, in der man stöbert. Der Kommentar dort sagt das
jetzt ausdrücklich, damit es niemand später „aufräumt".

Player, Live-TV und die Download-Engine sprechen noch unmittelbar mit
Plex. Sie folgen als Nächstes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-13 12:46:52 +02:00
co-authored by Claude Opus 5
parent effdcd4532
commit fe2d730c00
30 changed files with 432 additions and 318 deletions
@@ -0,0 +1,104 @@
import Foundation
import Testing
@testable import MediaKit
private func hub(_ identifier: String) -> MediaRowGroup {
MediaRowGroup(id: identifier, title: identifier.capitalized)
}
@Suite("Anordnung der Startseiten-Reihen")
struct ArrangementTests {
private let serverHubs = [hub("continue"), hub("recent"), hub("watchlist"), hub("collections")]
@Test("Ohne Einstellungen bleibt die Reihenfolge des Servers")
func defaultKeepsServerOrder() {
let result = Arrangement().apply(to: serverHubs)
#expect(result.map(\.id) == ["continue", "recent", "watchlist", "collections"])
}
@Test("Ausgeblendete Reihen verschwinden")
func hiddenRowsAreRemoved() {
let arrangement = Arrangement(hidden: ["watchlist", "collections"])
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["continue", "recent"])
}
@Test("Die gespeicherte Reihenfolge wird angewendet")
func storedOrderIsApplied() {
let arrangement = Arrangement(order: ["watchlist", "continue", "recent", "collections"])
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["watchlist", "continue", "recent", "collections"])
}
@Test("Neue Reihen vom Server landen am Ende statt zu verschwinden")
func unknownRowsAppearAtTheEnd() {
// Der Fall, der die einfachere Regel — nur Bekanntes anzeigen — zur
// Falle macht: ein neu angelegtes Regal auf dem Server wäre in der App
// nie aufgetaucht, ohne dass jemand den Grund gesehen hätte.
let arrangement = Arrangement(order: ["recent", "continue"])
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["recent", "continue", "watchlist", "collections"])
}
@Test("Mehrere neue Reihen behalten die Reihenfolge des Servers")
func multipleUnknownRowsKeepServerOrder() {
// Ohne stabile Rückfalllinie wäre ihre Anordnung zufällig — und
// änderte sich bei jedem Start.
let arrangement = Arrangement(order: ["collections"])
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["collections", "continue", "recent", "watchlist"])
}
@Test("Gespeicherte Reihen, die es nicht mehr gibt, stören nicht")
func staleOrderEntriesAreIgnored() {
// Entsteht beim Löschen einer Mediathek auf dem Server.
let arrangement = Arrangement(order: ["geloescht", "recent", "continue"])
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["recent", "continue", "watchlist", "collections"])
}
@Test("Ein doppelter Eintrag in der Reihenfolge stürzt nicht ab")
func duplicateOrderEntriesAreTolerated() {
// Kann nach einem abgebrochenen Schreibvorgang entstehen. Ein
// Dictionary aus doppelten Schlüsseln wäre ein harter Absturz.
let arrangement = Arrangement(order: ["recent", "recent", "continue"])
let result = arrangement.apply(to: serverHubs)
#expect(result.first?.id == "recent")
#expect(result.count == 4)
}
@Test("Ausblenden und Sortieren greifen zusammen")
func hidingAndOrderingCombine() {
let arrangement = Arrangement(
hidden: ["recent"],
order: ["collections", "recent", "continue"]
)
let result = arrangement.apply(to: serverHubs)
#expect(result.map(\.id) == ["collections", "continue", "watchlist"])
}
// MARK: - Die Liste für die Einstellungen
@Test("Der Einstellungsbildschirm zeigt Gespeichertes zuerst")
func settingsListPutsStoredFirst() {
let arrangement = Arrangement(order: ["watchlist", "continue"])
let identifiers = arrangement.arrangedIdentifiers(from: serverHubs)
#expect(identifiers == ["watchlist", "continue", "recent", "collections"])
}
@Test("Der Einstellungsbildschirm zeigt auch ausgeblendete Reihen")
func settingsListIncludesHidden() {
// Sonst ließe sich eine einmal ausgeblendete Reihe nie wieder
// einschalten — eine Einbahnstraße.
let arrangement = Arrangement(hidden: ["recent"])
let identifiers = arrangement.arrangedIdentifiers(from: serverHubs)
#expect(identifiers.contains("recent"))
}
@Test("Verschwundene Reihen erscheinen nicht in den Einstellungen")
func settingsListDropsStaleEntries() {
let arrangement = Arrangement(order: ["geloescht", "recent"])
let identifiers = arrangement.arrangedIdentifiers(from: serverHubs)
#expect(!identifiers.contains("geloescht"))
}
}