hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

pageparser_shortcode_test.go (13934B)

    1 // Copyright 2018 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package pageparser
   15 
   16 import (
   17 	"testing"
   18 )
   19 
   20 var (
   21 	tstEOF            = nti(tEOF, "")
   22 	tstLeftNoMD       = nti(tLeftDelimScNoMarkup, "{{<")
   23 	tstRightNoMD      = nti(tRightDelimScNoMarkup, ">}}")
   24 	tstLeftMD         = nti(tLeftDelimScWithMarkup, "{{%")
   25 	tstRightMD        = nti(tRightDelimScWithMarkup, "%}}")
   26 	tstSCClose        = nti(tScClose, "/")
   27 	tstSC1            = nti(tScName, "sc1")
   28 	tstSC1Inline      = nti(tScNameInline, "sc1.inline")
   29 	tstSC2Inline      = nti(tScNameInline, "sc2.inline")
   30 	tstSC2            = nti(tScName, "sc2")
   31 	tstSC3            = nti(tScName, "sc3")
   32 	tstSCSlash        = nti(tScName, "sc/sub")
   33 	tstParam1         = nti(tScParam, "param1")
   34 	tstParam2         = nti(tScParam, "param2")
   35 	tstParamBoolTrue  = nti(tScParam, "true")
   36 	tstParamBoolFalse = nti(tScParam, "false")
   37 	tstParamInt       = nti(tScParam, "32")
   38 	tstParamFloat     = nti(tScParam, "3.14")
   39 	tstVal            = nti(tScParamVal, "Hello World")
   40 	tstText           = nti(tText, "Hello World")
   41 )
   42 
   43 var shortCodeLexerTests = []lexerTest{
   44 	{"empty", "", []Item{tstEOF}},
   45 	{"spaces", " \t\n", []Item{nti(tText, " \t\n"), tstEOF}},
   46 	{"text", `to be or not`, []Item{nti(tText, "to be or not"), tstEOF}},
   47 	{"no markup", `{{< sc1 >}}`, []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
   48 	{"with EOL", "{{< sc1 \n >}}", []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
   49 
   50 	{"forward slash inside name", `{{< sc/sub >}}`, []Item{tstLeftNoMD, tstSCSlash, tstRightNoMD, tstEOF}},
   51 
   52 	{"simple with markup", `{{% sc1 %}}`, []Item{tstLeftMD, tstSC1, tstRightMD, tstEOF}},
   53 	{"with spaces", `{{<     sc1     >}}`, []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
   54 	{"indented on new line", "Hello\n    {{% sc1 %}}", []Item{nti(tText, "Hello\n"), nti(tIndentation, "    "), tstLeftMD, tstSC1, tstRightMD, tstEOF}},
   55 	{"indented on new line tab", "Hello\n\t{{% sc1 %}}", []Item{nti(tText, "Hello\n"), nti(tIndentation, "\t"), tstLeftMD, tstSC1, tstRightMD, tstEOF}},
   56 	{"indented on first line", "    {{% sc1 %}}", []Item{nti(tIndentation, "    "), tstLeftMD, tstSC1, tstRightMD, tstEOF}},
   57 	{"mismatched rightDelim", `{{< sc1 %}}`, []Item{
   58 		tstLeftNoMD, tstSC1,
   59 		nti(tError, "unrecognized character in shortcode action: U+0025 '%'. Note: Parameters with non-alphanumeric args must be quoted"),
   60 	}},
   61 	{"inner, markup", `{{% sc1 %}} inner {{% /sc1 %}}`, []Item{
   62 		tstLeftMD,
   63 		tstSC1,
   64 		tstRightMD,
   65 		nti(tText, " inner "),
   66 		tstLeftMD,
   67 		tstSCClose,
   68 		tstSC1,
   69 		tstRightMD,
   70 		tstEOF,
   71 	}},
   72 	{"close, but no open", `{{< /sc1 >}}`, []Item{
   73 		tstLeftNoMD, nti(tError, "got closing shortcode, but none is open"),
   74 	}},
   75 	{"close wrong", `{{< sc1 >}}{{< /another >}}`, []Item{
   76 		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose,
   77 		nti(tError, "closing tag for shortcode 'another' does not match start tag"),
   78 	}},
   79 	{"close, but no open, more", `{{< sc1 >}}{{< /sc1 >}}{{< /another >}}`, []Item{
   80 		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose,
   81 		nti(tError, "closing tag for shortcode 'another' does not match start tag"),
   82 	}},
   83 	{"close with extra keyword", `{{< sc1 >}}{{< /sc1 keyword>}}`, []Item{
   84 		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1,
   85 		nti(tError, "unclosed shortcode"),
   86 	}},
   87 	{"float param, positional", `{{< sc1 3.14 >}}`, []Item{
   88 		tstLeftNoMD, tstSC1, nti(tScParam, "3.14"), tstRightNoMD, tstEOF,
   89 	}},
   90 	{"float param, named", `{{< sc1 param1=3.14 >}}`, []Item{
   91 		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "3.14"), tstRightNoMD, tstEOF,
   92 	}},
   93 	{"named param, raw string", `{{< sc1 param1=` + "`" + "Hello World" + "`" + " >}}", []Item{
   94 		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "Hello World"), tstRightNoMD, tstEOF,
   95 	}},
   96 	{"float param, named, space before", `{{< sc1 param1= 3.14 >}}`, []Item{
   97 		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "3.14"), tstRightNoMD, tstEOF,
   98 	}},
   99 	{"Youtube id", `{{< sc1 -ziL-Q_456igdO-4 >}}`, []Item{
  100 		tstLeftNoMD, tstSC1, nti(tScParam, "-ziL-Q_456igdO-4"), tstRightNoMD, tstEOF,
  101 	}},
  102 	{"non-alphanumerics param quoted", `{{< sc1 "-ziL-.%QigdO-4" >}}`, []Item{
  103 		tstLeftNoMD, tstSC1, nti(tScParam, "-ziL-.%QigdO-4"), tstRightNoMD, tstEOF,
  104 	}},
  105 	{"raw string", `{{< sc1` + "`" + "Hello World" + "`" + ` >}}`, []Item{
  106 		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), tstRightNoMD, tstEOF,
  107 	}},
  108 	{"raw string with newline", `{{< sc1` + "`" + `Hello 
  109 	World` + "`" + ` >}}`, []Item{
  110 		tstLeftNoMD, tstSC1, nti(tScParam, `Hello 
  111 	World`), tstRightNoMD, tstEOF,
  112 	}},
  113 	{"raw string with escape character", `{{< sc1` + "`" + `Hello \b World` + "`" + ` >}}`, []Item{
  114 		tstLeftNoMD, tstSC1, nti(tScParam, `Hello \b World`), tstRightNoMD, tstEOF,
  115 	}},
  116 	{"two params", `{{< sc1 param1   param2 >}}`, []Item{
  117 		tstLeftNoMD, tstSC1, tstParam1, tstParam2, tstRightNoMD, tstEOF,
  118 	}},
  119 	// issue #934
  120 	{"self-closing", `{{< sc1 />}}`, []Item{
  121 		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF,
  122 	}},
  123 	// Issue 2498
  124 	{"multiple self-closing", `{{< sc1 />}}{{< sc1 />}}`, []Item{
  125 		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD,
  126 		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF,
  127 	}},
  128 	{"self-closing with param", `{{< sc1 param1 />}}`, []Item{
  129 		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
  130 	}},
  131 	{"multiple self-closing with param", `{{< sc1 param1 />}}{{< sc1 param1 />}}`, []Item{
  132 		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD,
  133 		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
  134 	}},
  135 	{"multiple different self-closing with param", `{{< sc1 param1 />}}{{< sc2 param1 />}}`, []Item{
  136 		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD,
  137 		tstLeftNoMD, tstSC2, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
  138 	}},
  139 	{"nested simple", `{{< sc1 >}}{{< sc2 >}}{{< /sc1 >}}`, []Item{
  140 		tstLeftNoMD, tstSC1, tstRightNoMD,
  141 		tstLeftNoMD, tstSC2, tstRightNoMD,
  142 		tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF,
  143 	}},
  144 	{"nested complex", `{{< sc1 >}}ab{{% sc2 param1 %}}cd{{< sc3 >}}ef{{< /sc3 >}}gh{{% /sc2 %}}ij{{< /sc1 >}}kl`, []Item{
  145 		tstLeftNoMD, tstSC1, tstRightNoMD,
  146 		nti(tText, "ab"),
  147 		tstLeftMD, tstSC2, tstParam1, tstRightMD,
  148 		nti(tText, "cd"),
  149 		tstLeftNoMD, tstSC3, tstRightNoMD,
  150 		nti(tText, "ef"),
  151 		tstLeftNoMD, tstSCClose, tstSC3, tstRightNoMD,
  152 		nti(tText, "gh"),
  153 		tstLeftMD, tstSCClose, tstSC2, tstRightMD,
  154 		nti(tText, "ij"),
  155 		tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD,
  156 		nti(tText, "kl"), tstEOF,
  157 	}},
  158 
  159 	{"two quoted params", `{{< sc1 "param nr. 1" "param nr. 2" >}}`, []Item{
  160 		tstLeftNoMD, tstSC1, nti(tScParam, "param nr. 1"), nti(tScParam, "param nr. 2"), tstRightNoMD, tstEOF,
  161 	}},
  162 	{"two named params", `{{< sc1 param1="Hello World" param2="p2Val">}}`, []Item{
  163 		tstLeftNoMD, tstSC1, tstParam1, tstVal, tstParam2, nti(tScParamVal, "p2Val"), tstRightNoMD, tstEOF,
  164 	}},
  165 	{"escaped quotes", `{{< sc1 param1=\"Hello World\"  >}}`, []Item{
  166 		tstLeftNoMD, tstSC1, tstParam1, tstVal, tstRightNoMD, tstEOF,
  167 	}},
  168 	{"escaped quotes, positional param", `{{< sc1 \"param1\"  >}}`, []Item{
  169 		tstLeftNoMD, tstSC1, tstParam1, tstRightNoMD, tstEOF,
  170 	}},
  171 	{"escaped quotes inside escaped quotes", `{{< sc1 param1=\"Hello \"escaped\" World\"  >}}`, []Item{
  172 		tstLeftNoMD, tstSC1, tstParam1,
  173 		nti(tScParamVal, `Hello `), nti(tError, `got positional parameter 'escaped'. Cannot mix named and positional parameters`),
  174 	}},
  175 	{
  176 		"escaped quotes inside nonescaped quotes",
  177 		`{{< sc1 param1="Hello \"escaped\" World"  >}}`,
  178 		[]Item{
  179 			tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, `Hello "escaped" World`), tstRightNoMD, tstEOF,
  180 		},
  181 	},
  182 	{
  183 		"escaped quotes inside nonescaped quotes in positional param",
  184 		`{{< sc1 "Hello \"escaped\" World"  >}}`,
  185 		[]Item{
  186 			tstLeftNoMD, tstSC1, nti(tScParam, `Hello "escaped" World`), tstRightNoMD, tstEOF,
  187 		},
  188 	},
  189 	{"escaped raw string, named param", `{{< sc1 param1=` + `\` + "`" + "Hello World" + `\` + "`" + ` >}}`, []Item{
  190 		tstLeftNoMD, tstSC1, tstParam1, nti(tError, "unrecognized escape character"),
  191 	}},
  192 	{"escaped raw string, positional param", `{{< sc1 param1 ` + `\` + "`" + "Hello World" + `\` + "`" + ` >}}`, []Item{
  193 		tstLeftNoMD, tstSC1, tstParam1, nti(tError, "unrecognized escape character"),
  194 	}},
  195 	{"two raw string params", `{{< sc1` + "`" + "Hello World" + "`" + "`" + "Second Param" + "`" + ` >}}`, []Item{
  196 		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), nti(tScParam, "Second Param"), tstRightNoMD, tstEOF,
  197 	}},
  198 	{"unterminated quote", `{{< sc1 param2="Hello World>}}`, []Item{
  199 		tstLeftNoMD, tstSC1, tstParam2, nti(tError, "unterminated quoted string in shortcode parameter-argument: 'Hello World>}}'"),
  200 	}},
  201 	{"unterminated raw string", `{{< sc1` + "`" + "Hello World" + ` >}}`, []Item{
  202 		tstLeftNoMD, tstSC1, nti(tError, "unterminated raw string in shortcode parameter-argument: 'Hello World >}}'"),
  203 	}},
  204 	{"unterminated raw string in second argument", `{{< sc1` + "`" + "Hello World" + "`" + "`" + "Second Param" + ` >}}`, []Item{
  205 		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), nti(tError, "unterminated raw string in shortcode parameter-argument: 'Second Param >}}'"),
  206 	}},
  207 	{"one named param, one not", `{{< sc1 param1="Hello World" p2 >}}`, []Item{
  208 		tstLeftNoMD, tstSC1, tstParam1, tstVal,
  209 		nti(tError, "got positional parameter 'p2'. Cannot mix named and positional parameters"),
  210 	}},
  211 	{"one named param, one quoted positional param, both raw strings", `{{< sc1 param1=` + "`" + "Hello World" + "`" + "`" + "Second Param" + "`" + ` >}}`, []Item{
  212 		tstLeftNoMD, tstSC1, tstParam1, tstVal,
  213 		nti(tError, "got quoted positional parameter. Cannot mix named and positional parameters"),
  214 	}},
  215 	{"one named param, one quoted positional param", `{{< sc1 param1="Hello World" "And Universe" >}}`, []Item{
  216 		tstLeftNoMD, tstSC1, tstParam1, tstVal,
  217 		nti(tError, "got quoted positional parameter. Cannot mix named and positional parameters"),
  218 	}},
  219 	{"one quoted positional param, one named param", `{{< sc1 "param1" param2="And Universe" >}}`, []Item{
  220 		tstLeftNoMD, tstSC1, tstParam1,
  221 		nti(tError, "got named parameter 'param2'. Cannot mix named and positional parameters"),
  222 	}},
  223 	{"ono positional param, one not", `{{< sc1 param1 param2="Hello World">}}`, []Item{
  224 		tstLeftNoMD, tstSC1, tstParam1,
  225 		nti(tError, "got named parameter 'param2'. Cannot mix named and positional parameters"),
  226 	}},
  227 	{"commented out", `{{</* sc1 */>}}`, []Item{
  228 		nti(tText, "{{<"), nti(tText, " sc1 "), nti(tText, ">}}"), tstEOF,
  229 	}},
  230 	{"commented out, with asterisk inside", `{{</* sc1 "**/*.pdf" */>}}`, []Item{
  231 		nti(tText, "{{<"), nti(tText, " sc1 \"**/*.pdf\" "), nti(tText, ">}}"), tstEOF,
  232 	}},
  233 	{"commented out, missing close", `{{</* sc1 >}}`, []Item{
  234 		nti(tError, "comment must be closed"),
  235 	}},
  236 	{"commented out, misplaced close", `{{</* sc1 >}}*/`, []Item{
  237 		nti(tError, "comment must be closed"),
  238 	}},
  239 	// Inline shortcodes
  240 	{"basic inline", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
  241 	{"basic inline with space", `{{< sc1.inline >}}Hello World{{< / sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
  242 	{"inline self closing", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD, tstEOF}},
  243 	{"inline self closing, then a new inline", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}{{< sc2.inline >}}Hello World{{< /sc2.inline >}}`, []Item{
  244 		tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD,
  245 		tstLeftNoMD, tstSC2Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC2Inline, tstRightNoMD, tstEOF,
  246 	}},
  247 	{"inline with template syntax", `{{< sc1.inline >}}{{ .Get 0 }}{{ .Get 1 }}{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, nti(tText, "{{ .Get 0 }}"), nti(tText, "{{ .Get 1 }}"), tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
  248 	{"inline with nested shortcode (not supported)", `{{< sc1.inline >}}Hello World{{< sc1 >}}{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, nti(tError, "inline shortcodes do not support nesting")}},
  249 	{"inline case mismatch", `{{< sc1.Inline >}}Hello World{{< /sc1.Inline >}}`, []Item{tstLeftNoMD, nti(tError, "period in shortcode name only allowed for inline identifiers")}},
  250 }
  251 
  252 func TestShortcodeLexer(t *testing.T) {
  253 	t.Parallel()
  254 	for i, test := range shortCodeLexerTests {
  255 		t.Run(test.name, func(t *testing.T) {
  256 			items := collect([]byte(test.input), true, lexMainSection)
  257 			if !equal(items, test.items) {
  258 				t.Errorf("[%d] %s: got\n\t%v\nexpected\n\t%v", i, test.name, items, test.items)
  259 			}
  260 		})
  261 	}
  262 }
  263 
  264 func BenchmarkShortcodeLexer(b *testing.B) {
  265 	testInputs := make([][]byte, len(shortCodeLexerTests))
  266 	for i, input := range shortCodeLexerTests {
  267 		testInputs[i] = []byte(input.input)
  268 	}
  269 	var cfg Config
  270 	b.ResetTimer()
  271 	for i := 0; i < b.N; i++ {
  272 		for _, input := range testInputs {
  273 			items := collectWithConfig(input, true, lexMainSection, cfg)
  274 			if len(items) == 0 {
  275 			}
  276 
  277 		}
  278 	}
  279 }