Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
/**
* Hält die Verzeichnis-Personensuche als eine Komponente zusammen.
*
* Die Suche lebt zentral in person-search.js/.css (Prefix `psx-`). Templates
* dürfen sie nur einbinden, nicht nachbauen: Ein eigener Nachbau driftet im
* Aussehen auseinander und verliert bei Änderungen an der Komponente den
* Anschluss.
*/
return static function (): void {
$tplDir = dirname(__DIR__) . '/templates';
/** @return string[] Alle Template-Pfade */
$templates = static function (string $dir): array {
$out = [];
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($it as $f) {
if ($f->getExtension() === 'php') {
$out[] = $f->getPathname();
}
}
sort($out);
return $out;
};
test('Kein Template definiert eigene Personensuche-Klassen', function () use ($templates, $tplDir) {
// Nachbauten erkennt man an CSS-Regeln für Dropdown, Treffer oder Plakette
// unter beliebigem Prefix, z. B. `.ps-dropdown` oder `.atm-ps-item`.
$muster = '/^\s*\.[a-z0-9-]*ps-(dropdown|drop|item|badge|input|no-results|nores)[a-z0-9-]*\s*[,{:]/mi';
$treffer = [];
foreach ($templates($tplDir) as $file) {
if (preg_match_all($muster, file_get_contents($file), $m)) {
$treffer[] = basename($file) . ': ' . trim($m[0][0]);
}
}
assertSame([], $treffer, 'Eigene Personensuche-Styles: ' . implode(' | ', $treffer));
});
test('Jede Seite mit Personensuche lädt die zentrale Komponente', function () use ($templates, $tplDir) {
// Nur vollständige Seiten (mit Layout-Aufruf) binden Assets ein. Fragmente
// ohne Layout erben sie von der Seite, die sie nachlädt.
$fehlend = [];
foreach ($templates($tplDir) as $file) {
$src = file_get_contents($file);
if (!str_contains($src, 'data-person-search') || !str_contains($src, '$this->layout(')) {
continue;
}
if (!str_contains($src, '/assets/js/person-search.js')
|| !str_contains($src, '/assets/css/person-search.css')) {
$fehlend[] = basename($file);
}
}
assertSame([], $fehlend, 'Komponente nicht eingebunden: ' . implode(', ', $fehlend));
});
test('Die Komponente kennt Plakette und Aufheben-Verhalten', function () {
$js = file_get_contents(dirname(__DIR__) . '/public/assets/js/person-search.js');
$css = file_get_contents(dirname(__DIR__) . '/public/assets/css/person-search.css');
assertTrue(str_contains($js, "getAttribute('data-ps-badge')"));
assertTrue(str_contains($js, "getAttribute('data-ps-clear')"));
assertTrue(str_contains($css, '.psx-selected'));
assertTrue(str_contains($css, '.psx-selected-clear'));
});
test('Personensuche-Felder tragen eine gestylte Eingabeklasse', function () use ($templates, $tplDir) {
// Ein Suchfeld ohne Eingabeklasse fällt auf das Browser-Aussehen zurück –
// .psx-wrap liefert nur die Positionierung, keine Feldoptik.
$ohneKlasse = [];
foreach ($templates($tplDir) as $file) {
$src = file_get_contents($file);
if (!str_contains($src, 'data-person-search')) {
continue;
}
// Jedes <input …>-Tag mit dem Attribut prüfen
preg_match_all('/<input\b[^>]*data-person-search[^>]*>/is', $src, $m);
foreach ($m[0] as $tag) {
if (!preg_match('/class\s*=\s*"([^"]*)"/i', $tag, $cm)) {
$ohneKlasse[] = basename($file) . ' (kein class-Attribut)';
continue;
}
$klassen = preg_split('/\s+/', trim($cm[1])) ?: [];
$gestylt = array_filter($klassen, static fn($c) => $c !== '' && $c !== 'psx-wrap');
if ($gestylt === []) {
$ohneKlasse[] = basename($file) . ' (nur Hilfsklassen)';
}
}
}
assertSame([], $ohneKlasse, 'Ungestylte Suchfelder: ' . implode(', ', $ohneKlasse));
});
};