// 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 ( "strings" "testing" "github.com/spf13/cast" "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.Result { p, err := Provider.New( converter.ProviderConfig{ MarkupConfig: mconf, Logger: loggers.NewErrorLogger(), }, ) c.Assert(err, qt.IsNil) conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"}) c.Assert(err, qt.IsNil) b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content)}) 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") ## Code Fences §§§bash LINE1 §§§ ## Code Fences No Lexer §§§moo LINE1 §§§ ## Custom ID {#custom} ## Auto ID * Autolink: https://gohugo.io/ * Strikethrough:~~Hi~~ Hello, world! ## Table | foo | bar | | --- | --- | | baz | bim | ## Task Lists (default on) - [x] Finish my changes[^1] - [ ] Push my commits to GitHub - [ ] Open a pull request ## Smartypants (default on) * Straight double "quotes" and single 'quotes' into “curly” quote HTML entities * Dashes (“--” and “---”) into en- and em-dash entities * Three consecutive dots (“...”) into an ellipsis entity * Apostrophes are also converted: "That was back in the '90s, that's a long time ago" ## Footnotes That's some text with a footnote.[^1] ## Definition Lists date : the datetime assigned to this page. description : the description for the content. ## 神真美好 ## 神真美好 ## 神真美好 [^1]: And that's the footnote. ` // Code fences content = strings.Replace(content, "§§§", "```", -1) mconf := markup_config.Default mconf.Highlight.NoClasses = false mconf.Goldmark.Renderer.Unsafe = true b := convert(c, mconf, content) got := string(b.Bytes()) // Links // c.Assert(got, qt.Contains, `Live Demo here!`) // Header IDs c.Assert(got, qt.Contains, `
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
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(),
},
)
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)})
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!"
`)
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<")
})
}