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