Finanzmanager/tests/33_SeitenleisteEinklappenTest.php
fds2610-bot 280664b8a3 Seitenleiste: Marke mit Münzstapel, ein Klick schiebt sie auf Symbolbreite
Die Marke oben in der Seitenleiste trägt jetzt das Coinstack-Symbol aus den
Altdaten und den zweifarbigen Schriftzug Finanz|manager – wie die
sidebar-brand im Steuerkram. Sie ist zugleich der Schalter: Ein Klick schiebt
die Leiste von 232 px auf 64 px zusammen, ein Klick auf das Symbol klappt sie
wieder auf. Die Menüpunkte behalten dabei Ziel und Symbol.

Der Zustand steht in einem Cookie und nicht im localStorage: So rendert der
Server die Leiste schon in der richtigen Breite, statt sie bei jedem
Seitenwechsel kurz breit aufblitzen zu lassen. Die Beschriftungen wandern
eingeklappt per clip-path aus dem Bild statt per display:none – sonst
verlören die Verweise ihren Namen für Vorleseprogramme.

Nebenbei: Die Marke hieß bisher .marke, wie die kleine Statusmarke weiter
unten im Stilblatt, deren Regeln sie überschrieben. Sie heißt jetzt
.seitenleisten-marke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:45:45 +02:00

