Fix #198: Fehlender View-Import in suppliers/list.php, veralteter E2E-Selektor
This commit is contained in:
parent
68ce22894f
commit
c6164cf823
5 changed files with 106 additions and 2 deletions
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-08-21 – Lieferantenliste (/suppliers) lief in HTTP 500: fehlender View-Import (#198)
|
||||||
|
|
||||||
|
- **`src/Views/suppliers/list.php`:** Die View rief `View::formatDate()` ohne `use Steuerkram\View;` auf. Views werden per `include` eingebunden und laufen dadurch im globalen Namespace – das `use` des einbindenden Controllers gilt dort nicht, der Aufruf ging also auf `\View::formatDate()` und die Seite starb mit `Class "View" not found`. Sichtbar wurde das erst mit dem ersten Lieferanten, weil die Zeile in der `foreach`-Schleife steht. Import ergänzt; alle übrigen Views hatten ihn bereits.
|
||||||
|
- **`tests/99_ViewKlassenImportTest.php`** (neu): prüft alle Views in `src/Views/` statisch – jeder unqualifizierte `Klasse::`-Aufruf muss entweder per `use` importiert sein oder eine global existierende Klasse (`DateTime`, `PDO`, …) meinen. Fängt fehlende Importe ab, bevor sie im Betrieb auffallen.
|
||||||
|
- **`tests/e2e/specs/rechnung-export.spec.js`:** klickte noch `button[onclick^="showAddLineDialog"]`; der Inline-Handler-Umbau aus #160 hat daraus `data-action="show-add-line-dialog"` gemacht – fünf E2E-Tests liefen deshalb in den Timeout. Selektor nachgezogen.
|
||||||
|
|
||||||
## 2026-08-21 – run-tests.sh: Commit-Gate meldete fälschlich Erfolg bei fehlgeschlagenen Tests (#195)
|
## 2026-08-21 – run-tests.sh: Commit-Gate meldete fälschlich Erfolg bei fehlgeschlagenen Tests (#195)
|
||||||
|
|
||||||
- **`scripts/run-tests.sh`:** Die Auswertung von `php tests/run.php --json` hing an `python3`, das weder im Container noch auf dem Host installiert ist – der Fallback `|| echo "0"` verwandelte jeden Parse-Fehler in „0 fehlgeschlagene Tests", das Commit-Gate griff also nie. Erfolg/Misserfolg hängt jetzt direkt am Exit-Code von `php tests/run.php --json`; Detailauswertung (`failed`, `failures`) läuft über `php -r` statt Python. Lässt sich die JSON-Ausgabe nicht auswerten, bricht das Skript mit einem Fehler ab statt fälschlich Erfolg zu melden. Der zuvor ungenutzte `EXIT_CODE` (verworfen durch ein voranstehendes `|| true`) wird jetzt tatsächlich ausgewertet. Die JSON-Payloads für die automatische Issue-Erstellung werden ebenfalls per `php -r` statt Python gebaut (Body-Text jetzt mit echten Zeilenumbrüchen statt literalem `\n`).
|
- **`scripts/run-tests.sh`:** Die Auswertung von `php tests/run.php --json` hing an `python3`, das weder im Container noch auf dem Host installiert ist – der Fallback `|| echo "0"` verwandelte jeden Parse-Fehler in „0 fehlgeschlagene Tests", das Commit-Gate griff also nie. Erfolg/Misserfolg hängt jetzt direkt am Exit-Code von `php tests/run.php --json`; Detailauswertung (`failed`, `failures`) läuft über `php -r` statt Python. Lässt sich die JSON-Ausgabe nicht auswerten, bricht das Skript mit einem Fehler ab statt fälschlich Erfolg zu melden. Der zuvor ungenutzte `EXIT_CODE` (verworfen durch ein voranstehendes `|| true`) wird jetzt tatsächlich ausgewertet. Die JSON-Payloads für die automatische Issue-Erstellung werden ebenfalls per `php -r` statt Python gebaut (Body-Text jetzt mit echten Zeilenumbrüchen statt literalem `\n`).
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
<?php use Steuerkram\View; ?>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1-260802
|
1-260821
|
||||||
|
|
|
||||||
96
tests/99_ViewKlassenImportTest.php
Normal file
96
tests/99_ViewKlassenImportTest.php
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 99: Klassen-Importe in den Views (Fix für /suppliers, HTTP 500)
|
||||||
|
*
|
||||||
|
* Views werden per include eingebunden und laufen dadurch im globalen
|
||||||
|
* Namespace – das `use Steuerkram\View;` des einbindenden Controllers gilt
|
||||||
|
* dort nicht. Fehlt der Import in der View selbst, wird `View::formatDate()`
|
||||||
|
* zu `\View::formatDate()` und die Seite stirbt mit "Class View not found".
|
||||||
|
*
|
||||||
|
* Der Fehler bleibt so lange unsichtbar, wie die betroffene Zeile nicht
|
||||||
|
* ausgeführt wird (in suppliers/list.php stand sie in einer foreach-Schleife,
|
||||||
|
* schlug also erst mit dem ersten Lieferanten zu). Dieser Test prüft alle
|
||||||
|
* Views statisch, damit ein fehlender Import nicht erst im Betrieb auffällt.
|
||||||
|
*/
|
||||||
|
class ViewKlassenImportTest extends TestCase
|
||||||
|
{
|
||||||
|
protected string $testName = 'Test 99: Klassen-Importe in den Views';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$viewDir = realpath(__DIR__ . '/../src/Views');
|
||||||
|
$this->assert($viewDir !== false, 'src/Views/ existiert');
|
||||||
|
|
||||||
|
$dateien = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($viewDir));
|
||||||
|
$geprueft = 0;
|
||||||
|
$fehler = [];
|
||||||
|
|
||||||
|
foreach ($dateien as $datei) {
|
||||||
|
if (!$datei->isFile() || $datei->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$inhalt = file_get_contents($datei->getPathname());
|
||||||
|
$relativ = str_replace($viewDir . '/', '', $datei->getPathname());
|
||||||
|
$geprueft++;
|
||||||
|
|
||||||
|
foreach ($this->unaufgeloesteKlassen($inhalt) as $klasse) {
|
||||||
|
$fehler[] = "$relativ verwendet $klasse:: ohne Import";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertGreaterThan(20, $geprueft, 'Es wurden Views eingelesen');
|
||||||
|
$this->assert(
|
||||||
|
$fehler === [],
|
||||||
|
'Jede View importiert die Klassen, die sie statisch aufruft'
|
||||||
|
. ($fehler === [] ? '' : ': ' . implode('; ', $fehler))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die in $inhalt statisch aufgerufenen Klassennamen, die weder
|
||||||
|
* global existieren noch per use-Statement importiert sind.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function unaufgeloesteKlassen(string $inhalt): array
|
||||||
|
{
|
||||||
|
preg_match_all(
|
||||||
|
'/\buse\s+([A-Za-z0-9_\\\\]+)(?:\s+as\s+([A-Za-z0-9_]+))?\s*;/i',
|
||||||
|
$inhalt,
|
||||||
|
$useTreffer,
|
||||||
|
PREG_SET_ORDER
|
||||||
|
);
|
||||||
|
|
||||||
|
$importiert = [];
|
||||||
|
foreach ($useTreffer as $treffer) {
|
||||||
|
$teile = explode('\\', $treffer[1]);
|
||||||
|
$name = $treffer[2] ?? end($teile);
|
||||||
|
$importiert[$name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aufrufe wie View::formatDate(), aber nicht \Steuerkram\View::,
|
||||||
|
// $obj::, self::/static::/parent::
|
||||||
|
preg_match_all('/(?<![\\\\$\w>])([A-Z][A-Za-z0-9_]*)\s*::/', $inhalt, $aufrufe);
|
||||||
|
|
||||||
|
$offen = [];
|
||||||
|
foreach (array_unique($aufrufe[1]) as $klasse) {
|
||||||
|
if (isset($importiert[$klasse])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Global existierende Klassen (DateTime, PDO, …) sind unkritisch.
|
||||||
|
if (class_exists($klasse, false) || interface_exists($klasse, false) || enum_exists($klasse, false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (class_exists('\\' . $klasse) || interface_exists('\\' . $klasse)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$offen[] = $klasse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $offen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ async function entwurfAnlegen(page, { tag, einzelpreis = '100.00', ustSatz = '19
|
||||||
await page.fill('#betreff', tag)
|
await page.fill('#betreff', tag)
|
||||||
|
|
||||||
// Position über den "+"-Button und das Popup hinzufügen.
|
// Position über den "+"-Button und das Popup hinzufügen.
|
||||||
await page.click('button[onclick^="showAddLineDialog"]')
|
await page.click('button[data-action="show-add-line-dialog"]')
|
||||||
await page.click('#add-line-popup >> text=Neue Position')
|
await page.click('#add-line-popup >> text=Neue Position')
|
||||||
|
|
||||||
const zeile = page.locator('#invoice-lines-body tr').last()
|
const zeile = page.locator('#invoice-lines-body tr').last()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue