1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 "testing"
12 )
13
14 func TestTypedContent(t *testing.T) {
15 data := []any{
16 `<b> "foo%" O'Reilly &bar;`,
17 CSS(`a[href =~ "//example.com"]#foo`),
18 HTML(`Hello, <b>World</b> &tc!`),
19 HTMLAttr(` dir="ltr"`),
20 JS(`c && alert("Hello, World!");`),
21 JSStr(`Hello, World & O'Reilly\u0021`),
22 URL(`greeting=H%69,&addressee=(World)`),
23 Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
24 URL(`,foo/,`),
25 }
26
27
28
29 tests := []struct {
30
31 input string
32 want []string
33 }{
34 {
35 `<style>{{.}} { color: blue }</style>`,
36 []string{
37 `ZgotmplZ`,
38
39 `a[href =~ "//example.com"]#foo`,
40 `ZgotmplZ`,
41 `ZgotmplZ`,
42 `ZgotmplZ`,
43 `ZgotmplZ`,
44 `ZgotmplZ`,
45 `ZgotmplZ`,
46 `ZgotmplZ`,
47 },
48 },
49 {
50 `<div style="{{.}}">`,
51 []string{
52 `ZgotmplZ`,
53
54 `a[href =~ "//example.com"]#foo`,
55 `ZgotmplZ`,
56 `ZgotmplZ`,
57 `ZgotmplZ`,
58 `ZgotmplZ`,
59 `ZgotmplZ`,
60 `ZgotmplZ`,
61 `ZgotmplZ`,
62 },
63 },
64 {
65 `{{.}}`,
66 []string{
67 `<b> "foo%" O'Reilly &bar;`,
68 `a[href =~ "//example.com"]#foo`,
69
70 `Hello, <b>World</b> &tc!`,
71 ` dir="ltr"`,
72 `c && alert("Hello, World!");`,
73 `Hello, World & O'Reilly\u0021`,
74 `greeting=H%69,&addressee=(World)`,
75 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
76 `,foo/,`,
77 },
78 },
79 {
80 `<a{{.}}>`,
81 []string{
82 `ZgotmplZ`,
83 `ZgotmplZ`,
84 `ZgotmplZ`,
85
86 ` dir="ltr"`,
87 `ZgotmplZ`,
88 `ZgotmplZ`,
89 `ZgotmplZ`,
90 `ZgotmplZ`,
91 `ZgotmplZ`,
92 },
93 },
94 {
95 `<a title={{.}}>`,
96 []string{
97 `<b> "foo%" O'Reilly &bar;`,
98 `a[href =~ "//example.com"]#foo`,
99
100 `Hello, World &tc!`,
101 ` dir="ltr"`,
102 `c && alert("Hello, World!");`,
103 `Hello, World & O'Reilly\u0021`,
104 `greeting=H%69,&addressee=(World)`,
105 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
106 `,foo/,`,
107 },
108 },
109 {
110 `<a title='{{.}}'>`,
111 []string{
112 `<b> "foo%" O'Reilly &bar;`,
113 `a[href =~ "//example.com"]#foo`,
114
115 `Hello, World &tc!`,
116 ` dir="ltr"`,
117 `c && alert("Hello, World!");`,
118 `Hello, World & O'Reilly\u0021`,
119 `greeting=H%69,&addressee=(World)`,
120 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
121 `,foo/,`,
122 },
123 },
124 {
125 `<textarea>{{.}}</textarea>`,
126 []string{
127 `<b> "foo%" O'Reilly &bar;`,
128 `a[href =~ "//example.com"]#foo`,
129
130 `Hello, <b>World</b> &tc!`,
131 ` dir="ltr"`,
132 `c && alert("Hello, World!");`,
133 `Hello, World & O'Reilly\u0021`,
134 `greeting=H%69,&addressee=(World)`,
135 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
136 `,foo/,`,
137 },
138 },
139 {
140 `<script>alert({{.}})</script>`,
141 []string{
142 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
143 `"a[href =~ \"//example.com\"]#foo"`,
144 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
145 `" dir=\"ltr\""`,
146
147 `c && alert("Hello, World!");`,
148
149 `"Hello, World & O'Reilly\u0021"`,
150 `"greeting=H%69,\u0026addressee=(World)"`,
151 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
152 `",foo/,"`,
153 },
154 },
155 {
156 `<button onclick="alert({{.}})">`,
157 []string{
158 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
159 `"a[href =~ \"//example.com\"]#foo"`,
160 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
161 `" dir=\"ltr\""`,
162
163 `c && alert("Hello, World!");`,
164
165 `"Hello, World & O'Reilly\u0021"`,
166 `"greeting=H%69,\u0026addressee=(World)"`,
167 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
168 `",foo/,"`,
169 },
170 },
171 {
172 `<script>alert("{{.}}")</script>`,
173 []string{
174 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
175 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
176 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
177 ` dir=\u0022ltr\u0022`,
178 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
179
180 `Hello, World \u0026 O\u0027Reilly\u0021`,
181 `greeting=H%69,\u0026addressee=(World)`,
182 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
183 `,foo\/,`,
184 },
185 },
186 {
187 `<script type="text/javascript">alert("{{.}}")</script>`,
188 []string{
189 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
190 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
191 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
192 ` dir=\u0022ltr\u0022`,
193 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
194
195 `Hello, World \u0026 O\u0027Reilly\u0021`,
196 `greeting=H%69,\u0026addressee=(World)`,
197 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
198 `,foo\/,`,
199 },
200 },
201 {
202 `<script type="text/javascript">alert({{.}})</script>`,
203 []string{
204 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
205 `"a[href =~ \"//example.com\"]#foo"`,
206 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
207 `" dir=\"ltr\""`,
208
209 `c && alert("Hello, World!");`,
210
211 `"Hello, World & O'Reilly\u0021"`,
212 `"greeting=H%69,\u0026addressee=(World)"`,
213 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
214 `",foo/,"`,
215 },
216 },
217 {
218
219 `<script type="text/template">{{.}}</script>`,
220 []string{
221 `<b> "foo%" O'Reilly &bar;`,
222 `a[href =~ "//example.com"]#foo`,
223
224 `Hello, <b>World</b> &tc!`,
225 ` dir="ltr"`,
226 `c && alert("Hello, World!");`,
227 `Hello, World & O'Reilly\u0021`,
228 `greeting=H%69,&addressee=(World)`,
229 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
230 `,foo/,`,
231 },
232 },
233 {
234 `<button onclick='alert("{{.}}")'>`,
235 []string{
236 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
237 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
238 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
239 ` dir=\u0022ltr\u0022`,
240 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
241
242 `Hello, World \u0026 O\u0027Reilly\u0021`,
243 `greeting=H%69,\u0026addressee=(World)`,
244 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
245 `,foo\/,`,
246 },
247 },
248 {
249 `<a href="?q={{.}}">`,
250 []string{
251 `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
252 `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
253 `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
254 `%20dir%3d%22ltr%22`,
255 `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
256 `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
257
258 `greeting=H%69,&addressee=%28World%29`,
259 `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
260 `,foo/,`,
261 },
262 },
263 {
264 `<style>body { background: url('?img={{.}}') }</style>`,
265 []string{
266 `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
267 `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
268 `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
269 `%20dir%3d%22ltr%22`,
270 `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
271 `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
272
273 `greeting=H%69,&addressee=%28World%29`,
274 `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
275 `,foo/,`,
276 },
277 },
278 {
279 `<img srcset="{{.}}">`,
280 []string{
281 `#ZgotmplZ`,
282 `#ZgotmplZ`,
283
284 `Hello,#ZgotmplZ`,
285
286 ` dir=%22ltr%22`,
287
288 `#ZgotmplZ, World!%22%29;`,
289 `Hello,#ZgotmplZ`,
290 `greeting=H%69%2c&addressee=%28World%29`,
291
292 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
293 `%2cfoo/%2c`,
294 },
295 },
296 {
297 `<img srcset={{.}}>`,
298 []string{
299 `#ZgotmplZ`,
300 `#ZgotmplZ`,
301 `Hello,#ZgotmplZ`,
302
303 ` dir=%22ltr%22`,
304 `#ZgotmplZ, World!%22%29;`,
305 `Hello,#ZgotmplZ`,
306 `greeting=H%69%2c&addressee=%28World%29`,
307 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
308
309 `%2cfoo/%2c`,
310 },
311 },
312 {
313 `<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`,
314 []string{
315 `#ZgotmplZ`,
316 `#ZgotmplZ`,
317 `Hello,#ZgotmplZ`,
318 ` dir=%22ltr%22`,
319 `#ZgotmplZ, World!%22%29;`,
320 `Hello,#ZgotmplZ`,
321 `greeting=H%69%2c&addressee=%28World%29`,
322 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
323 `%2cfoo/%2c`,
324 },
325 },
326 {
327 `<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`,
328 []string{
329 `#ZgotmplZ`,
330 `#ZgotmplZ`,
331 `Hello,#ZgotmplZ`,
332 ` dir=%22ltr%22`,
333 `#ZgotmplZ, World!%22%29;`,
334 `Hello,#ZgotmplZ`,
335 `greeting=H%69%2c&addressee=%28World%29`,
336 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
337 `%2cfoo/%2c`,
338 },
339 },
340 {
341 `<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`,
342 []string{
343 `#ZgotmplZ`,
344 `#ZgotmplZ`,
345 `Hello,#ZgotmplZ`,
346 ` dir=%22ltr%22`,
347 `#ZgotmplZ, World!%22%29;`,
348 `Hello,#ZgotmplZ`,
349 `greeting=H%69%2c&addressee=%28World%29`,
350 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
351 `%2cfoo/%2c`,
352 },
353 },
354 {
355 `<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`,
356 []string{
357 `#ZgotmplZ`,
358 `#ZgotmplZ`,
359 `Hello,#ZgotmplZ`,
360 ` dir=%22ltr%22`,
361 `#ZgotmplZ, World!%22%29;`,
362 `Hello,#ZgotmplZ`,
363 `greeting=H%69%2c&addressee=%28World%29`,
364 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
365 `%2cfoo/%2c`,
366 },
367 },
368 {
369 `<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`,
370 []string{
371 `#ZgotmplZ`,
372 `#ZgotmplZ`,
373 `Hello,#ZgotmplZ`,
374 ` dir=%22ltr%22`,
375 `#ZgotmplZ, World!%22%29;`,
376 `Hello,#ZgotmplZ`,
377 `greeting=H%69%2c&addressee=%28World%29`,
378 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
379 `%2cfoo/%2c`,
380 },
381 },
382 }
383
384 for _, test := range tests {
385 tmpl := Must(New("x").Parse(test.input))
386 pre := strings.Index(test.input, "{{.}}")
387 post := len(test.input) - (pre + 5)
388 var b strings.Builder
389 for i, x := range data {
390 b.Reset()
391 if err := tmpl.Execute(&b, x); err != nil {
392 t.Errorf("%q with %v: %s", test.input, x, err)
393 continue
394 }
395 if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
396 t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
397 continue
398 }
399 }
400 }
401 }
402
403
404 type myStringer struct {
405 v int
406 }
407
408 func (s *myStringer) String() string {
409 return fmt.Sprintf("string=%d", s.v)
410 }
411
412 type errorer struct {
413 v int
414 }
415
416 func (s *errorer) Error() string {
417 return fmt.Sprintf("error=%d", s.v)
418 }
419
420 func TestStringer(t *testing.T) {
421 s := &myStringer{3}
422 b := new(strings.Builder)
423 tmpl := Must(New("x").Parse("{{.}}"))
424 if err := tmpl.Execute(b, s); err != nil {
425 t.Fatal(err)
426 }
427 var expect = "string=3"
428 if b.String() != expect {
429 t.Errorf("expected %q got %q", expect, b.String())
430 }
431 e := &errorer{7}
432 b.Reset()
433 if err := tmpl.Execute(b, e); err != nil {
434 t.Fatal(err)
435 }
436 expect = "error=7"
437 if b.String() != expect {
438 t.Errorf("expected %q got %q", expect, b.String())
439 }
440 }
441
442
443 func TestEscapingNilNonemptyInterfaces(t *testing.T) {
444 tmpl := Must(New("x").Parse("{{.E}}"))
445
446 got := new(bytes.Buffer)
447 testData := struct{ E error }{}
448 tmpl.Execute(got, testData)
449
450
451 want := new(bytes.Buffer)
452 data := struct{ E any }{}
453 tmpl.Execute(want, data)
454
455 if !bytes.Equal(want.Bytes(), got.Bytes()) {
456 t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
457 }
458 }
459
View as plain text