1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "math"
10 "strings"
11 "testing"
12 )
13
14 func TestNextJsCtx(t *testing.T) {
15 tests := []struct {
16 jsCtx jsCtx
17 s string
18 }{
19
20 {jsCtxRegexp, ";"},
21
22
23
24
25
26
27 {jsCtxRegexp, "}"},
28
29
30 {jsCtxDivOp, ")"},
31 {jsCtxDivOp, "]"},
32
33
34 {jsCtxRegexp, "("},
35 {jsCtxRegexp, "["},
36 {jsCtxRegexp, "{"},
37
38
39 {jsCtxRegexp, "="},
40 {jsCtxRegexp, "+="},
41 {jsCtxRegexp, "*="},
42 {jsCtxRegexp, "*"},
43 {jsCtxRegexp, "!"},
44
45
46 {jsCtxRegexp, "+"},
47 {jsCtxRegexp, "-"},
48
49
50
51
52
53
54 {jsCtxDivOp, "--"},
55 {jsCtxDivOp, "++"},
56 {jsCtxDivOp, "x--"},
57
58
59 {jsCtxRegexp, "x---"},
60
61
62
63 {jsCtxRegexp, "return"},
64 {jsCtxRegexp, "return "},
65 {jsCtxRegexp, "return\t"},
66 {jsCtxRegexp, "return\n"},
67 {jsCtxRegexp, "return\u2028"},
68
69
70
71
72
73
74 {jsCtxDivOp, "x"},
75 {jsCtxDivOp, "x "},
76 {jsCtxDivOp, "x\t"},
77 {jsCtxDivOp, "x\n"},
78 {jsCtxDivOp, "x\u2028"},
79 {jsCtxDivOp, "preturn"},
80
81 {jsCtxDivOp, "0"},
82
83 {jsCtxDivOp, "0."},
84 }
85
86 for _, test := range tests {
87 if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
88 t.Errorf("want %s got %q", test.jsCtx, test.s)
89 }
90 if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
91 t.Errorf("want %s got %q", test.jsCtx, test.s)
92 }
93 }
94
95 if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp {
96 t.Error("Blank tokens")
97 }
98
99 if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp {
100 t.Error("Blank tokens")
101 }
102 }
103
104 func TestJSValEscaper(t *testing.T) {
105 tests := []struct {
106 x any
107 js string
108 }{
109 {int(42), " 42 "},
110 {uint(42), " 42 "},
111 {int16(42), " 42 "},
112 {uint16(42), " 42 "},
113 {int32(-42), " -42 "},
114 {uint32(42), " 42 "},
115 {int16(-42), " -42 "},
116 {uint16(42), " 42 "},
117 {int64(-42), " -42 "},
118 {uint64(42), " 42 "},
119 {uint64(1) << 53, " 9007199254740992 "},
120
121
122 {uint64(1)<<53 + 1, " 9007199254740993 "},
123 {float32(1.0), " 1 "},
124 {float32(-1.0), " -1 "},
125 {float32(0.5), " 0.5 "},
126 {float32(-0.5), " -0.5 "},
127 {float32(1.0) / float32(256), " 0.00390625 "},
128 {float32(0), " 0 "},
129 {math.Copysign(0, -1), " -0 "},
130 {float64(1.0), " 1 "},
131 {float64(-1.0), " -1 "},
132 {float64(0.5), " 0.5 "},
133 {float64(-0.5), " -0.5 "},
134 {float64(0), " 0 "},
135 {math.Copysign(0, -1), " -0 "},
136 {"", `""`},
137 {"foo", `"foo"`},
138
139 {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
140
141 {"\t\x0b", `"\t\u000b"`},
142 {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
143 {[]any{}, "[]"},
144 {[]any{42, "foo", nil}, `[42,"foo",null]`},
145 {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
146 {"<!--", `"\u003c!--"`},
147 {"-->", `"--\u003e"`},
148 {"<![CDATA[", `"\u003c![CDATA["`},
149 {"]]>", `"]]\u003e"`},
150 {"</script", `"\u003c/script"`},
151 {"\U0001D11E", "\"\U0001D11E\""},
152 {nil, " null "},
153 }
154
155 for _, test := range tests {
156 if js := jsValEscaper(test.x); js != test.js {
157 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
158 }
159
160
161 a := []any{test.x}
162 want := "[" + strings.TrimSpace(test.js) + "]"
163 if js := jsValEscaper(a); js != want {
164 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
165 }
166 }
167 }
168
169 func TestJSStrEscaper(t *testing.T) {
170 tests := []struct {
171 x any
172 esc string
173 }{
174 {"", ``},
175 {"foo", `foo`},
176 {"\u0000", `\u0000`},
177 {"\t", `\t`},
178 {"\n", `\n`},
179 {"\r", `\r`},
180 {"\u2028", `\u2028`},
181 {"\u2029", `\u2029`},
182 {"\\", `\\`},
183 {"\\n", `\\n`},
184 {"foo\r\nbar", `foo\r\nbar`},
185
186 {`"`, `\u0022`},
187 {`'`, `\u0027`},
188
189 {`&`, `\u0026amp;`},
190
191 {"</script>", `\u003c\/script\u003e`},
192 {"<![CDATA[", `\u003c![CDATA[`},
193 {"]]>", `]]\u003e`},
194
195
196
197
198
199
200
201
202
203
204 {"<!--", `\u003c!--`},
205 {"-->", `--\u003e`},
206
207 {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
208 `\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
209 },
210
211 {"foo\xA0bar", "foo\xA0bar"},
212
213 {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
214 }
215
216 for _, test := range tests {
217 esc := jsStrEscaper(test.x)
218 if esc != test.esc {
219 t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
220 }
221 }
222 }
223
224 func TestJSRegexpEscaper(t *testing.T) {
225 tests := []struct {
226 x any
227 esc string
228 }{
229 {"", `(?:)`},
230 {"foo", `foo`},
231 {"\u0000", `\u0000`},
232 {"\t", `\t`},
233 {"\n", `\n`},
234 {"\r", `\r`},
235 {"\u2028", `\u2028`},
236 {"\u2029", `\u2029`},
237 {"\\", `\\`},
238 {"\\n", `\\n`},
239 {"foo\r\nbar", `foo\r\nbar`},
240
241 {`"`, `\u0022`},
242 {`'`, `\u0027`},
243
244 {`&`, `\u0026amp;`},
245
246 {"</script>", `\u003c\/script\u003e`},
247 {"<![CDATA[", `\u003c!\[CDATA\[`},
248 {"]]>", `\]\]\u003e`},
249
250 {"<!--", `\u003c!\-\-`},
251 {"-->", `\-\-\u003e`},
252 {"*", `\*`},
253 {"+", `\u002b`},
254 {"?", `\?`},
255 {"[](){}", `\[\]\(\)\{\}`},
256 {"$foo|x.y", `\$foo\|x\.y`},
257 {"x^y", `x\^y`},
258 }
259
260 for _, test := range tests {
261 esc := jsRegexpEscaper(test.x)
262 if esc != test.esc {
263 t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
264 }
265 }
266 }
267
268 func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
269 input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
270 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
271 ` !"#$%&'()*+,-./` +
272 `0123456789:;<=>?` +
273 `@ABCDEFGHIJKLMNO` +
274 `PQRSTUVWXYZ[\]^_` +
275 "`abcdefghijklmno" +
276 "pqrstuvwxyz{|}~\x7f" +
277 "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
278
279 tests := []struct {
280 name string
281 escaper func(...any) string
282 escaped string
283 }{
284 {
285 "jsStrEscaper",
286 jsStrEscaper,
287 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
288 `\u0008\t\n\u000b\f\r\u000e\u000f` +
289 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
290 `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
291 ` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
292 `0123456789:;\u003c=\u003e?` +
293 `@ABCDEFGHIJKLMNO` +
294 `PQRSTUVWXYZ[\\]^_` +
295 "`abcdefghijklmno" +
296 "pqrstuvwxyz{|}~\u007f" +
297 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
298 },
299 {
300 "jsRegexpEscaper",
301 jsRegexpEscaper,
302 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
303 `\u0008\t\n\u000b\f\r\u000e\u000f` +
304 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
305 `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
306 ` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
307 `0123456789:;\u003c=\u003e\?` +
308 `@ABCDEFGHIJKLMNO` +
309 `PQRSTUVWXYZ\[\\\]\^_` +
310 "`abcdefghijklmno" +
311 `pqrstuvwxyz\{\|\}~` + "\u007f" +
312 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
313 },
314 }
315
316 for _, test := range tests {
317 if s := test.escaper(input); s != test.escaped {
318 t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
319 continue
320 }
321
322
323
324 var buf bytes.Buffer
325 for _, c := range input {
326 buf.WriteString(test.escaper(string(c)))
327 }
328
329 if s := buf.String(); s != test.escaped {
330 t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
331 continue
332 }
333 }
334 }
335
336 func TestIsJsMimeType(t *testing.T) {
337 tests := []struct {
338 in string
339 out bool
340 }{
341 {"application/javascript;version=1.8", true},
342 {"application/javascript;version=1.8;foo=bar", true},
343 {"application/javascript/version=1.8", false},
344 {"text/javascript", true},
345 {"application/json", true},
346 {"application/ld+json", true},
347 {"module", true},
348 }
349
350 for _, test := range tests {
351 if isJSType(test.in) != test.out {
352 t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
353 }
354 }
355 }
356
357 func BenchmarkJSValEscaperWithNum(b *testing.B) {
358 for i := 0; i < b.N; i++ {
359 jsValEscaper(3.141592654)
360 }
361 }
362
363 func BenchmarkJSValEscaperWithStr(b *testing.B) {
364 for i := 0; i < b.N; i++ {
365 jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
366 }
367 }
368
369 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
370 for i := 0; i < b.N; i++ {
371 jsValEscaper("The quick, brown fox jumps over the lazy dog")
372 }
373 }
374
375 func BenchmarkJSValEscaperWithObj(b *testing.B) {
376 o := struct {
377 S string
378 N int
379 }{
380 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
381 42,
382 }
383 for i := 0; i < b.N; i++ {
384 jsValEscaper(o)
385 }
386 }
387
388 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
389 o := struct {
390 S string
391 N int
392 }{
393 "The quick, brown fox jumps over the lazy dog",
394 42,
395 }
396 for i := 0; i < b.N; i++ {
397 jsValEscaper(o)
398 }
399 }
400
401 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
402 for i := 0; i < b.N; i++ {
403 jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
404 }
405 }
406
407 func BenchmarkJSStrEscaper(b *testing.B) {
408 for i := 0; i < b.N; i++ {
409 jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
410 }
411 }
412
413 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
414 for i := 0; i < b.N; i++ {
415 jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
416 }
417 }
418
419 func BenchmarkJSRegexpEscaper(b *testing.B) {
420 for i := 0; i < b.N; i++ {
421 jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
422 }
423 }
424
View as plain text