OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

InfrastructureRackAssetsTest.php

8 KB
<?php

declare(strict_types=1);

/**
 * Haelt die Rack-Seite und ihr Modul-Skript zusammen.
 *
 * Die Bedienung der Rack-Elevation liegt in `public/assets/js/infrastructure.js`,
 * aufgerufen wird sie aus Attributen im Markup. Diese Trennung faellt nicht auf,
 * wenn sie bricht: Ein Tippfehler im Funktionsnamen erzeugt keinen Fehler beim
 * Ausliefern, sondern eine Schaltflaeche, die nichts tut. Ebenso bleibt eine
 * wieder ins Markup gerutschte lange Auswahlliste unbemerkt – sie macht die
 * Seite nur wieder gross.
 */
return static function (): void {

    $wurzel  = dirname(__DIR__);
    $rack    = (string)file_get_contents($wurzel . '/templates/infrastructure/rack.php');
    $dialoge = (string)file_get_contents($wurzel . '/templates/partials/infrastructure-rack-dialogs.php');
    $skript  = (string)file_get_contents($wurzel . '/public/assets/js/infrastructure.js');

    // Die Seite besteht aus der Vorlage und ihren ausgelagerten Dialogen; was
    // aus dem Markup heraus gilt, gilt fuer beide Teile zusammen.
    $markup = $rack . "\n" . $dialoge;

    // ----------------------------------------------------------------
    // Markup ruft nur auf, was es auch gibt
    // ----------------------------------------------------------------

    test('Jede aus dem Rack-Markup gerufene Funktion ist im Modul-Skript definiert', function () use ($markup, $skript) {
        // PHP-Closures der Vorlage tragen denselben Praefix, laufen aber serverseitig.
        $serverseitig = ['infShade', 'infReadableFg'];

        preg_match_all('/\b(inf[A-Z][A-Za-z]*)\s*\(/', $markup, $treffer);
        $gerufen = array_diff(array_unique($treffer[1]), $serverseitig);
        assertTrue($gerufen !== [], 'Keine Aufrufe gefunden – Suchmuster passt nicht mehr');

        foreach ($gerufen as $name) {
            assertTrue(
                str_contains($skript, 'window.' . $name . ' ='),
                "Aus dem Markup gerufen, aber nirgends definiert: {$name}()"
            );
        }
    });

    test('Die Vorlage bringt kein eigenes Verhalten mehr mit', function () use ($rack, $dialoge) {
        // Die ausgelagerten Dialoge bringen gar kein Skript mit.
        assertFalse(str_contains($dialoge, '<script'), 'Die Dialoge fuehren wieder eigenes Skript');

        // In der Vorlage sind genau zwei Bloecke erlaubt: die Dateninsel und das
        // Setzen der Ansicht vor dem ersten Zeichnen.
        preg_match_all('/<script\b([^>]*)>/', $rack, $treffer);
        assertSame(2, count($treffer[0]), 'Zahl der <script>-Bloecke in rack.php geaendert');
        assertTrue(str_contains($treffer[1][0], 'type="application/json"'), 'Erster Block ist keine Dateninsel');
        assertTrue(str_contains($treffer[1][0], 'id="inf-rack-data"'), 'Dateninsel ohne erwartete Kennung');

        // Der zweite Block bleibt auf die Ansicht-Praeferenz beschraenkt.
        $zweiter = substr($rack, (int)strpos($rack, $treffer[0][1]));
        $zweiter = substr($zweiter, 0, (int)strpos($zweiter, '</script>'));
        assertTrue(str_contains($zweiter, 'inf-rack-theme'), 'Zweiter Block ist nicht die Ansicht-Praeferenz');
        assertFalse(str_contains($zweiter, 'function '), 'Wieder Funktionen in der Vorlage');
    });

    // ----------------------------------------------------------------
    // Dateninsel und Skript sprechen dieselbe Sprache
    // ----------------------------------------------------------------

    test('Das Skript liest nur Schluessel, die die Insel liefert', function () use ($rack, $skript) {
        $insel = substr($rack, (int)strpos($rack, 'id="inf-rack-data"'));
        $insel = substr($insel, 0, (int)strpos($insel, '</script>'));

        foreach (['connections', 'aPorts', 'bPorts', 'conns', 'portLabels', 'mounts',
                  'rowUs', 'numbering', 'side', 'csrf', 'connAddAction', 'options'] as $schluessel) {
            assertTrue(str_contains($insel, "'{$schluessel}'"), "Dateninsel ohne Schluessel {$schluessel}");
            assertTrue(str_contains($skript, $schluessel), "Schluessel {$schluessel} wird nirgends gelesen");
        }
    });

    test('Die Insel kann nicht aus ihrem script-Element ausbrechen', function () use ($rack) {
        // Ein Geraetename mit </script> wuerde sonst den Rest der Seite kapern.
        $insel = substr($rack, (int)strpos($rack, 'id="inf-rack-data"'));
        $insel = substr($insel, 0, (int)strpos($insel, '</script>'));
        assertTrue(str_contains($insel, 'JSON_HEX_TAG'), 'Spitze Klammern werden nicht maskiert');
        assertTrue(str_contains($insel, 'JSON_HEX_AMP'), 'Kaufmaennisches Und wird nicht maskiert');
    });

    // ----------------------------------------------------------------
    // Lange Auswahllisten bleiben aus dem Markup
    // ----------------------------------------------------------------

    test('Kein Dialog rendert seine Auswahlliste vorab', function () use ($markup) {
        // Die drei langen Listen kommen aus den Seitendaten; die Dokuseiten
        // stuenden sonst gleich dreifach im Quelltext.
        foreach (['docPages', 'assets', 'unifi'] as $liste) {
            assertTrue(
                str_contains($markup, 'data-inf-fill="' . $liste . '"'),
                "Auswahlliste {$liste} ist nicht als nachladbar gekennzeichnet"
            );
        }

        // Die drei Quellen waren die langen: Dokuseiten und Inventar wachsen mit
        // dem Bestand, nicht mit dem Rack. Schleifen ueber $mounts oder $targets
        // bleiben erlaubt – sie sind durch die Geraete dieses Racks begrenzt.
        foreach (['$docPages', "\$candidates['assets']", "\$candidates['unifi']"] as $quelle) {
            assertFalse(
                (bool)preg_match('/<select\b(?:(?!<\/select>).)*' . preg_quote($quelle, '/') . '(?:(?!<\/select>).)*<\/select>/s', $markup),
                "Ein Auswahlfeld baut seine Optionen wieder aus {$quelle}"
            );
        }
    });

    test('Ein gesetzter Dokuverweis ueberlebt das Bearbeiten', function () use ($markup, $skript) {
        // Ohne vorbelegten Wert speichert der Dialog eine leere Auswahl zurueck
        // und loest damit die Verknuepfung, die vorher am Geraet hing.
        assertTrue(str_contains($markup, "'doc_page_id' => (int)(\$m['doc_page_id'] ?? 0)"), 'Dokuverweis fehlt in den Geraetedaten');
        assertTrue(str_contains($skript, "f.elements['doc_page_id'].value = m.doc_page_id"), 'Dialog setzt den Dokuverweis nicht');

        // Gesetzt werden darf er erst, wenn die Liste steht – sonst greift der Wert ins Leere.
        $fuellen = strpos($skript, "fillSelects(byId('inf-edit-modal'))");
        $setzen  = strpos($skript, "f.elements['doc_page_id'].value = m.doc_page_id");
        assertTrue($fuellen !== false && $setzen !== false && $fuellen < $setzen, 'Der Verweis wird vor dem Fuellen gesetzt');

        // Entscheidend ist die Quelle: Der Dialog muss den Stand aus den
        // Seitendaten lesen. Eine zweite Beschreibung desselben Geraets am
        // Markup fuehrt sonst wieder ein Feld weniger mit als die erste.
        assertTrue(str_contains($skript, 'window.infEditMount = function (mountId)'), 'Der Dialog nimmt nicht die Geraetekennung entgegen');
        assertTrue(str_contains($skript, 'var m = MOUNTS[mountId]'), 'Der Dialog liest nicht aus den Seitendaten');
        // Port-Elemente tragen weiterhin ein data-mount – das ist die Kennung des
        // Geraets, zu dem der Port gehoert, nicht dessen Beschreibung.
        assertFalse(
            (bool)preg_match('/JSON\.parse\(\s*\w+\.getAttribute\(/', $skript),
            'Der Dialog liest einen serialisierten Geraetestand aus dem Markup'
        );
        assertFalse(
            (bool)preg_match('/data-[a-z-]+="[^"]*json_encode/', $markup),
            'Das Markup beschreibt Geraete wieder ein zweites Mal'
        );
    });

    test('Das Rack-Einstellungsfeld behaelt seinen Verweis auch ohne Skript', function () use ($dialoge) {
        // Der hinterlegte Eintrag steht als einzige Option im Markup; fehlte er,
        // saehe das Formular einen leeren Wert und loeschte die Verknuepfung.
        assertTrue(str_contains($dialoge, '$rackDocTitle'), 'Der hinterlegte Eintrag steht nicht im Markup');
        assertTrue(str_contains($dialoge, '<option value="<?= $rackDoc ?>" selected>'), 'Der Eintrag ist nicht vorausgewaehlt');
    });
};