Files
summercms/internal/docsite/highlight_test.go
Jakub Zych 28afd4d197 test(11.1-06): branch-level tests for every docsite stage (94.8% coverage)
- load, render, emit, snippet, highlight, serve and checker branch tests;
  docs:build --check, docs:sync and docs:serve output tests in cmd/summer
- fix: a CRLF or BOM page is reported as such, not as a page without a
  frontmatter block (TestFrontmatterEncodingProblems)
- fix: src=#Type.Method finds a method with a parenthesised receiver
  (TestSnippetGenericsAndGroups, Box.Paren)
- fix: docs:serve adds its watches before announcing the server, so an
  edit made right after the serving line rebuilds (TestServeWatchRebuilds)
2026-10-01 00:05:12 +02:00

153 lines
5.4 KiB
Go

package docsite
import (
"html"
"regexp"
"strings"
"testing"
)
// spanText matches one tok-* span.
var spanText = regexp.MustCompile(`<span class="(tok-[a-z]+)">([^<]*)</span>`)
// tokens returns the class and unescaped text of every tok-* span.
func tokens(out string) map[string][]string {
got := map[string][]string{}
for _, m := range spanText.FindAllStringSubmatch(out, -1) {
got[m[1]] = append(got[m[1]], html.UnescapeString(m[2]))
}
return got
}
func assertToken(t *testing.T, lang, out, class, text string) {
t.Helper()
for _, v := range tokens(out)[class] {
if strings.TrimSpace(v) == text {
return
}
}
t.Errorf("%s: no %s span for %q in\n%s", lang, class, text, out)
}
// assertNoTags fails when output has markup other than tok-* spans.
func assertNoTags(t *testing.T, lang, out string) {
t.Helper()
rest := spanText.ReplaceAllString(out, "")
if strings.ContainsAny(rest, "<>") {
t.Errorf("%s: unexpected markup in %q", lang, rest)
}
if strings.Contains(out, "style=") {
t.Errorf("%s: inline style in %q", lang, out)
}
}
func TestHighlightGo(t *testing.T) {
src := "package main\n\n// Say says.\nfunc Say() (int, string) {\n\treturn 42, \"a<b & \\\"c\\\"\"\n}\n"
out := highlight("go", src)
assertToken(t, "go", out, "tok-kw", "package")
assertToken(t, "go", out, "tok-kw", "func")
assertToken(t, "go", out, "tok-kw", "return")
assertToken(t, "go", out, "tok-com", "// Say says.")
assertToken(t, "go", out, "tok-num", "42")
assertToken(t, "go", out, "tok-str", `"a<b & \"c\""`)
assertNoTags(t, "go", out)
for _, want := range []string{"&lt;", "&amp;", "&#34;"} {
if !strings.Contains(out, want) {
t.Errorf("go output does not escape to %s: %s", want, out)
}
}
if html.UnescapeString(spanText.ReplaceAllString(out, "$2")) != src {
t.Errorf("go output does not round-trip to the source:\n%s", out)
}
}
func TestHighlightYAML(t *testing.T) {
src := "# comment\nname: \"quoted <x>\"\nport: 5432\nenabled: true\nplain: text value\n"
for _, lang := range []string{"yaml", "yml"} {
out := highlight(lang, src)
assertToken(t, lang, out, "tok-com", "# comment")
assertToken(t, lang, out, "tok-key", "name")
assertToken(t, lang, out, "tok-str", `"quoted <x>"`)
assertToken(t, lang, out, "tok-num", "5432")
assertToken(t, lang, out, "tok-num", "true")
assertToken(t, lang, out, "tok-str", "text value")
assertNoTags(t, lang, out)
}
out := highlight("json", `{"a": null, "b": 1}`)
assertToken(t, "json", out, "tok-num", "null")
assertToken(t, "json", out, "tok-num", "1")
assertNoTags(t, "json", out)
}
func TestHighlightShell(t *testing.T) {
src := "$ summer docs:build --out \"site\" # build\n# a comment line\n ./bin/acme migrate \\\n --force\n\nsummer\n"
for _, lang := range []string{"sh", "shell", "bash", "console"} {
out := highlight(lang, src)
assertToken(t, lang, out, "tok-prompt", "$")
assertToken(t, lang, out, "tok-kw", "summer")
assertToken(t, lang, out, "tok-kw", "./bin/acme")
assertToken(t, lang, out, "tok-com", "# a comment line")
assertToken(t, lang, out, "tok-com", "# build")
assertToken(t, lang, out, "tok-str", `"site"`)
assertNoTags(t, lang, out)
// The continuation line has no command word.
for _, kw := range tokens(out)["tok-kw"] {
if strings.Contains(kw, "--force") {
t.Errorf("%s: continuation line highlighted as a command: %q", lang, kw)
}
}
if !strings.HasPrefix(out, `<span class="tok-prompt">$ </span><span class="tok-kw">summer</span> `) {
t.Errorf("%s: output starts %q", lang, out[:min(len(out), 80)])
}
if !strings.Contains(out, "\n <span class=\"tok-kw\">./bin/acme</span>") {
t.Errorf("%s: indentation before a command was not kept:\n%s", lang, out)
}
if html.UnescapeString(spanText.ReplaceAllString(out, "$2")) != src {
t.Errorf("%s: output does not round-trip to the source:\n%s", lang, out)
}
}
// A line fragment without a trailing newline stays one line.
if out := highlight("sh", "summer serve"); strings.Contains(out, "\n") {
t.Errorf("sh fragment gained a newline: %q", out)
}
}
func TestHighlightFallback(t *testing.T) {
src := "a < b && c > \"d\" 'e'"
want := html.EscapeString(src)
for _, lang := range []string{"", "text", "txt", "plain", "plaintext", "no-such-language"} {
if got := highlight(lang, src); got != want {
t.Errorf("highlight(%q) = %q, want escaped plain text %q", lang, got, want)
}
}
if lexerFor("go") == nil || lexerFor("text") != nil || lexerFor("no-such-language") != nil {
t.Error("lexerFor")
}
var b strings.Builder
writeSpan(&b, "tok-kw", "")
writeSpan(&b, "", "<x>")
if b.String() != "&lt;x&gt;" {
t.Errorf("writeSpan = %q", b.String())
}
}
func TestHighlightFenceMarkup(t *testing.T) {
s, pages := renderFixture(t, map[string]string{
"docs/setup/start.md": page("Start", "setup", 10, "```\nno <lang>\n```\n\n```go title=x\nx := 1\n```\n"),
})
out := string(renderHTML(t, s, pages["setup/start"]).html)
for _, want := range []string{
`<figure class="code"><button type="button" class="copy-button" aria-label="Copy code" hidden><svg class="icon icon-copy"`,
`<svg class="icon icon-check"`,
"</button><pre><code>no &lt;lang&gt;\n</code></pre></figure>",
`<pre><code class="language-go">x := <span class="tok-num">1</span>`,
} {
if !strings.Contains(out, want) {
t.Errorf("missing %s in\n%s", want, out)
}
}
if strings.Contains(out, "<figcaption>") {
t.Error("a fence without src= has a caption")
}
}