package validation import ( "context" "strings" "testing" ) func TestParseMarkdownLinksAndSourceLocations(t *testing.T) { source := []byte("" + "é [inline](docs/a\\(b\\).md \"inline title\").\n" + "![chart]( 'image title')\n" + "[ref][target] then [ref][target].\n" + "[collapsed][] and [shortcut].\n" + "![logo][badge]\n" + "\n" + "[target]: docs/target.md \"reference title\"\n" + "[collapsed]: child.md\n" + "[shortcut]: short.md\n" + "[badge]: badge.png\n" + "Inline example: `[fake](fake.md)`.\n" + "```md\n[also fake](fenced.md)\n```\n" + "[undefined]\n" + "Autolink .\n" + "HTML\n") document := parseMarkdown(source) want := []markdownLink{ {Destination: "docs/a(b).md", Title: "inline title", Location: markdownLocation{Line: 1, Column: 4}}, {Destination: "images/chart one.svg", Title: "image title", Image: true, Location: markdownLocation{Line: 2, Column: 1}}, {Destination: "docs/target.md", Title: "reference title", Reference: true, Location: markdownLocation{Line: 3, Column: 1}}, {Destination: "docs/target.md", Title: "reference title", Reference: true, Location: markdownLocation{Line: 3, Column: 20}}, {Destination: "child.md", Reference: true, Location: markdownLocation{Line: 4, Column: 1}}, {Destination: "short.md", Reference: true, Location: markdownLocation{Line: 4, Column: 19}}, {Destination: "badge.png", Image: true, Reference: true, Location: markdownLocation{Line: 5, Column: 1}}, {Destination: "https://example.invalid", Location: markdownLocation{Line: 16, Column: 10}}, } if len(document.links) != len(want) { t.Fatalf("found %d links, want %d: %#v", len(document.links), len(want), document.links) } for index := range want { if got := document.links[index]; got != want[index] { t.Errorf("link %d = %#v, want %#v", index, got, want[index]) } } } func TestParseMarkdownDecodesEntitiesInDestinations(t *testing.T) { document := parseMarkdown([]byte("[entity]()\n")) if len(document.links) != 1 || document.links[0].Destination != "a&b.md" { t.Fatalf("parsed links = %#v, want decoded destination a&b.md", document.links) } } func TestParseMarkdownHeadingAnchors(t *testing.T) { source := []byte("" + "# Café & 東京 `Code Here` Bold!\n" + "Setext **Heading**\n" + "---\n" + "# Foo\n" + "# Foo\n" + "# Foo-1\n" + "# Foo\n" + "# Foo-1\n" + "# \n" + "# \n" + "# Contact Today\n") document := parseMarkdown(source) for _, anchor := range []string{ "café--東京-code-here-bold", "setext-heading", "foo", "foo-1", "foo-1-1", "foo-2", "foo-1-2", "httpsexampleinvalid", "maintainerexampleinvalid", "contact-maintainerexampleinvalid-today", } { if _, ok := document.anchors[anchor]; !ok { t.Errorf("missing anchor %q in %#v", anchor, document.anchors) } } if len(document.anchors) != 10 { t.Fatalf("found %d anchors, want 10: %#v", len(document.anchors), document.anchors) } } func TestMarkdownCacheKeepsParsedDocumentsPerCandidatePath(t *testing.T) { repository := newTreeRepository(t) writeTreeFile(t, repository, "guide.md", []byte("# Guide\n[other](other.md)\n")) writeTreeFile(t, repository, "other.md", []byte("# Other\n")) gitTree(t, repository, "add", "guide.md", "other.md") gitTree(t, repository, "commit", "-m", "add markdown files") commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD")) tree, err := ReadCandidateTree(context.Background(), repository, commit) if err != nil { t.Fatal(err) } cache := newMarkdownCache(tree) first, err := cache.document(context.Background(), "guide.md") if err != nil { t.Fatal(err) } second, err := cache.document(context.Background(), "guide.md") if err != nil { t.Fatal(err) } if first != second { t.Fatal("repeated document lookup returned a different parsed document") } other, err := cache.document(context.Background(), "other.md") if err != nil { t.Fatal(err) } if other == first { t.Fatal("different candidate paths shared a parsed document") } if _, ok := other.anchors["other"]; !ok { t.Fatalf("other document anchors = %#v", other.anchors) } }