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>
102 lines
3.8 KiB
PHP
102 lines
3.8 KiB
PHP
<?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);
|
||
}
|
||
}
|