Artifact d3a9d4b8dd9616b9305e7b9cf2e39a3278283acc1c40adcd6e411416f9279b6c:
- File
internal/markup/fossilwiki_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: 3792) [annotate] [blame] [check-ins using]
package markup import ( "strings" "testing" ) type fakeRes struct{} func (fakeRes) ResolveHash(p string) string { return "/r/x/checkin/" + p } func (fakeRes) ResolveWiki(n string) string { if n == "Missing" { return "" } return "/r/x/wiki/" + n } func TestFossilWikiParagraphsAndLists(t *testing.T) { src := `First paragraph line one. line two of the same paragraph. Second paragraph. * bullet one * bullet two # first enumerated # second enumerated ` got := string(mustFW(t, src, nil)) if strings.Count(got, "<p>") != 2 { t.Fatalf("want 2 paragraphs:\n%s", got) } if !strings.Contains(got, "<ul>") || strings.Count(got, "<li>") != 4 { t.Fatalf("lists wrong:\n%s", got) } if !strings.Contains(got, "<ol>") { t.Fatalf("enumeration list missing:\n%s", got) } } // The construct that motivated all of this: [target|label] links, which // markdown renders as literal text. func TestFossilWikiLinks(t *testing.T) { got := string(mustFW(t, "See [WikiPage|the page] and [https://example.com|a site].", fakeRes{})) if !strings.Contains(got, `<a href="/r/x/wiki/WikiPage" rel="nofollow">the page</a>`) && !strings.Contains(got, `href="/r/x/wiki/WikiPage"`) { t.Fatalf("wiki link not resolved:\n%s", got) } if !strings.Contains(got, `href="https://example.com"`) || !strings.Contains(got, ">a site<") { t.Fatalf("external link not rendered:\n%s", got) } // Bare [target] uses the target as the label. got = string(mustFW(t, "Go to [WikiPage].", fakeRes{})) if !strings.Contains(got, ">WikiPage<") { t.Fatalf("bare link label:\n%s", got) } // Unresolvable targets stay literal rather than becoming dead links. got = string(mustFW(t, "See [Missing|nope].", fakeRes{})) if strings.Contains(got, "<a ") { t.Fatalf("unresolvable target linked:\n%s", got) } } func TestFossilWikiVerbatimAndHTML(t *testing.T) { got := string(mustFW(t, "<verbatim>[not a link] *not a bullet*</verbatim>", nil)) if !strings.Contains(got, "<pre>") || !strings.Contains(got, "[not a link]") { t.Fatalf("verbatim not preserved:\n%s", got) } if strings.Contains(got, "<a ") { t.Fatal("verbatim content was link-processed") } // Ordinary HTML passes through (rule 6) but still meets the sanitizer. got = string(mustFW(t, "Text with <b>bold</b> and <script>alert(1)</script>.", nil)) if !strings.Contains(got, "<b>bold</b>") { t.Fatalf("inline HTML dropped:\n%s", got) } if strings.Contains(got, "<script") || strings.Contains(got, "alert(1)") { t.Fatalf("sanitizer gate leaked script:\n%s", got) } } func TestFossilWikiIndentedParagraph(t *testing.T) { got := string(mustFW(t, "Normal paragraph.\n\n indented paragraph here\n", nil)) if !strings.Contains(got, "<blockquote>") { t.Fatalf("indented paragraph not rendered:\n%s", got) } } func mustFW(t *testing.T, src string, r Resolver) []byte { t.Helper() out, err := RenderFossilWiki([]byte(src), r) if err != nil { t.Fatal(err) } return out } // Fossil site-relative routes: /doc maps onto quarry's file route, and // a verb quarry has no page for drops the link rather than 404ing. func (fakeRes) ResolvePath(p string) string { if strings.HasPrefix(p, "/doc/") { return "/r/x/file/" + strings.TrimPrefix(p, "/doc/") } return "" } func TestFossilWikiSiteRelativeLinks(t *testing.T) { got := string(mustFW(t, "See [/doc/trunk/README.md|the readme] and [/finfo?name=x|file history].", fakeRes{})) if !strings.Contains(got, `href="/r/x/file/trunk/README.md"`) { t.Fatalf("/doc not mapped to the file route:\n%s", got) } if strings.Contains(got, "/finfo") || strings.Contains(got, "[/finfo") { t.Fatalf("unmapped route left as a link or raw markup:\n%s", got) } if !strings.Contains(got, "file history") { t.Fatalf("label lost when link was dropped:\n%s", got) } }