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