Umsetzung der Ausbaustufen 2.1, 3.6, 3.8, 3.9, 4.1, 4.2, 4.4, 4.6, 5.1–5.4 (Details in AUSBAUSTUFEN.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3 KiB
Go
90 lines
3 KiB
Go
package markdown
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestGFMErweiterungen: Tabellen, Durchstreichen, Checklisten und automatische
|
|
// Links funktionieren — in Einträgen genauso wie in der Hilfe (AUSBAUSTUFEN.md 5.3).
|
|
func TestGFMErweiterungen(t *testing.T) {
|
|
faelle := []struct {
|
|
name, quelle, erwartet string
|
|
}{
|
|
{"Tabelle", "| a | b |\n|---|---|\n| 1 | 2 |", "<table>"},
|
|
{"Durchstreichen", "~~veraltet~~", "<del>veraltet</del>"},
|
|
{"Checkliste offen", "- [ ] offen", `<input disabled="" type="checkbox">`},
|
|
{"Checkliste erledigt", "- [x] erledigt", `<input checked="" disabled="" type="checkbox">`},
|
|
{"automatischer Link", "siehe https://example.org", `<a href="https://example.org">`},
|
|
}
|
|
|
|
// Beide Renderer prüfen: Sie sollen sich nur in den Ankern unterscheiden.
|
|
renderer := map[string]func(string) (string, error){
|
|
"Render": Render,
|
|
"RenderMitAnkern": RenderMitAnkern,
|
|
}
|
|
|
|
for rName, render := range renderer {
|
|
for _, f := range faelle {
|
|
html, err := render(f.quelle)
|
|
if err != nil {
|
|
t.Fatalf("%s/%s: %v", rName, f.name, err)
|
|
}
|
|
if !strings.Contains(html, f.erwartet) {
|
|
t.Errorf("%s/%s: %q fehlt in %q", rName, f.name, f.erwartet, html)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestKeinRohHTML ist der wichtigste Test dieses Pakets: Die Weboberfläche setzt
|
|
// das Ergebnis ohne weiteres Escaping in die Seite. Das geht nur, solange hier
|
|
// weder Skripte noch gefährliche Link-Ziele durchkommen — auch nicht mit den
|
|
// GFM-Erweiterungen.
|
|
func TestKeinRohHTML(t *testing.T) {
|
|
boese := []string{
|
|
"<script>alert(1)</script>",
|
|
`<img src=x onerror="alert(1)">`,
|
|
"[klick](javascript:alert(1))",
|
|
"| a |\n|---|\n| <script>alert(1)</script> |",
|
|
"- [ ] <script>alert(1)</script>",
|
|
}
|
|
for _, quelle := range boese {
|
|
for _, render := range []func(string) (string, error){Render, RenderMitAnkern} {
|
|
html, _ := render(quelle)
|
|
if strings.Contains(html, "<script") || strings.Contains(html, "onerror") || strings.Contains(html, "javascript:") {
|
|
t.Errorf("gefährlicher Inhalt durchgelassen: %q → %q", quelle, html)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAnker: Nur die Hilfe bekommt ids an den Überschriften, mit gefalteten
|
|
// Umlauten und eindeutig je Seite.
|
|
func TestAnker(t *testing.T) {
|
|
quelle := "## Zugänge verwalten\n\n## Protokoll\n\n## Protokoll\n\n## ???"
|
|
|
|
html, err := RenderMitAnkern(quelle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, id := range []string{`id="zugaenge-verwalten"`, `id="protokoll"`, `id="protokoll-1"`, `id="abschnitt"`} {
|
|
if !strings.Contains(html, id) {
|
|
t.Errorf("%s fehlt in %q", id, html)
|
|
}
|
|
}
|
|
|
|
// Ein zweiter Aufruf fängt wieder von vorn an — die vergebenen ids der
|
|
// ersten Seite dürfen nicht hinüberwirken.
|
|
nochmal, _ := RenderMitAnkern("## Protokoll")
|
|
if !strings.Contains(nochmal, `id="protokoll"`) {
|
|
t.Errorf("zweite Seite bekommt nicht die einfache id: %q", nochmal)
|
|
}
|
|
|
|
// Einträge: keine ids, damit Eintragstext nicht mit ids der Seite
|
|
// kollidieren kann.
|
|
eintrag, _ := Render(quelle)
|
|
if strings.Contains(eintrag, "id=") {
|
|
t.Errorf("Eintrag hat Überschrift-ids bekommen: %q", eintrag)
|
|
}
|
|
}
|