Skip to content

test(anchors): den Anker-Vertrag um die Randfaelle erweitern - #44

Draft
goldnead wants to merge 2 commits into
mainfrom
urlaub/toc-anker-vertrag-randfaelle
Draft

test(anchors): den Anker-Vertrag um die Randfaelle erweitern#44
goldnead wants to merge 2 commits into
mainfrom
urlaub/toc-anker-vertrag-randfaelle

Conversation

@goldnead

@goldnead goldnead commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Was hier drin ist

Nur Tests. Kein Produktivcode angefasst, keine API-Änderung, kein Breaking Change.

Der Auftrag war, den Umbau aus backlog-statamic-toc-v2-anker-vertrag zu bauen: eine Quelle für Überschriften und Anker-IDs. Der Umbau steht bereits vollständig auf mainRegistry, Slugger, IdInjector, Extractors/{Bard,Html,Markdown}, und der Modifier rechnet keine IDs mehr aus. Ich habe das gegen den Code geprüft, nicht aus dem Ticket übernommen. Es gab also nichts zu bauen.

Offen war die zweite Hälfte des Auftrags: die Randfälle, die der Vertragstest noch nicht festhielt.

Neu im Vertrag

Fall Warum er fehlte
Leere Überschrift (<h2></h2>) Gar nicht abgedeckt. Der Redakteur lässt einen Heading-Block leer stehen.
Überschrift ohne Text (Whitespace, <br>, leeres Inline-Markup) Dito, als DataProvider mit drei Zeilen.
Titel ohne slugfähige Zeichen (???) Der section-Fallback in Slugger::unique war nirgends gepinnt.
Manuell gesetzte id unterhalb der kollidierenden Überschrift Die bestehende Abdeckung hatte die manuelle id immer oben. Dass Slugger::assign vorhandene IDs in einem eigenen Vorlauf beansprucht, hing an nichts.
id in anderer Notation (einfache Anführungszeichen, Großschreibung) Der hasId-Guard im Injektor ist tolerant, der Test war es nicht.
Markdown-Feld: Liste aus der Quelle, IDs aus dem gerenderten HTML Die normale Form einer Markdown-Seite. Zwei verschiedene Strings, ein Heading-Verlauf — genau das, was der Registry-Fingerprint überbrücken muss.

Zwei Löcher in den Hilfsfunktionen des Tests

Beide fanden sich beim Schreiben, beide hätten Zusicherungen aus dem falschen Grund grün gehalten:

  1. anchors() matchte href="#([^"]+)". Ein leeres href="#" fiel damit aus dem Ergebnis heraus — ein Listeneintrag, der nirgendwohin zeigt, sah aus wie kein Listeneintrag.
  2. ids() las nur id="…" in Kleinschreibung mit doppelten Anführungszeichen. Für eine Überschrift, die sichtbar eine id trägt, meldete es „keine id".

Was die Mutationsprobe gefunden hat

M4 (hasId erkennt nur doppelt quotierte Kleinschreibung) überlebte im ersten Durchlauf. Grund: erkennt der Injektor die einfach quotierte id nicht, hängt er eine zweite daneben. Der Browser behält die erste, die Liste zeigt auf denselben Wert — der Vertrag sah eingehalten aus, obwohl das HTML kaputt war.

Deshalb prüft assertAnchorsResolve() jetzt zusätzlich, dass keine Überschrift zwei id-Attribute trägt. Danach fällt M4.

Testlauf

Vor dem Branch: OK (129 Tests, 292 Assertions)
Auf dem Branch: OK (137 Tests, 363 Assertions)

pint --test grün.

phpstan analyse konnte ich lokal nicht auswerten: interner Fehler beim Auflösen der phpstorm-stubs (dom_c.stub), Artefakt eines quer kopierten vendor/. Derselbe Fehler tritt auf einem unveränderten main-Checkout auf, hat also nichts mit diesem Branch zu tun. Die CI hat es entschieden: Static analysis grün, ebenso alle sechs Matrix-Zeilen und Code style.

Mutationsprobe (real gelaufen, jeweils einzeln eingespielt und zurückgesetzt)

Mutation Ergebnis
M0 Injektor schreibt eine andere id als die Liste verlinkt (nur Dokumentseite) rot, 16 von 18
M1 Slugger behandelt eine textlose Überschrift nicht mehr als leer rot, 4
M2 kein section-Fallback für einen nicht slugfähigen Titel rot, 1
M3 vorhandene ids werden erst beim Vorbeilaufen beansprucht rot, 1
M4 hasId erkennt nur id="…" in Kleinschreibung rot, 1 (nach der Verschärfung oben)

Nach jeder Rücknahme wieder OK (18 Tests, 99 Assertions) im Vertragstest, Working Tree am Ende sauber.

Zwei Tests überleben M0, beide zu Recht: test_no_heading_carries_two_ids prüft nur Duplikate, und test_excluding_a_heading_… arbeitet direkt am Parser ohne zu rendern.

Ehrliche Grenze, unverändert gegenüber der Prüfung vom 01.08.: der Test prüft das Ergebnis, nicht die Architektur. Ein Modifier, der die Registry umgeht und einen frischen, zustandslosen Slugger benutzt, überlebt. Sobald die zweite Rechenstelle abweicht — und genau das war der v1-Fehler — fällt der Test.

Nicht gemacht, bewusst

Kein Merge, kein Tag, kein Release, kein Packagist-Push. Draft, weil Adrian im Urlaub ist.

Offene Fragen für Adrian

  1. Leere Überschrift: ist „aus beiden Seiten raus" die gewollte Regel? Das ist der heutige Stand, und ich habe ihn festgeschrieben, nicht entschieden. Denkbar wäre auch, sie in der Liste zu zeigen und auf #section zeigen zu lassen. Kein Ticket sagt dazu etwas.
  2. Der section-Fallback ist ein magischer String in Slugger::unique, nicht konfigurierbar. Für ein englischsprachig ausgeliefertes Addon vermutlich in Ordnung, aber niemand hat das entschieden.
  3. Die Restpunkte aus dem Ticket bleiben offen und sind keine Bauarbeit: GitHub-Release für v2 (jüngstes Release ist v1.9, obwohl v2.0.0 und v2.1.0 getaggt sind), Packagist-Verifikation (packagist.org ist aus dieser Umgebung nicht erreichbar), und der veraltete Zahlenstand im Ticket-Text.

Leere Ueberschriften, Titel ohne slugfaehige Zeichen, eine manuell
gesetzte id unterhalb der kollidierenden Ueberschrift, andere
Anfuehrungszeichen und Grossschreibung, und ein Markdown-Feld, dessen
Liste aus der Quelle und dessen ids aus dem gerenderten HTML kommen.

Ausserdem zwei Loecher in den Hilfsfunktionen des Tests geschlossen:
anchors() sah wegen [^"]+ ein leeres href="#" gar nicht, und ids()
erkannte weder einfache Anfuehrungszeichen noch Grossschreibung.
Die Mutationsprobe hat gezeigt, dass der neue Notations-Test allein nicht
greift: erkennt der Injektor eine einfach quotierte id nicht, haengt er
eine zweite daneben. Der Browser behaelt die erste, die Liste zeigt auf
denselben Wert, und der Vertrag sah eingehalten aus, obwohl das HTML
kaputt war.

assertAnchorsResolve() prueft jetzt zusaetzlich, dass keine Ueberschrift
zwei id-Attribute traegt.

Ausserdem den Klassenkommentar korrigiert: er kuendigte noch an, die
Tests wuerden bis zum v2-Umbau rot sein.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant