// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package goldmark
import (
"fmt"
"strings"
"testing"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/highlight"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/markup/converter"
qt "github.com/frankban/quicktest"
)
func convert(c *qt.C, mconf markup_config.Config, content string) converter.ResultRender {
p, err := Provider.New(
converter.ProviderConfig{
MarkupConfig: mconf,
Logger: loggers.NewErrorLogger(),
},
)
c.Assert(err, qt.IsNil)
h := highlight.New(mconf.Highlight)
getRenderer := func(t hooks.RendererType, id any) any {
if t == hooks.CodeBlockRendererType {
return h
}
return nil
}
conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"})
c.Assert(err, qt.IsNil)
b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content), GetRenderer: getRenderer})
c.Assert(err, qt.IsNil)
return b
}
func TestConvert(t *testing.T) {
c := qt.New(t)
// Smoke test of the default configuration.
content := `
## Links
https://github.com/gohugoio/hugo/issues/6528
[Live Demo here!](https://docuapi.netlify.com/)
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
LINE1\n
LINE1\n
")
// Extensions
c.Assert(got, qt.Contains, `Autolink: https://gohugo.io/`)
c.Assert(got, qt.Contains, `Strikethrough:\n", }, /*{ // TODO(bep) this needs an upstream fix, see https://github.com/yuin/goldmark/issues/195 "Code block, CodeFences=false", func(conf *markup_config.Config) { withBlockAttributes(conf) conf.Highlight.CodeFences = false }, "```bash\necho 'foo';\n```\n{.myclass}", "TODO", },*/ { "Code block, CodeFences=true", func(conf *markup_config.Config) { withBlockAttributes(conf) conf.Highlight.CodeFences = true }, "```bash {.myclass id=\"myid\"}\necho 'foo';\n````\n", "foo\nbar
\n
\n\n1\n
\n\necho 'foo';\n
\n\n",
},
{
"Code block, CodeFences=true,lineanchors, default ordinal",
func(conf *markup_config.Config) {
withBlockAttributes(conf)
conf.Highlight.CodeFences = true
conf.Highlight.NoClasses = false
},
"```bash {linenos=inline, anchorlinenos=true}\necho 'foo';\nnecho 'bar';\n```\n\n```bash {linenos=inline, anchorlinenos=true}\necho 'baz';\nnecho 'qux';\n```",
[]string{
"1echo 'foo'",
"2necho 'bar'",
"2necho 'qux'",
},
},
{
"Paragraph",
withBlockAttributes,
"\nHi there.\n{.myclass }",
"Hi there.
\n",
},
{
"Ordered list",
withBlockAttributes,
"\n1. First\n2. Second\n{.myclass }",
"\n- First
\n- Second
\n
\n",
},
{
"Unordered list",
withBlockAttributes,
"\n* First\n* Second\n{.myclass }",
"\n- First
\n- Second
\n
\n",
},
{
"Unordered list, indented",
withBlockAttributes,
`* Fruit
* Apple
* Orange
* Banana
{.fruits}
* Dairy
* Milk
* Cheese
{.dairies}
{.list}`,
[]string{"\n- Fruit\n
", "- Dairy\n
"},
},
{
"Table",
withBlockAttributes,
`| A | B |
| ------------- |:-------------:| -----:|
| AV | BV |
{.myclass }`,
"\n",
},
{
"Title and Blockquote",
withTitleAndBlockAttributes,
"## heading {#id .className attrName=attrValue class=\"class1 class2\"}\n> foo\n> bar\n{.myclass}",
"heading
\nfoo\nbar
\n
\n",
},
} {
c.Run(test.name, func(c *qt.C) {
mconf := markup_config.Default
if test.withConfig != nil {
test.withConfig(&mconf)
}
b := convert(c, mconf, test.input)
got := string(b.Bytes())
for _, s := range cast.ToStringSlice(test.expect) {
c.Assert(got, qt.Contains, s)
}
})
}
}
func TestConvertIssues(t *testing.T) {
c := qt.New(t)
// https://github.com/gohugoio/hugo/issues/7619
c.Run("Hyphen in HTML attributes", func(c *qt.C) {
mconf := markup_config.Default
mconf.Goldmark.Renderer.Unsafe = true
input := `
This will be "slotted" into the custom element.
`
b := convert(c, mconf, input)
got := string(b.Bytes())
c.Assert(got, qt.Contains, "\n This will be \"slotted\" into the custom element.\n \n")
})
}
func TestCodeFence(t *testing.T) {
c := qt.New(t)
lines := `LINE1
LINE2
LINE3
LINE4
LINE5
`
convertForConfig := func(c *qt.C, conf highlight.Config, code, language string) string {
mconf := markup_config.Default
mconf.Highlight = conf
p, err := Provider.New(
converter.ProviderConfig{
MarkupConfig: mconf,
Logger: loggers.NewErrorLogger(),
},
)
h := highlight.New(conf)
getRenderer := func(t hooks.RendererType, id any) any {
if t == hooks.CodeBlockRendererType {
return h
}
return nil
}
content := "```" + language + "\n" + code + "\n```"
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)
b, err := conv.Convert(converter.RenderContext{Src: []byte(content), GetRenderer: getRenderer})
c.Assert(err, qt.IsNil)
return string(b.Bytes())
}
c.Run("Basic", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
result := convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "bash")
// TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func.
c.Assert(result, qt.Equals, "echo "Hugo Rocks!"\n
")
result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown")
c.Assert(result, qt.Equals, "echo "Hugo Rocks!"\n
")
})
c.Run("Highlight lines, default config", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
result := convertForConfig(c, cfg, lines, `bash {linenos=table,hl_lines=[2 "4-5"],linenostart=3}`)
c.Assert(result, qt.Contains, "\n\n4")
result = convertForConfig(c, cfg, lines, "bash {linenos=inline,hl_lines=[2]}")
c.Assert(result, qt.Contains, "2LINE2\n")
c.Assert(result, qt.Not(qt.Contains), "2\n")
})
c.Run("Highlight lines, linenumbers default on", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
cfg.LineNos = true
result := convertForConfig(c, cfg, lines, "bash")
c.Assert(result, qt.Contains, "2\n")
result = convertForConfig(c, cfg, lines, "bash {linenos=false,hl_lines=[2]}")
c.Assert(result, qt.Not(qt.Contains), "class=\"lnt\"")
})
c.Run("Highlight lines, linenumbers default on, linenumbers in table default off", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
cfg.LineNos = true
cfg.LineNumbersInTable = false
result := convertForConfig(c, cfg, lines, "bash")
c.Assert(result, qt.Contains, "2LINE2\n")
result = convertForConfig(c, cfg, lines, "bash {linenos=table}")
c.Assert(result, qt.Contains, "1\n")
})
c.Run("No language", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
cfg.LineNos = true
cfg.LineNumbersInTable = false
result := convertForConfig(c, cfg, lines, "")
c.Assert(result, qt.Contains, "LINE1\n")
})
c.Run("No language, guess syntax", func(c *qt.C) {
cfg := highlight.DefaultConfig
cfg.NoClasses = false
cfg.GuessSyntax = true
cfg.LineNos = true
cfg.LineNumbersInTable = false
result := convertForConfig(c, cfg, lines, "")
c.Assert(result, qt.Contains, "2LINE2\n")
})
}