Artifact 41fbac5e9709ad7190ba2527e3939e9d7ee206ccfb21b7880a2e48b41608eb0f:
- File
internal/markup/fossilwiki_test.go
— part of check-in
[44ba7c78f8]
at
2026-08-23 01:03:04
on branch doc-links
— doc: relative links between documents, and their images
Fossil's docs link each other as Quick Start and those stayed literal text, so a rendered doc was a dead end. Relative targets now resolve for tree documents only — a wiki artifact sits at no path and must not emit links relative to one — and the href is left relative so the browser resolves it against the doc's own URL. Images are served as bytes under the same route the markup already points at.
Also stops double-escaping a character reference the author wrote: fossil's own index rendered "Tips & Hints". (user: dmestas, size: 5849) [annotate] [blame] [check-ins using] [more...]
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) } } // Fossil's docs link each other relatively — [./quickstart.wiki|Quick // Start] — and a wiki artifact, which sits at no path, has no sibling to // resolve that against. Only a DocResolver gets those links; everyone // else keeps the old literal, so a wiki page cannot emit an href // relative to a URL it does not live at. type docRes struct{ fakeRes } func (docRes) DocRelative(t string) string { return t } func TestRelativeLinksResolveOnlyForDocuments(t *testing.T) { src := []byte(`[./quickstart.wiki | Quick Start] and [../COPYRIGHT-BSD2.txt|License] ` + `and [./concepts.wiki#workflow | autosync]`) doc, err := RenderFossilWiki(src, docRes{}) if err != nil { t.Fatal(err) } for _, want := range []string{ `href="./quickstart.wiki"`, `href="../COPYRIGHT-BSD2.txt"`, `href="./concepts.wiki#workflow"`, // a fragment rides along } { if !strings.Contains(string(doc), want) { t.Errorf("document render missing %s:\n%s", want, doc) } } // A wiki page must not: it would emit a link relative to /wiki/Name. page, err := RenderFossilWiki(src, fakeRes{}) if err != nil { t.Fatal(err) } if strings.Contains(string(page), `href="./`) { t.Errorf("a wiki artifact emitted a relative document link:\n%s", page) } } // "a safe subset of HTML" includes character references, so an ampersand // the author already spelled must not be spelled again. func TestLinkLabelsKeepEntitiesTheAuthorWrote(t *testing.T) { out, err := RenderFossilWiki([]byte(`[./hints.wiki | Tips & Hints]`), docRes{}) if err != nil { t.Fatal(err) } if strings.Contains(string(out), "&amp;") { t.Errorf("entity escaped twice:\n%s", out) } if !strings.Contains(string(out), "Tips & Hints") { t.Errorf("label lost its ampersand:\n%s", out) } // A bare ampersand still has to be escaped exactly once. bare, err := RenderFossilWiki([]byte(`[./a.wiki | Tom & Jerry]`), docRes{}) if err != nil { t.Fatal(err) } if !strings.Contains(string(bare), "Tom & Jerry") { t.Errorf("bare ampersand not escaped:\n%s", bare) } }