Skip to content
quarry Artifact [d3a9d4b8dd]
Login

Artifact [d3a9d4b8dd]

Artifact d3a9d4b8dd9616b9305e7b9cf2e39a3278283acc1c40adcd6e411416f9279b6c:


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)
	}
}