github.com/dominikszabo/hugo-ds-clean@v0.47.1/hugolib/shortcodeparser_test.go (about) 1 // Copyright 2015 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 hugolib 15 16 import ( 17 "testing" 18 ) 19 20 type shortCodeLexerTest struct { 21 name string 22 input string 23 items []item 24 } 25 26 var ( 27 tstEOF = item{tEOF, 0, ""} 28 tstLeftNoMD = item{tLeftDelimScNoMarkup, 0, "{{<"} 29 tstRightNoMD = item{tRightDelimScNoMarkup, 0, ">}}"} 30 tstLeftMD = item{tLeftDelimScWithMarkup, 0, "{{%"} 31 tstRightMD = item{tRightDelimScWithMarkup, 0, "%}}"} 32 tstSCClose = item{tScClose, 0, "/"} 33 tstSC1 = item{tScName, 0, "sc1"} 34 tstSC2 = item{tScName, 0, "sc2"} 35 tstSC3 = item{tScName, 0, "sc3"} 36 tstSCSlash = item{tScName, 0, "sc/sub"} 37 tstParam1 = item{tScParam, 0, "param1"} 38 tstParam2 = item{tScParam, 0, "param2"} 39 tstVal = item{tScParamVal, 0, "Hello World"} 40 ) 41 42 var shortCodeLexerTests = []shortCodeLexerTest{ 43 {"empty", "", []item{tstEOF}}, 44 {"spaces", " \t\n", []item{{tText, 0, " \t\n"}, tstEOF}}, 45 {"text", `to be or not`, []item{{tText, 0, "to be or not"}, tstEOF}}, 46 {"no markup", `{{< sc1 >}}`, []item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}}, 47 {"with EOL", "{{< sc1 \n >}}", []item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}}, 48 49 {"forward slash inside name", `{{< sc/sub >}}`, []item{tstLeftNoMD, tstSCSlash, tstRightNoMD, tstEOF}}, 50 51 {"simple with markup", `{{% sc1 %}}`, []item{tstLeftMD, tstSC1, tstRightMD, tstEOF}}, 52 {"with spaces", `{{< sc1 >}}`, []item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}}, 53 {"mismatched rightDelim", `{{< sc1 %}}`, []item{tstLeftNoMD, tstSC1, 54 {tError, 0, "unrecognized character in shortcode action: U+0025 '%'. Note: Parameters with non-alphanumeric args must be quoted"}}}, 55 {"inner, markup", `{{% sc1 %}} inner {{% /sc1 %}}`, []item{ 56 tstLeftMD, 57 tstSC1, 58 tstRightMD, 59 {tText, 0, " inner "}, 60 tstLeftMD, 61 tstSCClose, 62 tstSC1, 63 tstRightMD, 64 tstEOF, 65 }}, 66 {"close, but no open", `{{< /sc1 >}}`, []item{ 67 tstLeftNoMD, {tError, 0, "got closing shortcode, but none is open"}}}, 68 {"close wrong", `{{< sc1 >}}{{< /another >}}`, []item{ 69 tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, 70 {tError, 0, "closing tag for shortcode 'another' does not match start tag"}}}, 71 {"close, but no open, more", `{{< sc1 >}}{{< /sc1 >}}{{< /another >}}`, []item{ 72 tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, 73 {tError, 0, "closing tag for shortcode 'another' does not match start tag"}}}, 74 {"close with extra keyword", `{{< sc1 >}}{{< /sc1 keyword>}}`, []item{ 75 tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, 76 {tError, 0, "unclosed shortcode"}}}, 77 {"Youtube id", `{{< sc1 -ziL-Q_456igdO-4 >}}`, []item{ 78 tstLeftNoMD, tstSC1, {tScParam, 0, "-ziL-Q_456igdO-4"}, tstRightNoMD, tstEOF}}, 79 {"non-alphanumerics param quoted", `{{< sc1 "-ziL-.%QigdO-4" >}}`, []item{ 80 tstLeftNoMD, tstSC1, {tScParam, 0, "-ziL-.%QigdO-4"}, tstRightNoMD, tstEOF}}, 81 82 {"two params", `{{< sc1 param1 param2 >}}`, []item{ 83 tstLeftNoMD, tstSC1, tstParam1, tstParam2, tstRightNoMD, tstEOF}}, 84 // issue #934 85 {"self-closing", `{{< sc1 />}}`, []item{ 86 tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF}}, 87 // Issue 2498 88 {"multiple self-closing", `{{< sc1 />}}{{< sc1 />}}`, []item{ 89 tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, 90 tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF}}, 91 {"self-closing with param", `{{< sc1 param1 />}}`, []item{ 92 tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF}}, 93 {"multiple self-closing with param", `{{< sc1 param1 />}}{{< sc1 param1 />}}`, []item{ 94 tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, 95 tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF}}, 96 {"multiple different self-closing with param", `{{< sc1 param1 />}}{{< sc2 param1 />}}`, []item{ 97 tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, 98 tstLeftNoMD, tstSC2, tstParam1, tstSCClose, tstRightNoMD, tstEOF}}, 99 {"nested simple", `{{< sc1 >}}{{< sc2 >}}{{< /sc1 >}}`, []item{ 100 tstLeftNoMD, tstSC1, tstRightNoMD, 101 tstLeftNoMD, tstSC2, tstRightNoMD, 102 tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF}}, 103 {"nested complex", `{{< sc1 >}}ab{{% sc2 param1 %}}cd{{< sc3 >}}ef{{< /sc3 >}}gh{{% /sc2 %}}ij{{< /sc1 >}}kl`, []item{ 104 tstLeftNoMD, tstSC1, tstRightNoMD, 105 {tText, 0, "ab"}, 106 tstLeftMD, tstSC2, tstParam1, tstRightMD, 107 {tText, 0, "cd"}, 108 tstLeftNoMD, tstSC3, tstRightNoMD, 109 {tText, 0, "ef"}, 110 tstLeftNoMD, tstSCClose, tstSC3, tstRightNoMD, 111 {tText, 0, "gh"}, 112 tstLeftMD, tstSCClose, tstSC2, tstRightMD, 113 {tText, 0, "ij"}, 114 tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, 115 {tText, 0, "kl"}, tstEOF, 116 }}, 117 118 {"two quoted params", `{{< sc1 "param nr. 1" "param nr. 2" >}}`, []item{ 119 tstLeftNoMD, tstSC1, {tScParam, 0, "param nr. 1"}, {tScParam, 0, "param nr. 2"}, tstRightNoMD, tstEOF}}, 120 {"two named params", `{{< sc1 param1="Hello World" param2="p2Val">}}`, []item{ 121 tstLeftNoMD, tstSC1, tstParam1, tstVal, tstParam2, {tScParamVal, 0, "p2Val"}, tstRightNoMD, tstEOF}}, 122 {"escaped quotes", `{{< sc1 param1=\"Hello World\" >}}`, []item{ 123 tstLeftNoMD, tstSC1, tstParam1, tstVal, tstRightNoMD, tstEOF}}, 124 {"escaped quotes, positional param", `{{< sc1 \"param1\" >}}`, []item{ 125 tstLeftNoMD, tstSC1, tstParam1, tstRightNoMD, tstEOF}}, 126 {"escaped quotes inside escaped quotes", `{{< sc1 param1=\"Hello \"escaped\" World\" >}}`, []item{ 127 tstLeftNoMD, tstSC1, tstParam1, 128 {tScParamVal, 0, `Hello `}, {tError, 0, `got positional parameter 'escaped'. Cannot mix named and positional parameters`}}}, 129 {"escaped quotes inside nonescaped quotes", 130 `{{< sc1 param1="Hello \"escaped\" World" >}}`, []item{ 131 tstLeftNoMD, tstSC1, tstParam1, {tScParamVal, 0, `Hello "escaped" World`}, tstRightNoMD, tstEOF}}, 132 {"escaped quotes inside nonescaped quotes in positional param", 133 `{{< sc1 "Hello \"escaped\" World" >}}`, []item{ 134 tstLeftNoMD, tstSC1, {tScParam, 0, `Hello "escaped" World`}, tstRightNoMD, tstEOF}}, 135 {"unterminated quote", `{{< sc1 param2="Hello World>}}`, []item{ 136 tstLeftNoMD, tstSC1, tstParam2, {tError, 0, "unterminated quoted string in shortcode parameter-argument: 'Hello World>}}'"}}}, 137 {"one named param, one not", `{{< sc1 param1="Hello World" p2 >}}`, []item{ 138 tstLeftNoMD, tstSC1, tstParam1, tstVal, 139 {tError, 0, "got positional parameter 'p2'. Cannot mix named and positional parameters"}}}, 140 {"one named param, one quoted positional param", `{{< sc1 param1="Hello World" "And Universe" >}}`, []item{ 141 tstLeftNoMD, tstSC1, tstParam1, tstVal, 142 {tError, 0, "got quoted positional parameter. Cannot mix named and positional parameters"}}}, 143 {"one quoted positional param, one named param", `{{< sc1 "param1" param2="And Universe" >}}`, []item{ 144 tstLeftNoMD, tstSC1, tstParam1, 145 {tError, 0, "got named parameter 'param2'. Cannot mix named and positional parameters"}}}, 146 {"ono positional param, one not", `{{< sc1 param1 param2="Hello World">}}`, []item{ 147 tstLeftNoMD, tstSC1, tstParam1, 148 {tError, 0, "got named parameter 'param2'. Cannot mix named and positional parameters"}}}, 149 {"commented out", `{{</* sc1 */>}}`, []item{ 150 {tText, 0, "{{<"}, {tText, 0, " sc1 "}, {tText, 0, ">}}"}, tstEOF}}, 151 {"commented out, with asterisk inside", `{{</* sc1 "**/*.pdf" */>}}`, []item{ 152 {tText, 0, "{{<"}, {tText, 0, " sc1 \"**/*.pdf\" "}, {tText, 0, ">}}"}, tstEOF}}, 153 {"commented out, missing close", `{{</* sc1 >}}`, []item{ 154 {tError, 0, "comment must be closed"}}}, 155 {"commented out, misplaced close", `{{</* sc1 >}}*/`, []item{ 156 {tError, 0, "comment must be closed"}}}, 157 } 158 159 func TestShortcodeLexer(t *testing.T) { 160 t.Parallel() 161 for i, test := range shortCodeLexerTests { 162 items := collect(&test) 163 if !equal(items, test.items) { 164 t.Errorf("[%d] %s: got\n\t%v\nexpected\n\t%v", i, test.name, items, test.items) 165 } 166 } 167 } 168 169 func BenchmarkShortcodeLexer(b *testing.B) { 170 b.ResetTimer() 171 for i := 0; i < b.N; i++ { 172 for _, test := range shortCodeLexerTests { 173 items := collect(&test) 174 if !equal(items, test.items) { 175 b.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 176 } 177 } 178 } 179 } 180 181 func collect(t *shortCodeLexerTest) (items []item) { 182 l := newShortcodeLexer(t.name, t.input, 0) 183 for { 184 item := l.nextItem() 185 items = append(items, item) 186 if item.typ == tEOF || item.typ == tError { 187 break 188 } 189 } 190 return 191 } 192 193 // no positional checking, for now ... 194 func equal(i1, i2 []item) bool { 195 if len(i1) != len(i2) { 196 return false 197 } 198 for k := range i1 { 199 if i1[k].typ != i2[k].typ { 200 return false 201 } 202 if i1[k].val != i2[k].val { 203 return false 204 } 205 } 206 return true 207 }