231 lines
8.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* Die Marke der Seitenleiste trägt das Coinstack-Symbol und den zweifarbigen
* Schriftzug – und dient zugleich als Schalter: Ein Klick schiebt die Leiste
* auf Symbolbreite zusammen, ein zweiter klappt sie wieder auf.
*/
class SeitenleisteEinklappenTest extends TestCase
{
protected string $testName = 'Test 33: Seitenleiste ein- und ausklappen';
public function run(): void
{
$this->markeZeigtSymbolUndSchriftzug();
$this->markeIstDerSchalter();
$this->cookieEntscheidetUeberDieBreite();
$this->stilblattSchiebtNurDieBeschriftungenWeg();
$this->skriptMerktSichDenZustand();
}
private function markeZeigtSymbolUndSchriftzug(): void
{
$symbol = dirname(__DIR__) . '/public/static/coinstack.png';
$this->assert(is_file($symbol), 'Das Markensymbol liegt unter public/static/coinstack.png');
$this->assertEquals('image/png', (string)(getimagesize($symbol)['mime'] ?? ''), 'Das Symbol ist ein PNG');
$leiste = $this->seitenleiste($this->rendere('/'));
$this->assert(
str_contains($leiste, '<img src="/static/coinstack.png" alt="" class="marken-symbol">'),
'Die Marke führt das Coinstack-Symbol'
);
$this->assert(
str_contains($leiste, '<span class="marken-text">Finanz<span>manager</span></span>'),
'Der Schriftzug ist wie im Steuerkram zweigeteilt'
);
$this->assert(
!str_contains($leiste, '<div class="marke">'),
'Die alte Marke als Statusmarken-Div ist verschwunden'
);
}
private function markeIstDerSchalter(): void
{
$leiste = $this->seitenleiste($this->rendere('/'));
$this->assert(
(bool)preg_match('#<button type="button" class="seitenleisten-marke" data-seitenleiste-schalter#', $leiste),
'Die Marke ist ein Schalter'
);
$this->assert(
str_contains($leiste, 'aria-controls="seitenleiste"'),
'Der Schalter nennt die Seitenleiste, die er bedient'
);
$this->assert(
str_contains($leiste, 'id="seitenleiste"'),
'Die Seitenleiste trägt die angesprochene Kennung'
);
// Die Symbole behalten ihr Ziel; nur die Beschriftung bekommt eine
// eigene Hülle, damit sie sich eingeklappt wegschieben lässt.
foreach (['/', '/konten', '/banking', '/zahlungsverkehr', '/auswertungen', '/planung', '/wertpapiere', '/verwaltung'] as $ziel) {
$this->assert(
(bool)preg_match('#href="' . preg_quote($ziel, '#') . '"#', $leiste),
'Der Menüpunkt ' . $ziel . ' bleibt erreichbar'
);
}
$this->assertEquals(
8,
substr_count($leiste, '<span class="beschriftung">'),
'Jeder Menüpunkt trägt seine Beschriftung in einer eigenen Hülle'
);
$this->assertEquals(
8,
substr_count($leiste, '<span class="icon" aria-hidden="true">'),
'Und jeder behält sein Symbol'
);
$this->assert(
str_contains($leiste, 'title="Kontenübersicht"'),
'Eingeklappt verrät der Titel, wohin ein Symbol führt'
);
}
private function cookieEntscheidetUeberDieBreite(): void
{
$offen = $this->rendere('/');
$this->assert(str_contains($offen, '<div class="rahmen">'), 'Ohne Cookie steht die Leiste offen');
$this->assert(str_contains($offen, 'aria-expanded="true"'), 'Der Schalter meldet die offene Leiste');
$this->assert(str_contains($offen, 'title="Seitenleiste einklappen"'), 'Und lädt zum Einklappen ein');
$eingeklappt = $this->rendere('/', 'eingeklappt');
$this->assert(
str_contains($eingeklappt, '<div class="rahmen eingeklappt">'),
'Mit Cookie rendert der Server die schmale Leiste sofort'
);
$this->assert(str_contains($eingeklappt, 'aria-expanded="false"'), 'Der Schalter meldet die schmale Leiste');
$this->assert(
str_contains($eingeklappt, 'title="Seitenleiste ausklappen"'),
'Und lädt zum Ausklappen ein'
);
$unsinn = $this->rendere('/', 'irgendwas');
$this->assert(
str_contains($unsinn, '<div class="rahmen">'),
'Ein fremder Cookie-Wert lässt die Leiste offen'
);
}
private function stilblattSchiebtNurDieBeschriftungenWeg(): void
{
$stil = $this->stilblatt();
$this->assert(
(bool)preg_match('#\.rahmen\.eingeklappt\s*\{[^}]*grid-template-columns:\s*64px 1fr#', $stil),
'Eingeklappt bleibt nur eine Symbolspalte stehen'
);
$this->assert(
(bool)preg_match('#\.rahmen\s*\{[^}]*grid-template-columns:\s*232px 1fr#', $stil),
'Offen behält die Leiste ihre ursprüngliche Breite'
);
// display:none nähme den Verweisen ihren Namen – deshalb wird die
// Beschriftung nur aus dem Bild geschoben.
$this->assert(
(bool)preg_match(
'#\.rahmen\.eingeklappt \.marken-text,\s*\.rahmen\.eingeklappt \.beschriftung\s*\{[^}]*clip-path:\s*inset\(50%\)#',
$stil
),
'Die Beschriftungen verschwinden aus dem Bild, nicht aus dem Baum'
);
$this->assert(
!preg_match('#\.rahmen\.eingeklappt \.marken-symbol[^{]*\{[^}]*display:\s*none#', $stil),
'Das Markensymbol bleibt eingeklappt sichtbar'
);
$this->assert(
(bool)preg_match('#\.seitenleisten-marke\s*\{[^}]*cursor:\s*pointer#', $stil),
'Die Marke zeigt sich als anklickbar'
);
$this->assert(
(bool)preg_match('#\.marken-text span\s*\{\s*color:\s*var\(--navi-marke\)#', $stil),
'Die zweite Worthälfte trägt die Akzentfarbe'
);
$this->assert(
(bool)preg_match('#--navi-marke:\s*#', $stil),
'Die Akzentfarbe steht als Custom Property'
);
// In schmalen Fenstern liegt die Navigation waagerecht - dort gibt es
// nichts einzusparen.
$schmal = substr($stil, (int)strpos($stil, '@media (max-width: 820px)'));
$this->assert(
(bool)preg_match('#\.rahmen,\s*\.rahmen\.eingeklappt\s*\{\s*grid-template-columns:\s*1fr#', $schmal),
'Im schmalen Fenster spielt das Einklappen keine Rolle'
);
$this->assert(
(bool)preg_match('#\.rahmen\.eingeklappt \.beschriftung\s*\{[^}]*clip-path:\s*none#', $schmal),
'Und die Beschriftungen bleiben dort lesbar'
);
}
private function skriptMerktSichDenZustand(): void
{
$skript = (string)file_get_contents(dirname(__DIR__) . '/public/static/app.js');
$this->assert(
str_contains($skript, "querySelector('[data-seitenleiste-schalter]')"),
'Das Skript findet den Schalter'
);
$this->assert(
str_contains($skript, "rahmen.classList.toggle('eingeklappt')"),
'Ein Klick klappt die Leiste um'
);
$this->assert(
(bool)preg_match("#document\.cookie = 'seitenleiste='#", $skript),
'Der Zustand landet im Cookie, damit der Server ihn kennt'
);
$this->assert(
str_contains($skript, 'samesite=lax'),
'Das Cookie reist nicht zu fremden Seiten mit'
);
$this->assert(
str_contains($skript, "schalter.setAttribute('aria-expanded'"),
'Der Schalter meldet den neuen Zustand'
);
}
private function stilblatt(): string
{
return (string)file_get_contents(dirname(__DIR__) . '/public/static/app.css');
}
private function rendere(string $pfad, ?string $cookie = null): string
{
$vorher = $_COOKIE['seitenleiste'] ?? null;
if ($cookie === null) {
unset($_COOKIE['seitenleiste']);
} else {
$_COOKIE['seitenleiste'] = $cookie;
}
$titel = 'Test';
$flash = [];
$benutzer = null;
$csrfToken = 'x';
$aktuellerPfad = $pfad;
ob_start();
include dirname(__DIR__) . '/src/Views/layout/kopf.php';
$html = (string)ob_get_clean();
if ($vorher === null) {
unset($_COOKIE['seitenleiste']);
} else {
$_COOKIE['seitenleiste'] = $vorher;
}
return $html;
}
/** Nur die Seitenleiste – Verweise im Inhalt sollen nicht mitzählen. */
private function seitenleiste(string $html): string
{
$anfang = (int)strpos($html, '<nav');
$ende = (int)strpos($html, '</nav>');
return substr($html, $anfang, $ende - $anfang);
}
}