Merge pull request 'Seitenleiste: Marke mit Münzstapel, ein Klick schiebt sie auf Symbolbreite' (#36) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/36
This commit is contained in:
commit
b0675dc58b
7 changed files with 440 additions and 9 deletions
23
Changes.md
23
Changes.md
|
|
@ -1,5 +1,28 @@
|
|||
# Änderungen
|
||||
|
||||
## Seitenleiste: Marke mit Münzstapel, ein Klick schiebt sie auf Symbolbreite (18.08.2026)
|
||||
|
||||
Oben in der Seitenleiste steht jetzt eine richtige **Marke**: das Bild `Coinstack.png` aus den
|
||||
Altdaten als Symbol (`public/static/coinstack.png`, auf 128 px gerechnet) und daneben der
|
||||
zweifarbige Schriftzug **Finanz**manager – dieselbe Machart wie die `sidebar-brand` im
|
||||
Schwesterprojekt Steuerkram.
|
||||
|
||||
Die Marke ist zugleich der **Schalter**: Ein Klick schiebt die Seitenleiste von 232 px auf
|
||||
64 px zusammen, dann stehen dort nur noch das Markensymbol und die Symbole der Menüpunkte –
|
||||
mit unveränderten Zielen; der gewonnene Platz geht an den Hauptbereich. Ein Klick auf das
|
||||
Symbol ganz oben klappt sie wieder auf ihre alte Breite auf.
|
||||
|
||||
Zwei Feinheiten: Der Zustand steht in einem **Cookie**, nicht im `localStorage` – so rendert
|
||||
der Server die Leiste schon in der richtigen Breite, statt sie bei jedem Seitenwechsel kurz
|
||||
breit aufblitzen zu lassen. Und die Beschriftungen werden eingeklappt nur **aus dem Bild
|
||||
geschoben** (`clip-path`), nicht mit `display:none` entfernt: Sonst verlören die Verweise
|
||||
ihren Namen für Vorleseprogramme. In schmalen Fenstern, wo die Navigation ohnehin waagerecht
|
||||
liegt, bleibt alles wie gehabt.
|
||||
|
||||
Nebenbei behoben: Die Marke der Seitenleiste hieß bisher schlicht `.marke` – dieselbe Klasse
|
||||
wie die kleine Statusmarke weiter unten im Stilblatt, deren Regeln sie überschrieben. Sie
|
||||
heißt jetzt `.seitenleisten-marke`.
|
||||
|
||||
## Bankleitzahlen-Verzeichnis: die FinTS-Adresse nachschlagen statt suchen (18.08.2026)
|
||||
|
||||
Beim Anlegen einer FinTS-Verbindung fehlt in aller Regel genau eine Angabe: der
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
--navi-text: #cdd7e2;
|
||||
--navi-text-aktiv: #ffffff;
|
||||
--navi-aktiv: #1f6feb;
|
||||
--navi-marke: #6ea8fe;
|
||||
--positiv: #1a7f4b;
|
||||
--negativ: #c0392b;
|
||||
--warnung: #9a6700;
|
||||
|
|
@ -117,14 +118,41 @@ code {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.marke {
|
||||
/* Die Marke der Seitenleiste hat eine eigene Klasse: ".marke" allein ist
|
||||
weiter unten die kleine Statusmarke, deren Regeln hier sonst gewönnen. */
|
||||
.seitenleisten-marke {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, .08);
|
||||
font-family: inherit;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
padding: 1.1rem 1.25rem;
|
||||
letter-spacing: .01em;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.seitenleisten-marke:hover { background: rgba(255, 255, 255, .05); }
|
||||
|
||||
|
||||
.marken-symbol {
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.marken-text { color: #fff; white-space: nowrap; }
|
||||
|
||||
/* Wie im Steuerkram: die zweite Worthälfte trägt die Akzentfarbe. */
|
||||
.marken-text span { color: var(--navi-marke); }
|
||||
|
||||
.seitenleiste ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
|
@ -166,6 +194,32 @@ code {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Eingeklappte Seitenleiste ───────────────────────────────────────────── */
|
||||
|
||||
/* Ein Klick auf die Marke schiebt die Leiste auf Symbolbreite zusammen. Die
|
||||
Beschriftungen verschwinden nur aus dem Bild, nicht aus dem Baum – sonst
|
||||
verlören die Verweise ihren Namen für Vorleseprogramme. */
|
||||
.rahmen.eingeklappt { grid-template-columns: 64px 1fr; }
|
||||
|
||||
.rahmen.eingeklappt .seitenleisten-marke,
|
||||
.rahmen.eingeklappt .seitenleiste a {
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
|
||||
.rahmen.eingeklappt .marken-text,
|
||||
.rahmen.eingeklappt .beschriftung {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Werkzeugleiste ──────────────────────────────────────────────────────── */
|
||||
|
||||
.werkzeugleiste {
|
||||
|
|
@ -2070,7 +2124,25 @@ table.liste tr.luecke td {
|
|||
/* ── Schmale Fenster ─────────────────────────────────────────────────────── */
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.rahmen { grid-template-columns: 1fr; }
|
||||
.rahmen,
|
||||
.rahmen.eingeklappt { grid-template-columns: 1fr; }
|
||||
|
||||
/* Waagerecht gibt es nichts zu sparen: Beschriftungen bleiben sichtbar. */
|
||||
.rahmen.eingeklappt .marken-text,
|
||||
.rahmen.eingeklappt .beschriftung {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
clip-path: none;
|
||||
}
|
||||
|
||||
.rahmen.eingeklappt .seitenleisten-marke,
|
||||
.rahmen.eingeklappt .seitenleiste a {
|
||||
justify-content: flex-start;
|
||||
gap: .6rem;
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.seitenleiste {
|
||||
position: static;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,24 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Die Marke klappt die Seitenleiste auf Symbolbreite zusammen und wieder
|
||||
// auf. 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.
|
||||
const schalter = document.querySelector('[data-seitenleiste-schalter]');
|
||||
const rahmen = document.querySelector('.rahmen');
|
||||
if (schalter && rahmen) {
|
||||
schalter.addEventListener('click', function () {
|
||||
const eingeklappt = rahmen.classList.toggle('eingeklappt');
|
||||
|
||||
schalter.setAttribute('aria-expanded', eingeklappt ? 'false' : 'true');
|
||||
schalter.title = eingeklappt ? 'Seitenleiste ausklappen' : 'Seitenleiste einklappen';
|
||||
|
||||
document.cookie = 'seitenleiste=' + (eingeklappt ? 'eingeklappt' : 'offen')
|
||||
+ '; path=/; max-age=31536000; samesite=lax';
|
||||
});
|
||||
}
|
||||
|
||||
// Formulare mit data-bestaetigen fragen vor dem Absenden nach.
|
||||
// In der Capture-Phase, damit die Rückfrage auch dann greift, wenn ein
|
||||
// anderer Handler das Ereignis unterwegs stoppt.
|
||||
|
|
|
|||
BIN
public/static/coinstack.png
Normal file
BIN
public/static/coinstack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -20,6 +20,11 @@ $navigation = [
|
|||
['pfad' => '/verwaltung', 'name' => 'Verwaltung', 'icon' => '⚙', 'auch' => ['/termine', '/vertraege']],
|
||||
];
|
||||
|
||||
// Ob die Seitenleiste eingeklappt ist, merkt sich ein Cookie. Das Skript
|
||||
// setzt es beim Umschalten; hier entscheidet es schon beim Rendern, damit die
|
||||
// breite Leiste nicht bei jedem Seitenwechsel kurz aufblitzt.
|
||||
$eingeklappt = ($_COOKIE['seitenleiste'] ?? '') === 'eingeklappt';
|
||||
|
||||
/** @param list<string> $auch */
|
||||
$istAktiv = static function (string $pfad, array $auch = []) use ($aktuellerPfad): bool {
|
||||
if (!is_string($aktuellerPfad)) {
|
||||
|
|
@ -50,16 +55,26 @@ $istAktiv = static function (string $pfad, array $auch = []) use ($aktuellerPfad
|
|||
</head>
|
||||
<body>
|
||||
<?php include __DIR__ . '/meldungen.php'; ?>
|
||||
<div class="rahmen">
|
||||
<nav class="seitenleiste" aria-label="Hauptnavigation">
|
||||
<div class="marke">Finanzmanager</div>
|
||||
<div class="rahmen<?= $eingeklappt ? ' eingeklappt' : '' ?>">
|
||||
<nav class="seitenleiste" id="seitenleiste" aria-label="Hauptnavigation">
|
||||
<!-- Die Marke ist zugleich der Schalter: Ein Klick schiebt die
|
||||
Seitenleiste auf Symbolbreite zusammen und wieder auf. -->
|
||||
<button type="button" class="seitenleisten-marke" data-seitenleiste-schalter
|
||||
aria-controls="seitenleiste" aria-expanded="<?= $eingeklappt ? 'false' : 'true' ?>"
|
||||
title="<?= $eingeklappt ? 'Seitenleiste ausklappen' : 'Seitenleiste einklappen' ?>">
|
||||
<img src="/static/coinstack.png" alt="" class="marken-symbol">
|
||||
<span class="marken-text">Finanz<span>manager</span></span>
|
||||
</button>
|
||||
<ul>
|
||||
<?php foreach ($navigation as $eintrag): ?>
|
||||
<li>
|
||||
<?php /* title bleibt hinten: Der Navigationstest prüft href und
|
||||
class in dieser Reihenfolge unmittelbar hintereinander. */ ?>
|
||||
<a href="<?= View::h($eintrag['pfad']) ?>"
|
||||
class="<?= $istAktiv($eintrag['pfad'], $eintrag['auch'] ?? []) ? 'aktiv' : '' ?>">
|
||||
class="<?= $istAktiv($eintrag['pfad'], $eintrag['auch'] ?? []) ? 'aktiv' : '' ?>"
|
||||
title="<?= View::h($eintrag['name']) ?>">
|
||||
<span class="icon" aria-hidden="true"><?= $eintrag['icon'] ?></span>
|
||||
<?= View::h($eintrag['name']) ?>
|
||||
<span class="beschriftung"><?= View::h($eintrag['name']) ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
231
tests/33_SeitenleisteEinklappenTest.php
Normal file
231
tests/33_SeitenleisteEinklappenTest.php
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
72
tests/e2e/specs/seitenleiste.spec.js
Normal file
72
tests/e2e/specs/seitenleiste.spec.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test')
|
||||
|
||||
const BENUTZER = process.env.E2E_USER || 'e2e'
|
||||
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
|
||||
|
||||
async function anmelden(page) {
|
||||
await page.goto('/login')
|
||||
test.skip(page.url().includes('/einrichtung'), 'Instanz noch nicht eingerichtet')
|
||||
|
||||
await page.fill('#benutzername', BENUTZER)
|
||||
await page.fill('#passwort', PASSWORT)
|
||||
await page.click('button[type="submit"]')
|
||||
await expect(page).toHaveURL(/\/$/)
|
||||
}
|
||||
|
||||
test.describe('Seitenleiste ein- und ausklappen', () => {
|
||||
test('Klick auf die Marke schiebt die Leiste auf Symbolbreite und zurück', async ({ page }) => {
|
||||
await anmelden(page)
|
||||
|
||||
const rahmen = page.locator('.rahmen')
|
||||
const marke = page.locator('[data-seitenleiste-schalter]')
|
||||
const symbol = page.locator('.marken-symbol')
|
||||
const beschriftung = page.locator('.seitenleiste .beschriftung').first()
|
||||
|
||||
await expect(symbol).toBeVisible()
|
||||
await expect(beschriftung).toBeVisible()
|
||||
const breitOffen = (await page.locator('.seitenleiste').boundingBox()).width
|
||||
|
||||
await marke.click()
|
||||
|
||||
await expect(rahmen).toHaveClass(/eingeklappt/)
|
||||
await expect(marke).toHaveAttribute('aria-expanded', 'false')
|
||||
// Das Markensymbol und die Symbole der Menüpunkte bleiben stehen,
|
||||
// nur die Beschriftungen verschwinden.
|
||||
await expect(symbol).toBeVisible()
|
||||
await expect(beschriftung).toBeHidden()
|
||||
await expect(page.locator('.seitenleiste .icon').first()).toBeVisible()
|
||||
|
||||
const breitEingeklappt = (await page.locator('.seitenleiste').boundingBox()).width
|
||||
expect(breitEingeklappt).toBeLessThan(breitOffen)
|
||||
|
||||
// Der Hauptbereich bekommt den gewonnenen Platz.
|
||||
const hauptOffen = breitOffen
|
||||
expect((await page.locator('.hauptbereich').boundingBox()).width).toBeGreaterThan(0)
|
||||
|
||||
await marke.click()
|
||||
await expect(rahmen).not.toHaveClass(/eingeklappt/)
|
||||
await expect(marke).toHaveAttribute('aria-expanded', 'true')
|
||||
await expect(beschriftung).toBeVisible()
|
||||
expect((await page.locator('.seitenleiste').boundingBox()).width).toBe(hauptOffen)
|
||||
})
|
||||
|
||||
test('Die Symbole führen eingeklappt zu denselben Seiten', async ({ page }) => {
|
||||
await anmelden(page)
|
||||
|
||||
await page.locator('[data-seitenleiste-schalter]').click()
|
||||
await expect(page.locator('.rahmen')).toHaveClass(/eingeklappt/)
|
||||
|
||||
await page.locator('.seitenleiste a[href="/konten"]').click()
|
||||
await expect(page).toHaveURL(/\/konten/)
|
||||
await expect(page.locator('h1')).toContainText('Kontenübersicht')
|
||||
|
||||
// Der Zustand hält über den Seitenwechsel hinweg – ohne Aufblitzen,
|
||||
// weil der Server ihn schon aus dem Cookie kennt.
|
||||
await expect(page.locator('.rahmen')).toHaveClass(/eingeklappt/)
|
||||
await expect(page.locator('.seitenleiste .beschriftung').first()).toBeHidden()
|
||||
|
||||
await page.locator('[data-seitenleiste-schalter]').click()
|
||||
await expect(page.locator('.rahmen')).not.toHaveClass(/eingeklappt/)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue