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