github.com/fighterlyt/hugo@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  }