- eighteen violation plants pin nested src=, Go aliases, unrun examples, build membership and command forms - unit tests cover fence collection, captions, goLang, go doc -c, commandWord and Sync
166 lines
5.7 KiB
Go
166 lines
5.7 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{"<", "&", """} {
|
|
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() != "<x>" {
|
|
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 <lang>\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")
|
|
}
|
|
}
|
|
|
|
func TestGoLang(t *testing.T) {
|
|
for _, lang := range []string{"go", "Go", "GO", "golang", "Golang", "main.go"} {
|
|
if !goLang(lang) {
|
|
t.Errorf("goLang(%q) = false, want true", lang)
|
|
}
|
|
}
|
|
for _, lang := range []string{"go-html-template", "go-text-template", "text", "yaml", "sh", ""} {
|
|
if goLang(lang) {
|
|
t.Errorf("goLang(%q) = true, want false", lang)
|
|
}
|
|
}
|
|
}
|