Files
Flaro/FlixyUITests/LayoutSnapshots.swift
T
ScarriffleandClaude Opus 5 d0f732402c Der Aufmacher endet an der Leiste, und durch sie sieht man nur noch gedämpft
Vier Dinge, die zusammengehören:

Die Spiegelung ist weg. `backgroundExtensionEffect` füllt die Fläche unter der
Leiste, indem er den Bildrand gespiegelt weiterzeichnet — und je größer die zu
füllende Fläche wurde, desto mehr übernahm der Spiegel das Bild. Auf dem iPhone
legte er sich ohne jede Seitenleiste über den Statusbalken. Jetzt läuft das
Bild selbst weiter, über einen negativen Rand.

Es endet dabei an der **Kante der Leiste**, nicht am Fensterrand. Der
Unterschied sind zwölf Punkte, und die waren deutlich zu sehen: neben der
schwebenden Leiste stand ein heller Streifen Bild, den nichts abdeckte.

Das Seitenverhältnis im Karussell musste weichen. Es legte den Aufmacher auf
die Breite des Inhaltsbereichs fest; weil er nach links unter die Leiste
greift, blieb rechts genau so viel schwarz übrig, wie er links gewann.

Und die Maße, in denen das Hintergrundbild angefordert wird, richten sich jetzt
nach dem Rahmen, in dem es landet. Vorher wurden 900×1200 geholt — hochkant —
und in einem 16:9-Rahmen gezeigt: zu sehen war ein Streifen aus der Mitte, oben
und unten fehlte je ein Drittel.

Dazu: auf schmalen Geräten ist der Aufmacher hochkant. In 16:9 blieben auf
einem iPhone keine zweihundertzwanzig Punkte Höhe, und Schriftzug, Zeile,
Beschreibung und zwei Knöpfe lagen übereinander.

Und ein Schleier unter dem Glas der Leiste. Reines Glas zeigt alles, was
dahinter vorbeizieht, und beim Scrollen zieht die ganze Startseite vorbei — aus
den ruhigen Beschriftungen wurde ein unruhiger Brei. Der Schleier nimmt der
Bewegung die Schärfe, ohne die Fläche zuzumauern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:05:07 +02:00

133 lines
5.6 KiB
Swift

