Finanzmanager/tests/66_TaschenrechnerTest.php
fds2610-bot d1db40fa55 Fix #126: Taschenrechner an den Betragsfeldern
Jedes Zahlenfeld (inputmode="decimal") zeigt rechts ein Rechnersymbol.
Klick darauf oder Alt+R öffnet einen Taschenrechner mit Grundrechenarten
und Klammern, komplett per Tastatur bedienbar: Ziffern, + - * / ( ),
Backspace, Entf, Pfeiltasten, C löscht alles, = setzt das Ergebnis ein,
Enter überträgt ins Feld, Escape schließt und fokussiert das Feld. Das
Ergebnis steht nach jedem Tastendruck unter der Rechnung.

Das Symbol ist ein Hintergrundbild (kein Wrapper-Element, kein Eingriff in
die Layouts); gerechnet wird mit eigenem Parser statt eval. In modalen
Dialogen wandert der Rechner mit hinein. Stück/Kurs übernehmen 6,
Zinssätze 3 Nachkommastellen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:22:18 +02:00

102 lines
3.8 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);
/**
* Taschenrechner an den Zahlenfeldern (Fix #126). Das Verhalten im Browser
* prüft tests/e2e/specs/rechner.spec.js; hier nur, dass er überall ankommt:
* eingebunden im Seitenrahmen, Symbol im Stilblatt, genaue Übernahme bei
* Stückzahl, Kurs und Zinssatz.
*/
class TaschenrechnerTest extends TestCase
{
protected string $testName = 'Test 66: Taschenrechner an Betragsfeldern';
private string $wurzel;
public function run(): void
{
$this->wurzel = dirname(__DIR__);
$this->seitenrahmenBindetRechnerEin();
$this->stilblattZeigtSymbol();
$this->skriptRechnetOhneEval();
$this->genaueFelderTragenNachkommastellen();
}
private function seitenrahmenBindetRechnerEin(): void
{
$kopf = $this->lies('src/Views/layout/kopf.php');
$this->assert(
str_contains($kopf, '<script src="/static/rechner.js" defer></script>'),
'Der Seitenrahmen bindet rechner.js ein'
);
$this->assert(
strpos($kopf, '/static/betrag.js') !== false
&& strpos($kopf, '/static/betrag.js') < strpos($kopf, '/static/rechner.js'),
'betrag.js steht vor rechner.js – der Rechner liest den Feldwert damit'
);
$this->assert(is_file($this->wurzel . '/public/static/rechner.js'), 'rechner.js liegt unter public/static');
}
private function stilblattZeigtSymbol(): void
{
$css = $this->lies('public/static/app.css');
$this->assert(
(bool)preg_match(
'/input\[type="text"\]\[inputmode="decimal"\]:not\(\[data-ohne-rechner\]\)[^{]*\{[^}]*padding-right:[^}]*background-image:\s*url\("data:image\/svg\+xml/',
$css
),
'Zahlenfelder tragen rechts das Rechnersymbol und halten den Platz dafür frei'
);
$this->assert(
(bool)preg_match('/\.rechner-tasten\s*\{[^}]*grid-template-columns:\s*repeat\(4, 1fr\)/', $css),
'Das Tastenfeld steht in vier Spalten'
);
$this->assert(
!preg_match('/\.rechner[^{]*\{[^}]*#[0-9a-f]{3,6}\b/i', $css),
'Der Rechner nimmt seine Farben aus den Custom Properties des Themas'
);
}
private function skriptRechnetOhneEval(): void
{
$js = $this->lies('public/static/rechner.js');
$this->assert(!preg_match('/\beval\s*\(|new Function\s*\(/', $js), 'Gerechnet wird ohne eval');
$this->assert(str_contains($js, "'input[type=\"text\"][inputmode=\"decimal\"]"), 'Der Rechner hängt an allen Zahlenfeldern');
foreach (["'Enter'", "'Escape'", "'C'", "'='"] as $taste) {
$this->assert(str_contains($js, $taste), "Taste $taste ist belegt");
}
}
private function genaueFelderTragenNachkommastellen(): void
{
$felder = [
'src/Views/wertpapiere/formular.php' => ['stueck' => 6, 'kurs' => 6],
'src/Views/buchungen/wertpapierfelder.php' => ['stueck' => 6, 'kurs' => 6],
'src/Views/wertpapiere/stammdaten.php' => ['kurs' => 6],
'src/Views/vertraege/kredite.php' => ['zinssatz' => 3],
];
foreach ($felder as $datei => $namen) {
$html = $this->lies($datei);
foreach ($namen as $name => $stellen) {
$this->assert(
(bool)preg_match(
'/name="' . $name . '"[^>]*data-nachkommastellen="' . $stellen . '"/',
$html
),
"$datei: „$name“ übernimmt $stellen Nachkommastellen"
);
}
}
}
private function lies(string $pfad): string
{
return (string)file_get_contents($this->wurzel . '/' . $pfad);
}
}