- 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)
153 lines
5.4 KiB
Go
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{"<", "&", """} {
|
|
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")
|
|
}
|
|
}
|