import XCTest
/// Fährt die App in die Zustände, über die geredet wird, und legt Bilder davon ab.
///
/// Der Grund für diese Datei ist ein wiederkehrender Fehler: Aussagen über das
/// Aussehen waren Vermutungen, weil niemand hinsehen konnte. Der Simulator
/// kennt keinen Befehl fürs Antippen, und dieses Xcode bringt kein
/// Simulator-Fenster mehr mit, das sich fernsteuern ließe. Ein UI-Test kann
/// beides — tippen und fotografieren.
///
/// Die Bilder landen im Dokumentenordner des Testläufers, der vom Rechner aus
/// über `simctl get_app_container` erreichbar ist. Anhänge im Ergebnisbündel
/// wären der vorgesehene Weg, müssten aber erst wieder herausgeschält werden.
///
/// Die Inhalte kommen aus `Tools/JellyfinStub` — kein echter Server, kein
/// echtes Konto, kein echter Token.
final class LayoutSnapshots: XCTestCase {
override func setUp() {
continueAfterFailure = false
}
/// `XCUIScreen` fotografiert in der Lage des **Geräts**, nicht in der der
/// Oberfläche — im Querformat kommt das Bild dann hochkant und um
/// neunzig Grad gedreht heraus, und jedes Maß daran ist falsch herum.
/// Die App selbst fotografiert richtig herum.
private func shoot(_ app: XCUIApplication, _ name: String) {
let data = app.screenshot().pngRepresentation
let folder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
try? data.write(to: folder.appendingPathComponent("\(name).png"))
}
/// Das Umschalten zwischen Seitenleiste und Leiste oben.
///
/// Beim Umschalten steht die Leiste kurz falsch, bevor sie sich fängt.
/// Fotografiert wird in Serie, weil genau der Zwischenzustand gesucht ist
/// und ein einzelnes Bild danach nichts zeigt.
func testSidebarToggle() throws {
let app = XCUIApplication()
// Die Stellung der Leiste wird gespeichert, und ein voriger Lauf hat
// sie womöglich umgestellt. Ein Startargument überschreibt sie, damit
// jeder Lauf beim selben Bild anfängt.
app.launchArguments = [
"-FlixyStubServer", "http://127.0.0.1:8096",
"-flixy.navigation.placement", "sidebar",
]
app.launch()
XCUIDevice.shared.orientation = .landscapeLeft
XCTAssertTrue(app.staticTexts["Weiterschauen"].waitForExistence(timeout: 20))
Thread.sleep(forTimeInterval: 2)
shoot(app, "10-seitenleiste")
// Über die Beschriftung gesucht, nicht über die Stellung: „der zweite
// Knopf" war einmal der Umschalter und nach einem Umbau die Kontozeile,
// und der Test öffnete fortan das Kontomenü.
let toggle = app.buttons["Navigation nach oben"]
XCTAssertTrue(toggle.waitForExistence(timeout: 5))
toggle.tap()
for step in 0..<8 {
shoot(app, String(format: "11-umschalten-%d", step))
Thread.sleep(forTimeInterval: 0.1)
}
Thread.sleep(forTimeInterval: 2)
shoot(app, "12-oben")
}
/// Dasselbe auf einem schmalen Bildschirm, hochkant.
///
/// Eigener Test, weil dort alles anders ist: keine Seitenleiste, die
/// Leiste unten, und der Aufmacher hochkant statt 16:9.
func testPhoneHome() throws {
let app = XCUIApplication()
app.launchArguments = ["-FlixyStubServer", "http://127.0.0.1:8096"]
app.launch()
XCTAssertTrue(app.staticTexts["Weiterschauen"].waitForExistence(timeout: 20))
Thread.sleep(forTimeInterval: 3)
shoot(app, "20-iphone")
}
func testHomeAndDetail() throws {
let app = XCUIApplication()
// Die Stellung der Leiste wird gespeichert, und ein voriger Lauf hat
// sie womöglich umgestellt. Ein Startargument überschreibt sie, damit
// jeder Lauf beim selben Bild anfängt.
app.launchArguments = [
"-FlixyStubServer", "http://127.0.0.1:8096",
"-flixy.navigation.placement", "sidebar",
]
app.launch()
XCUIDevice.shared.orientation = .landscapeLeft
// Die Startseite muss stehen, bevor fotografiert wird — sonst ist auf
// dem Bild nur der Ladekreis.
XCTAssertTrue(app.staticTexts["Weiterschauen"].waitForExistence(timeout: 20))
Thread.sleep(forTimeInterval: 3)
shoot(app, "01-start-oben")
// Der Aufmacher wechselt nach sieben Sekunden. Kurz davor anfangen
// und in Serie fotografieren: nur so ist der Übergang selbst zu sehen
// und nicht bloß sein Ergebnis.
Thread.sleep(forTimeInterval: 4.2)
for step in 0..<6 {
shoot(app, String(format: "02-wechsel-%d", step))
Thread.sleep(forTimeInterval: 0.12)
}
Thread.sleep(forTimeInterval: 2)
shoot(app, "03-danach")
// Und gescrollt: dort zieht alles an der Seitenleiste vorbei, und was
// dabei durch sie hindurch zu sehen ist, entscheidet, ob sie ruhig
// wirkt oder unruhig.
// Ein gezogener Strich statt `swipeUp`: der Aufmacher fängt Wischgesten
// ab, und `app.scrollViews.firstMatch` ist die **waagerechte** Reihe,
// nicht die Seite. Gezogen wird deshalb tief unten, wo nur die Seite
// selbst zuhört.
func scroll() {
let from = app.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.92))
let to = app.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.35))
from.press(forDuration: 0.05, thenDragTo: to)
Thread.sleep(forTimeInterval: 1.5)
}
scroll()
shoot(app, "04-gescrollt")
scroll()
shoot(app, "05-gescrollt-weiter")
}
}