Skip to content
quarry Artifact [276d1b9d2d]
Login

Artifact [276d1b9d2d]

Artifact 276d1b9d2dc705f61e60e7187efd92b12a8781cd1a07c96f266ce391470a4dd1:


package markup

import (
	"strings"
	"testing"
)

// Fossil wiki passes hand-written HTML through, so its headings arrive
// with no id — 28 of fossil's own 83 wiki pages are that format, and its
// Cookbook page carries 51 headings and a hand-written index because
// nothing could link to them. Outline is what makes those pages
// navigable without touching the anchors markdown already mints.
func TestOutlineAnchorsHeadingsAndKeepsExistingIDs(t *testing.T) {
	in := []byte(`<h1>Fossil Cookbook</h1>` +
		`<p>words</p>` +
		`<h2 id="already-here">Using <code>fossil ui</code></h2>` +
		`<h2>Using Fossil's Built-In CGI</h2>` +
		`<h3>Windows XP</h3>`)

	got, outline := Outline(in)
	s := string(got)

	if len(outline) != 4 {
		t.Fatalf("outline has %d headings, want 4: %+v", len(outline), outline)
	}
	// A hand-written heading gets GitHub's slug shape.
	if outline[0].ID != "fossil-cookbook" || outline[0].Level != 1 {
		t.Errorf("first heading = %+v, want h1 #fossil-cookbook", outline[0])
	}
	if !strings.Contains(s, `<h1 id="fossil-cookbook">`) {
		t.Errorf("anchor was not written into the html:\n%s", s)
	}
	// An id already present is LEFT ALONE: goldmark's slugs are what
	// existing deep links point at.
	if outline[1].ID != "already-here" {
		t.Errorf("existing id was rewritten to %q", outline[1].ID)
	}
	if strings.Count(s, `id="already-here"`) != 1 {
		t.Errorf("existing id duplicated:\n%s", s)
	}
	// Markup inside a heading is stripped for the label but kept in the
	// document — a contents entry wants the words, not the tags.
	if outline[1].Text != "Using fossil ui" {
		t.Errorf("label = %q, want the tags stripped", outline[1].Text)
	}
	if !strings.Contains(s, "<code>fossil ui</code>") {
		t.Error("stripping the label also stripped the document's own markup")
	}
	// An apostrophe is punctuation, not a word break that doubles a dash.
	if outline[2].ID != "using-fossil-s-built-in-cgi" {
		t.Errorf("slug = %q", outline[2].ID)
	}
	if outline[3].Level != 3 {
		t.Errorf("level = %d, want 3 reported as written", outline[3].Level)
	}
}

// Two headings of the same words must not answer to one link.
func TestOutlineDeduplicatesRepeatedHeadings(t *testing.T) {
	_, outline := Outline([]byte(`<h2>Setup</h2><h2>Setup</h2><h2>Setup</h2>`))
	if len(outline) != 3 {
		t.Fatalf("want 3 headings, got %d", len(outline))
	}
	ids := map[string]bool{}
	for _, h := range outline {
		if ids[h.ID] {
			t.Fatalf("duplicate id %q across %+v", h.ID, outline)
		}
		ids[h.ID] = true
	}
	if outline[0].ID != "setup" || outline[1].ID != "setup-1" {
		t.Errorf("ids = %q, %q; want setup, setup-1", outline[0].ID, outline[1].ID)
	}
}

// The sanitizer's id pattern demands a leading alphanumeric. A heading
// that slugs to nothing, or to something starting with punctuation,
// still needs a target the policy would admit on a later pass.
func TestOutlineMintsIDsTheSanitizerWouldAccept(t *testing.T) {
	_, outline := Outline([]byte(`<h2>!!!</h2><h2>—dash lead</h2>`))
	if len(outline) != 2 {
		t.Fatalf("want 2, got %d", len(outline))
	}
	for _, h := range outline {
		if h.ID == "" {
			t.Fatal("a heading was left with no target")
		}
		c := h.ID[0]
		if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') {
			t.Errorf("id %q does not start with an alphanumeric", h.ID)
		}
	}
}

// Outline runs on rendered output, so it must agree with what the two
// renderers actually produce rather than with a hand-written fixture.
func TestOutlineAgreesWithBothRenderers(t *testing.T) {
	md, err := Render([]byte("# Title\n\n## A Section\n\ntext\n"), nil)
	if err != nil {
		t.Fatal(err)
	}
	_, mdOutline := Outline(md)
	if len(mdOutline) != 2 || mdOutline[1].ID != "a-section" {
		t.Errorf("markdown outline = %+v", mdOutline)
	}

	fw, err := RenderFossilWiki([]byte("<h1>Title</h1>\n\n<h2>A Section</h2>\n\ntext\n"), nil)
	if err != nil {
		t.Fatal(err)
	}
	amended, fwOutline := Outline(fw)
	if len(fwOutline) != 2 || fwOutline[1].ID != "a-section" {
		t.Fatalf("fossil-wiki outline = %+v", fwOutline)
	}
	// The two formats must address alike, or a link that works on one
	// kind of page breaks on the other.
	if mdOutline[1].ID != fwOutline[1].ID {
		t.Errorf("same heading addresses differently: %q vs %q",
			mdOutline[1].ID, fwOutline[1].ID)
	}
	if !strings.Contains(string(amended), `id="a-section"`) {
		t.Errorf("fossil-wiki heading never got its anchor:\n%s", amended)
	}
}