hugo

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

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

js_test.go (12113B)

    1 // Copyright 2011 The Go Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style
    3 // license that can be found in the LICENSE file.
    4 
    5 //go:build go1.13 && !windows
    6 // +build go1.13,!windows
    7 
    8 package template
    9 
   10 import (
   11 	"bytes"
   12 	"math"
   13 	"strings"
   14 	"testing"
   15 )
   16 
   17 func TestNextJsCtx(t *testing.T) {
   18 	tests := []struct {
   19 		jsCtx jsCtx
   20 		s     string
   21 	}{
   22 		// Statement terminators precede regexps.
   23 		{jsCtxRegexp, ";"},
   24 		// This is not airtight.
   25 		//     ({ valueOf: function () { return 1 } } / 2)
   26 		// is valid JavaScript but in practice, devs do not do this.
   27 		// A block followed by a statement starting with a RegExp is
   28 		// much more common:
   29 		//     while (x) {...} /foo/.test(x) || panic()
   30 		{jsCtxRegexp, "}"},
   31 		// But member, call, grouping, and array expression terminators
   32 		// precede div ops.
   33 		{jsCtxDivOp, ")"},
   34 		{jsCtxDivOp, "]"},
   35 		// At the start of a primary expression, array, or expression
   36 		// statement, expect a regexp.
   37 		{jsCtxRegexp, "("},
   38 		{jsCtxRegexp, "["},
   39 		{jsCtxRegexp, "{"},
   40 		// Assignment operators precede regexps as do all exclusively
   41 		// prefix and binary operators.
   42 		{jsCtxRegexp, "="},
   43 		{jsCtxRegexp, "+="},
   44 		{jsCtxRegexp, "*="},
   45 		{jsCtxRegexp, "*"},
   46 		{jsCtxRegexp, "!"},
   47 		// Whether the + or - is infix or prefix, it cannot precede a
   48 		// div op.
   49 		{jsCtxRegexp, "+"},
   50 		{jsCtxRegexp, "-"},
   51 		// An incr/decr op precedes a div operator.
   52 		// This is not airtight. In (g = ++/h/i) a regexp follows a
   53 		// pre-increment operator, but in practice devs do not try to
   54 		// increment or decrement regular expressions.
   55 		// (g++/h/i) where ++ is a postfix operator on g is much more
   56 		// common.
   57 		{jsCtxDivOp, "--"},
   58 		{jsCtxDivOp, "++"},
   59 		{jsCtxDivOp, "x--"},
   60 		// When we have many dashes or pluses, then they are grouped
   61 		// left to right.
   62 		{jsCtxRegexp, "x---"}, // A postfix -- then a -.
   63 		// return followed by a slash returns the regexp literal or the
   64 		// slash starts a regexp literal in an expression statement that
   65 		// is dead code.
   66 		{jsCtxRegexp, "return"},
   67 		{jsCtxRegexp, "return "},
   68 		{jsCtxRegexp, "return\t"},
   69 		{jsCtxRegexp, "return\n"},
   70 		{jsCtxRegexp, "return\u2028"},
   71 		// Identifiers can be divided and cannot validly be preceded by
   72 		// a regular expressions. Semicolon insertion cannot happen
   73 		// between an identifier and a regular expression on a new line
   74 		// because the one token lookahead for semicolon insertion has
   75 		// to conclude that it could be a div binary op and treat it as
   76 		// such.
   77 		{jsCtxDivOp, "x"},
   78 		{jsCtxDivOp, "x "},
   79 		{jsCtxDivOp, "x\t"},
   80 		{jsCtxDivOp, "x\n"},
   81 		{jsCtxDivOp, "x\u2028"},
   82 		{jsCtxDivOp, "preturn"},
   83 		// Numbers precede div ops.
   84 		{jsCtxDivOp, "0"},
   85 		// Dots that are part of a number are div preceders.
   86 		{jsCtxDivOp, "0."},
   87 	}
   88 
   89 	for _, test := range tests {
   90 		if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
   91 			t.Errorf("want %s got %q", test.jsCtx, test.s)
   92 		}
   93 		if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
   94 			t.Errorf("want %s got %q", test.jsCtx, test.s)
   95 		}
   96 	}
   97 
   98 	if nextJSCtx([]byte("   "), jsCtxRegexp) != jsCtxRegexp {
   99 		t.Error("Blank tokens")
  100 	}
  101 
  102 	if nextJSCtx([]byte("   "), jsCtxDivOp) != jsCtxDivOp {
  103 		t.Error("Blank tokens")
  104 	}
  105 }
  106 
  107 func TestJSValEscaper(t *testing.T) {
  108 	tests := []struct {
  109 		x  any
  110 		js string
  111 	}{
  112 		{int(42), " 42 "},
  113 		{uint(42), " 42 "},
  114 		{int16(42), " 42 "},
  115 		{uint16(42), " 42 "},
  116 		{int32(-42), " -42 "},
  117 		{uint32(42), " 42 "},
  118 		{int16(-42), " -42 "},
  119 		{uint16(42), " 42 "},
  120 		{int64(-42), " -42 "},
  121 		{uint64(42), " 42 "},
  122 		{uint64(1) << 53, " 9007199254740992 "},
  123 		// ulp(1 << 53) > 1 so this loses precision in JS
  124 		// but it is still a representable integer literal.
  125 		{uint64(1)<<53 + 1, " 9007199254740993 "},
  126 		{float32(1.0), " 1 "},
  127 		{float32(-1.0), " -1 "},
  128 		{float32(0.5), " 0.5 "},
  129 		{float32(-0.5), " -0.5 "},
  130 		{float32(1.0) / float32(256), " 0.00390625 "},
  131 		{float32(0), " 0 "},
  132 		{math.Copysign(0, -1), " -0 "},
  133 		{float64(1.0), " 1 "},
  134 		{float64(-1.0), " -1 "},
  135 		{float64(0.5), " 0.5 "},
  136 		{float64(-0.5), " -0.5 "},
  137 		{float64(0), " 0 "},
  138 		{math.Copysign(0, -1), " -0 "},
  139 		{"", `""`},
  140 		{"foo", `"foo"`},
  141 		// Newlines.
  142 		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
  143 		// "\v" == "v" on IE 6 so use "\u000b" instead.
  144 		{"\t\x0b", `"\t\u000b"`},
  145 		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
  146 		{[]any{}, "[]"},
  147 		{[]any{42, "foo", nil}, `[42,"foo",null]`},
  148 		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
  149 		{"<!--", `"\u003c!--"`},
  150 		{"-->", `"--\u003e"`},
  151 		{"<![CDATA[", `"\u003c![CDATA["`},
  152 		{"]]>", `"]]\u003e"`},
  153 		{"</script", `"\u003c/script"`},
  154 		{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
  155 		{nil, " null "},
  156 	}
  157 
  158 	for _, test := range tests {
  159 		if js := jsValEscaper(test.x); js != test.js {
  160 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
  161 		}
  162 		// Make sure that escaping corner cases are not broken
  163 		// by nesting.
  164 		a := []any{test.x}
  165 		want := "[" + strings.TrimSpace(test.js) + "]"
  166 		if js := jsValEscaper(a); js != want {
  167 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
  168 		}
  169 	}
  170 }
  171 
  172 func TestJSStrEscaper(t *testing.T) {
  173 	tests := []struct {
  174 		x   any
  175 		esc string
  176 	}{
  177 		{"", ``},
  178 		{"foo", `foo`},
  179 		{"\u0000", `\u0000`},
  180 		{"\t", `\t`},
  181 		{"\n", `\n`},
  182 		{"\r", `\r`},
  183 		{"\u2028", `\u2028`},
  184 		{"\u2029", `\u2029`},
  185 		{"\\", `\\`},
  186 		{"\\n", `\\n`},
  187 		{"foo\r\nbar", `foo\r\nbar`},
  188 		// Preserve attribute boundaries.
  189 		{`"`, `\u0022`},
  190 		{`'`, `\u0027`},
  191 		// Allow embedding in HTML without further escaping.
  192 		{`&amp;`, `\u0026amp;`},
  193 		// Prevent breaking out of text node and element boundaries.
  194 		{"</script>", `\u003c\/script\u003e`},
  195 		{"<![CDATA[", `\u003c![CDATA[`},
  196 		{"]]>", `]]\u003e`},
  197 		// https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
  198 		//   "The text in style, script, title, and textarea elements
  199 		//   must not have an escaping text span start that is not
  200 		//   followed by an escaping text span end."
  201 		// Furthermore, spoofing an escaping text span end could lead
  202 		// to different interpretation of a </script> sequence otherwise
  203 		// masked by the escaping text span, and spoofing a start could
  204 		// allow regular text content to be interpreted as script
  205 		// allowing script execution via a combination of a JS string
  206 		// injection followed by an HTML text injection.
  207 		{"<!--", `\u003c!--`},
  208 		{"-->", `--\u003e`},
  209 		// From https://code.google.com/p/doctype/wiki/ArticleUtf7
  210 		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
  211 			`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
  212 		},
  213 		// Invalid UTF-8 sequence
  214 		{"foo\xA0bar", "foo\xA0bar"},
  215 		// Invalid unicode scalar value.
  216 		{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
  217 	}
  218 
  219 	for _, test := range tests {
  220 		esc := jsStrEscaper(test.x)
  221 		if esc != test.esc {
  222 			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
  223 		}
  224 	}
  225 }
  226 
  227 func TestJSRegexpEscaper(t *testing.T) {
  228 	tests := []struct {
  229 		x   any
  230 		esc string
  231 	}{
  232 		{"", `(?:)`},
  233 		{"foo", `foo`},
  234 		{"\u0000", `\u0000`},
  235 		{"\t", `\t`},
  236 		{"\n", `\n`},
  237 		{"\r", `\r`},
  238 		{"\u2028", `\u2028`},
  239 		{"\u2029", `\u2029`},
  240 		{"\\", `\\`},
  241 		{"\\n", `\\n`},
  242 		{"foo\r\nbar", `foo\r\nbar`},
  243 		// Preserve attribute boundaries.
  244 		{`"`, `\u0022`},
  245 		{`'`, `\u0027`},
  246 		// Allow embedding in HTML without further escaping.
  247 		{`&amp;`, `\u0026amp;`},
  248 		// Prevent breaking out of text node and element boundaries.
  249 		{"</script>", `\u003c\/script\u003e`},
  250 		{"<![CDATA[", `\u003c!\[CDATA\[`},
  251 		{"]]>", `\]\]\u003e`},
  252 		// Escaping text spans.
  253 		{"<!--", `\u003c!\-\-`},
  254 		{"-->", `\-\-\u003e`},
  255 		{"*", `\*`},
  256 		{"+", `\u002b`},
  257 		{"?", `\?`},
  258 		{"[](){}", `\[\]\(\)\{\}`},
  259 		{"$foo|x.y", `\$foo\|x\.y`},
  260 		{"x^y", `x\^y`},
  261 	}
  262 
  263 	for _, test := range tests {
  264 		esc := jsRegexpEscaper(test.x)
  265 		if esc != test.esc {
  266 			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
  267 		}
  268 	}
  269 }
  270 
  271 func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
  272 	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
  273 		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
  274 		` !"#$%&'()*+,-./` +
  275 		`0123456789:;<=>?` +
  276 		`@ABCDEFGHIJKLMNO` +
  277 		`PQRSTUVWXYZ[\]^_` +
  278 		"`abcdefghijklmno" +
  279 		"pqrstuvwxyz{|}~\x7f" +
  280 		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
  281 
  282 	tests := []struct {
  283 		name    string
  284 		escaper func(...any) string
  285 		escaped string
  286 	}{
  287 		{
  288 			"jsStrEscaper",
  289 			jsStrEscaper,
  290 			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
  291 				`\u0008\t\n\u000b\f\r\u000e\u000f` +
  292 				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
  293 				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
  294 				` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
  295 				`0123456789:;\u003c=\u003e?` +
  296 				`@ABCDEFGHIJKLMNO` +
  297 				`PQRSTUVWXYZ[\\]^_` +
  298 				"`abcdefghijklmno" +
  299 				"pqrstuvwxyz{|}~\u007f" +
  300 				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
  301 		},
  302 		{
  303 			"jsRegexpEscaper",
  304 			jsRegexpEscaper,
  305 			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
  306 				`\u0008\t\n\u000b\f\r\u000e\u000f` +
  307 				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
  308 				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
  309 				` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
  310 				`0123456789:;\u003c=\u003e\?` +
  311 				`@ABCDEFGHIJKLMNO` +
  312 				`PQRSTUVWXYZ\[\\\]\^_` +
  313 				"`abcdefghijklmno" +
  314 				`pqrstuvwxyz\{\|\}~` + "\u007f" +
  315 				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
  316 		},
  317 	}
  318 
  319 	for _, test := range tests {
  320 		if s := test.escaper(input); s != test.escaped {
  321 			t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
  322 			continue
  323 		}
  324 
  325 		// Escape it rune by rune to make sure that any
  326 		// fast-path checking does not break escaping.
  327 		var buf bytes.Buffer
  328 		for _, c := range input {
  329 			buf.WriteString(test.escaper(string(c)))
  330 		}
  331 
  332 		if s := buf.String(); s != test.escaped {
  333 			t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
  334 			continue
  335 		}
  336 	}
  337 }
  338 
  339 func TestIsJsMimeType(t *testing.T) {
  340 	tests := []struct {
  341 		in  string
  342 		out bool
  343 	}{
  344 		{"application/javascript;version=1.8", true},
  345 		{"application/javascript;version=1.8;foo=bar", true},
  346 		{"application/javascript/version=1.8", false},
  347 		{"text/javascript", true},
  348 		{"application/json", true},
  349 		{"application/ld+json", true},
  350 		{"module", true},
  351 	}
  352 
  353 	for _, test := range tests {
  354 		if isJSType(test.in) != test.out {
  355 			t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
  356 		}
  357 	}
  358 }
  359 
  360 func BenchmarkJSValEscaperWithNum(b *testing.B) {
  361 	for i := 0; i < b.N; i++ {
  362 		jsValEscaper(3.141592654)
  363 	}
  364 }
  365 
  366 func BenchmarkJSValEscaperWithStr(b *testing.B) {
  367 	for i := 0; i < b.N; i++ {
  368 		jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  369 	}
  370 }
  371 
  372 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
  373 	for i := 0; i < b.N; i++ {
  374 		jsValEscaper("The quick, brown fox jumps over the lazy dog")
  375 	}
  376 }
  377 
  378 func BenchmarkJSValEscaperWithObj(b *testing.B) {
  379 	o := struct {
  380 		S string
  381 		N int
  382 	}{
  383 		"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
  384 		42,
  385 	}
  386 	for i := 0; i < b.N; i++ {
  387 		jsValEscaper(o)
  388 	}
  389 }
  390 
  391 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
  392 	o := struct {
  393 		S string
  394 		N int
  395 	}{
  396 		"The quick, brown fox jumps over the lazy dog",
  397 		42,
  398 	}
  399 	for i := 0; i < b.N; i++ {
  400 		jsValEscaper(o)
  401 	}
  402 }
  403 
  404 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
  405 	for i := 0; i < b.N; i++ {
  406 		jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
  407 	}
  408 }
  409 
  410 func BenchmarkJSStrEscaper(b *testing.B) {
  411 	for i := 0; i < b.N; i++ {
  412 		jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  413 	}
  414 }
  415 
  416 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
  417 	for i := 0; i < b.N; i++ {
  418 		jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
  419 	}
  420 }
  421 
  422 func BenchmarkJSRegexpEscaper(b *testing.B) {
  423 	for i := 0; i < b.N; i++ {
  424 		jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  425 	}
  426 }