Artifact 276d1b9d2dc705f61e60e7187efd92b12a8781cd1a07c96f266ce391470a4dd1:
- File
internal/markup/outline_test.go
— part of check-in
[c195eed2fb]
at
2026-08-23 00:33:47
on branch wiki-read
— wiki: contents, a measure, sibling pages and a real index
Long pages were unreadable: fossil's Cookbook runs 26,000 pixels with 51 headings, no contents, and lines of ~152 characters — its author had hand-written an index at the top, which is the need stated in the only place they could state it. Headings now carry anchors whichever format wrote them (Fossil wiki passes raw HTML through, so half a fossil corpus had none), a contents rail tracks the reading position, and the measure is capped near 65.
The index gains a filter, a sort, revision counts, two columns and the Home page pinned above them. Not paged: the largest wiki in this corpus is 172 pages, and a browse list you page through is one you cannot scan.
Also fixes the global nav-link box leaking into both new rails and into the ticket rail, which drew a border around the current entry. (user: dmestas, size: 4398) [annotate] [blame] [check-ins using] [more...]
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) } }