Artifact 1286b504a64124283487a754f261a866e5b2f2c524e5811e73a3090dd186e4b3:
- File
internal/markup/pikchr_test.go
— part of check-in
[1d680372e5]
at
2026-08-16 13:44:53
on branch trunk
— markup: map fossil site-relative links; theme-neutral pikchr ink
Two bugs found by browsing real content on a phone.
Imported wiki/technote/forum bodies are full of fossil's own routes (libfossil's wiki alone: 500 /finfo, 211 /dir, 105 /timeline, 23 /doc). Quarry passed those hrefs through verbatim, so libfossil's home page offered a README.md link that went nowhere. Resolver grows ResolvePath: /doc/<ver>/<path> maps to quarry's file route, /info to the artifact, /timeline /wiki /forum /technote to their pages; a verb quarry has no page for drops the link and keeps the label, since a dead link is worse than plain text. file.get now lets zit resolve symbolic versions (trunk, tip, tags) instead of demanding a hash — that is what /doc links use, and zit is the authority on names. The href rewrite runs BEFORE reference resolution so the two passes never see each other's output.
Pikchr hardcodes rgb(0,0,0), which is invisible on quarry's dark theme — the DOM assertions all passed while the diagram was unreadable, and only a screenshot caught it. Its default ink now becomes currentColor so a diagram follows the page theme; author-specified colours are untouched. Fossil solves this with a server-side --dark-mode flag, which cannot work for a CSS-themed page. (user: dmestas, size: 3531) [annotate] [blame] [check-ins using]
package markup import ( "strings" "testing" ) func TestRenderPikchrSanitized(t *testing.T) { out, err := RenderPikchr([]byte(`box "hello" fit; arrow right; circle "world" fit`)) if err != nil { t.Fatal(err) } svg := string(out) if !strings.Contains(svg, "<svg") || !strings.Contains(svg, "hello") { t.Fatalf("svg missing content:\n%s", svg[:min(300, len(svg))]) } // Geometry must survive the narrow policy — a diagram stripped of its // paths would render blank while still "passing". if !strings.Contains(svg, "<path") && !strings.Contains(svg, "<polygon") { t.Fatalf("geometry stripped by policy:\n%s", svg[:min(400, len(svg))]) } if !strings.Contains(svg, "style=") { t.Fatalf("styling stripped, diagram would be invisible:\n%s", svg[:min(400, len(svg))]) } } func TestPikchrLabelCannotInject(t *testing.T) { out, err := RenderPikchr([]byte(`box "</svg><script>alert(1)</script>" fit`)) if err != nil { return // refusing to render is also an acceptable outcome } // The payload must survive only as escaped display text. Checking // for the raw substring would false-positive on "<script>", // which is exactly what correct escaping looks like. if strings.Contains(string(out), "<script") || strings.Contains(string(out), "javascript:") { t.Fatalf("unescaped script survived:\n%s", out) } if !strings.Contains(string(out), "<script>") { t.Fatalf("payload neither escaped nor removed:\n%s", out) } } func min(a, b int) int { if a < b { return a }; return b } // A ```pikchr fence inside markdown becomes an inline diagram, and the // surrounding document still renders normally. func TestPikchrFenceInMarkdown(t *testing.T) { src := "Before.\n\n```pikchr\nbox \"fenced\" fit\n```\n\nAfter.\n" out, err := Render([]byte(src), nil) if err != nil { t.Fatal(err) } got := string(out) if !strings.Contains(got, "<svg") || !strings.Contains(got, "fenced") { t.Fatalf("fence not rendered as svg:\n%s", got) } if !strings.Contains(got, "Before.") || !strings.Contains(got, "After.") { t.Fatalf("surrounding markdown lost:\n%s", got) } // An ordinary code fence must still be a code block, not a diagram. out, _ = Render([]byte("```go\nfmt.Println()\n```\n"), nil) if strings.Contains(string(out), "<svg") { t.Fatalf("non-pikchr fence rendered as a diagram:\n%s", out) } } // A fence that will not compile shows its source plus the diagnostic, // never a broken page or a silent blank. func TestPikchrFenceErrorShowsSource(t *testing.T) { out, err := Render([]byte("```pikchr\nnot ( valid\n```\n"), nil) if err != nil { t.Fatal(err) } got := string(out) if !strings.Contains(got, "not ( valid") || !strings.Contains(got, "pikchr:") { t.Fatalf("bad diagram did not degrade to source + message:\n%s", got) } } // A diagram must be legible on both themes: quarry's pages follow // prefers-color-scheme, so Pikchr's hardcoded black ink has to become // currentColor or the whole diagram vanishes on the dark theme. func TestPikchrInheritsThemeColor(t *testing.T) { out, err := RenderPikchr([]byte(`box "visible" fit; arrow right; circle "here" fit`)) if err != nil { t.Fatal(err) } got := string(out) if strings.Contains(got, "rgb(0,0,0)") || strings.Contains(got, "rgb(0, 0, 0)") { t.Fatalf("hardcoded black ink survives — invisible on the dark theme:\n%s", got[:min(400, len(got))]) } if !strings.Contains(got, "currentColor") { t.Fatalf("no currentColor: diagram will not follow the page theme:\n%s", got[:min(400, len(got))]) } }