transform_integration_test.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
transform_integration_test.go (15369B)
---
1 // Copyright 2024 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 transform_test
15
16 import (
17 "fmt"
18 "strings"
19 "testing"
20
21 qt "github.com/frankban/quicktest"
22 "github.com/gohugoio/hugo/hugolib"
23 )
24
25 // Issue #11698
26 func TestMarkdownifyIssue11698(t *testing.T) {
27 t.Parallel()
28
29 files := `
30 -- config.toml --
31 disableKinds = ['home','section','rss','sitemap','taxonomy','term']
32 [markup.goldmark.parser.attribute]
33 title = true
34 block = true
35 -- layouts/_default/single.html --
36 _{{ markdownify .RawContent }}_
37 -- content/p1.md --
38 ---
39 title: p1
40 ---
41 foo bar
42 -- content/p2.md --
43 ---
44 title: p2
45 ---
46 foo
47
48 **bar**
49 -- content/p3.md --
50 ---
51 title: p3
52 ---
53 ## foo
54
55 bar
56 -- content/p4.md --
57 ---
58 title: p4
59 ---
60 foo
61 {#bar}
62 `
63
64 b := hugolib.Test(t, files)
65
66 b.AssertFileContent("public/p1/index.html", "_foo bar_")
67 b.AssertFileContent("public/p2/index.html", "_<p>foo</p>\n<p><strong>bar</strong></p>\n_")
68 b.AssertFileContent("public/p3/index.html", "_<h2 id=\"foo\">foo</h2>\n<p>bar</p>\n_")
69 b.AssertFileContent("public/p4/index.html", "_<p id=\"bar\">foo</p>\n_")
70 }
71
72 func TestXMLEscape(t *testing.T) {
73 t.Parallel()
74
75 files := `
76 -- config.toml --
77 disableKinds = ['section','sitemap','taxonomy','term']
78 -- content/p1.md --
79 ---
80 title: p1
81 ---
82 a **b** ` + "\v" + ` c
83 <!--more-->
84 `
85 b := hugolib.Test(t, files)
86
87 b.AssertFileContent("public/index.xml", `
88 <description><p>a <strong>b</strong> c</p></description>
89 `)
90 }
91
92 // Issue #9642
93 func TestHighlightError(t *testing.T) {
94 t.Parallel()
95
96 files := `
97 -- hugo.toml --
98 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
99 -- layouts/index.html --
100 {{ highlight "a" "b" 0 }}
101 `
102 b := hugolib.NewIntegrationTestBuilder(
103 hugolib.IntegrationTestConfig{
104 T: t,
105 TxtarString: files,
106 },
107 )
108
109 _, err := b.BuildE()
110 b.Assert(err.Error(), qt.Contains, "error calling highlight: invalid Highlight option: 0")
111 }
112
113 // Issue #11884
114 func TestUnmarshalCSVLazyDecoding(t *testing.T) {
115 t.Parallel()
116
117 files := `
118 -- hugo.toml --
119 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
120 -- assets/pets.csv --
121 name,description,age
122 Spot,a nice dog,3
123 Rover,"a big dog",5
124 Felix,a "malicious" cat,7
125 Bella,"an "evil" cat",9
126 Scar,"a "dead cat",11
127 -- layouts/index.html --
128 {{ $opts := dict "lazyQuotes" true }}
129 {{ $data := resources.Get "pets.csv" | transform.Unmarshal $opts }}
130 {{ printf "%v" $data | safeHTML }}
131 `
132 b := hugolib.Test(t, files)
133
134 b.AssertFileContent("public/index.html", `
135 [[name description age] [Spot a nice dog 3] [Rover a big dog 5] [Felix a "malicious" cat 7] [Bella an "evil" cat 9] [Scar a "dead cat 11]]
136 `)
137 }
138
139 func TestToMath(t *testing.T) {
140 files := `
141 -- hugo.toml --
142 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
143 -- layouts/index.html --
144 {{ transform.ToMath "c = \\pm\\sqrt{a^2 + b^2}" }}
145 `
146 b := hugolib.Test(t, files)
147
148 b.AssertFileContent("public/index.html", `
149 <span class="katex"><math
150 `)
151 }
152
153 func TestToMathError(t *testing.T) {
154 t.Run("Default", func(t *testing.T) {
155 files := `
156 -- hugo.toml --
157 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
158 -- layouts/index.html --
159 {{ transform.ToMath "c = \\foo{a^2 + b^2}" }}
160 `
161 b, err := hugolib.TestE(t, files, hugolib.TestOptWarn())
162
163 b.Assert(err, qt.IsNotNil)
164 b.Assert(err.Error(), qt.Contains, "KaTeX parse error: Undefined control sequence: \\foo")
165 })
166
167 t.Run("Disable ThrowOnError", func(t *testing.T) {
168 files := `
169 -- hugo.toml --
170 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
171 -- layouts/index.html --
172 {{ $opts := dict "throwOnError" false }}
173 {{ transform.ToMath "c = \\foo{a^2 + b^2}" $opts }}
174 `
175 b, err := hugolib.TestE(t, files, hugolib.TestOptWarn())
176
177 b.Assert(err, qt.IsNil)
178 b.AssertFileContent("public/index.html", `#cc0000`) // Error color
179 })
180
181 t.Run("Handle in template", func(t *testing.T) {
182 files := `
183 -- hugo.toml --
184 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
185 -- layouts/index.html --
186 {{ with try (transform.ToMath "c = \\foo{a^2 + b^2}") }}
187 {{ with .Err }}
188 {{ warnf "error: %s" . }}
189 {{ else }}
190 {{ .Value }}
191 {{ end }}
192 {{ end }}
193 `
194 b, err := hugolib.TestE(t, files, hugolib.TestOptWarn())
195
196 b.Assert(err, qt.IsNil)
197 b.AssertLogContains("WARN error: template: index.html:1:22: executing \"index.html\" at <transform.ToMath>: error calling ToMath: KaTeX parse error: Undefined control sequence: \\foo at position 5: c = \\̲f̲o̲o̲{a^2 + b^2}")
198 })
199
200 // See issue 13239.
201 t.Run("Handle in template, old Err construct", func(t *testing.T) {
202 files := `
203 -- hugo.toml --
204 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
205 -- layouts/index.html --
206 {{ with transform.ToMath "c = \\pm\\sqrt{a^2 + b^2}" }}
207 {{ with .Err }}
208 {{ warnf "error: %s" . }}
209 {{ else }}
210 {{ . }}
211 {{ end }}
212 {{ end }}
213 `
214 b, err := hugolib.TestE(t, files, hugolib.TestOptWarn())
215
216 b.Assert(err, qt.IsNotNil)
217 b.Assert(err.Error(), qt.Contains, "the return type of transform.ToMath was changed in Hugo v0.141.0 and the error handling replaced with a new try keyword, see https://gohugo.io/functions/go-template/try/")
218 })
219 }
220
221 func TestToMathBigAndManyExpressions(t *testing.T) {
222 filesTemplate := `
223 -- hugo.toml --
224 disableKinds = ['rss','section','sitemap','taxonomy','term']
225 [markup.goldmark.extensions.passthrough]
226 enable = true
227 [markup.goldmark.extensions.passthrough.delimiters]
228 block = [['\[', '\]'], ['$$', '$$']]
229 inline = [['\(', '\)'], ['$', '$']]
230 -- content/p1.md --
231 P1_CONTENT
232 -- layouts/index.html --
233 Home.
234 -- layouts/_default/single.html --
235 Content: {{ .Content }}|
236 -- layouts/_default/_markup/render-passthrough.html --
237 {{ $opts := dict "throwOnError" false "displayMode" true }}
238 {{ transform.ToMath .Inner $opts }}
239 `
240
241 t.Run("Very large file with many complex KaTeX expressions", func(t *testing.T) {
242 files := strings.ReplaceAll(filesTemplate, "P1_CONTENT", "sourcefilename: testdata/large-katex.md")
243 b := hugolib.Test(t, files)
244 b.AssertFileContent("public/p1/index.html", `
245 <span class="katex"><math
246 `)
247 })
248
249 t.Run("Large and complex expression", func(t *testing.T) {
250 // This is pulled from the file above, which times out for some reason.
251 largeAndComplexeExpressions := `\begin{align*} \frac{\pi^2}{6}&=\frac{4}{3}\frac{(\arcsin 1)^2}{2}\\ &=\frac{4}{3}\int_0^1\frac{\arcsin x}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}\int_0^1\frac{x+\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!}\frac{x^{2n+1}}{2n+1}}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}\int_0^1\frac{x}{\sqrt{1-x^2}}\,dx +\frac{4}{3}\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!(2n+1)}\int_0^1x^{2n}\frac{x}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}+\frac{4}{3}\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!(2n+1)}\left[\frac{(2n)!!}{(2n+1)!!}\right]\\ &=\frac{4}{3}\sum_{n=0}^{\infty}\frac{1}{(2n+1)^2}\\ &=\frac{4}{3}\left(\sum_{n=1}^{\infty}\frac{1}{n^2}-\frac{1}{4}\sum_{n=1}^{\infty}\frac{1}{n^2}\right)\\ &=\sum_{n=1}^{\infty}\frac{1}{n^2} \end{align*}`
252 files := strings.ReplaceAll(filesTemplate, "P1_CONTENT", fmt.Sprintf(`---
253 title: p1
254 ---
255
256 $$%s$$
257 `, largeAndComplexeExpressions))
258
259 b := hugolib.Test(t, files)
260 b.AssertFileContent("public/p1/index.html", `
261 <span class="katex"><math
262 `)
263 })
264 }
265
266 // Issue #13406.
267 func TestToMathRenderHookPosition(t *testing.T) {
268 filesTemplate := `
269 -- hugo.toml --
270 disableKinds = ['rss','section','sitemap','taxonomy','term']
271 [markup.goldmark.extensions.passthrough]
272 enable = true
273 [markup.goldmark.extensions.passthrough.delimiters]
274 block = [['\[', '\]'], ['$$', '$$']]
275 inline = [['\(', '\)'], ['$', '$']]
276 -- content/p1.md --
277 ---
278 title: p1
279 ---
280
281 Block:
282
283 $$1+2$$
284
285 Some inline $1+3$ math.
286
287 -- layouts/index.html --
288 Home.
289 -- layouts/_default/single.html --
290 Content: {{ .Content }}|
291 -- layouts/_default/_markup/render-passthrough.html --
292 {{ $opts := dict "throwOnError" true "displayMode" true }}
293 {{- with try (transform.ToMath .Inner $opts ) }}
294 {{- with .Err }}
295 {{ errorf "KaTeX: %s: see %s." . $.Position }}
296 {{- else }}
297 {{- .Value }}
298 {{- end }}
299 {{- end -}}
300
301 `
302
303 // Block math.
304 files := strings.Replace(filesTemplate, "$$1+2$$", "$$\\foo1+2$$", 1)
305 b, err := hugolib.TestE(t, files)
306 b.Assert(err, qt.IsNotNil)
307 b.AssertLogContains("p1.md:6:1")
308
309 // Inline math.
310 files = strings.Replace(filesTemplate, "$1+3$", "$\\foo1+3$", 1)
311 b, err = hugolib.TestE(t, files)
312 b.Assert(err, qt.IsNotNil)
313 b.AssertLogContains("p1.md:8:13")
314 }
315
316 func TestToMathMacros(t *testing.T) {
317 files := `
318 -- hugo.toml --
319 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
320 -- layouts/index.html --
321 {{ $macros := dict
322 "\\addBar" "\\bar{#1}"
323 "\\bold" "\\mathbf{#1}"
324 }}
325 {{ $opts := dict "macros" $macros }}
326 {{ transform.ToMath "\\addBar{y} + \\bold{H}" $opts }}
327 `
328 b := hugolib.Test(t, files)
329
330 b.AssertFileContent("public/index.html", `
331 <mi>y</mi>
332 `)
333 }
334
335 // Issue #12977
336 func TestUnmarshalWithIndentedYAML(t *testing.T) {
337 t.Parallel()
338
339 files := `
340 -- hugo.toml --
341 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
342 -- layouts/index.html --
343 {{ $yaml := "\n a:\n b: 1\n c:\n d: 2\n" }}
344 {{ $yaml | transform.Unmarshal | encoding.Jsonify }}
345 `
346
347 b := hugolib.Test(t, files)
348
349 b.AssertFileExists("public/index.html", true)
350 b.AssertFileContent("public/index.html", `{"a":{"b":1},"c":{"d":2}}`)
351 }
352
353 func TestPortableText(t *testing.T) {
354 files := `
355 -- hugo.toml --
356 -- assets/sample.json --
357 [
358 {
359 "_key": "a",
360 "_type": "block",
361 "children": [
362 {
363 "_key": "b",
364 "_type": "span",
365 "marks": [],
366 "text": "Heading 2"
367 }
368 ],
369 "markDefs": [],
370 "style": "h2"
371 }
372 ]
373 -- layouts/index.html --
374 {{ $markdown := resources.Get "sample.json" | transform.Unmarshal | transform.PortableText }}
375 Markdown: {{ $markdown }}|
376
377 `
378 b := hugolib.Test(t, files)
379
380 b.AssertFileContent("public/index.html", "Markdown: ## Heading 2\n|")
381 }
382
383 func TestUnmarshalCSV(t *testing.T) {
384 t.Parallel()
385
386 files := `
387 -- hugo.toml --
388 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
389 -- layouts/all.html --
390 {{ $opts := OPTS }}
391 {{ with resources.Get "pets.csv" | transform.Unmarshal $opts }}
392 {{ jsonify . }}
393 {{ end }}
394 -- assets/pets.csv --
395 DATA
396 `
397
398 // targetType = map
399 f := strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
400 f = strings.ReplaceAll(f, "DATA",
401 "name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
402 )
403 b := hugolib.Test(t, f)
404 b.AssertFileContent("public/index.html",
405 `[{"age":"3","breed":"Collie","name":"Spot","type":"dog"},{"age":"7","breed":"Malicious","name":"Felix","type":"cat"}]`,
406 )
407
408 // targetType = map (no data)
409 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
410 f = strings.ReplaceAll(f, "DATA", "")
411 b = hugolib.Test(t, f)
412 b.AssertFileContent("public/index.html", "")
413
414 // targetType = slice
415 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "slice"`)
416 f = strings.ReplaceAll(f, "DATA",
417 "name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
418 )
419 b = hugolib.Test(t, f)
420 b.AssertFileContent("public/index.html",
421 `[["name","type","breed","age"],["Spot","dog","Collie","3"],["Felix","cat","Malicious","7"]]`,
422 )
423
424 // targetType = slice (no data)
425 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "slice"`)
426 f = strings.ReplaceAll(f, "DATA", "")
427 b = hugolib.Test(t, f)
428 b.AssertFileContent("public/index.html", "")
429
430 // targetType not specified
431 f = strings.ReplaceAll(files, "OPTS", "dict")
432 f = strings.ReplaceAll(f, "DATA",
433 "name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
434 )
435 b = hugolib.Test(t, f)
436 b.AssertFileContent("public/index.html",
437 `[["name","type","breed","age"],["Spot","dog","Collie","3"],["Felix","cat","Malicious","7"]]`,
438 )
439
440 // targetType not specified (no data)
441 f = strings.ReplaceAll(files, "OPTS", "dict")
442 f = strings.ReplaceAll(f, "DATA", "")
443 b = hugolib.Test(t, f)
444 b.AssertFileContent("public/index.html", "")
445
446 // targetType = foo
447 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "foo"`)
448 _, err := hugolib.TestE(t, f)
449 if err == nil {
450 t.Errorf("expected error")
451 } else {
452 if !strings.Contains(err.Error(), `invalid targetType: expected either slice or map, received foo`) {
453 t.Log(err.Error())
454 t.Errorf("error message does not match expected error message")
455 }
456 }
457
458 // targetType = foo (no data)
459 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "foo"`)
460 f = strings.ReplaceAll(f, "DATA", "")
461 _, err = hugolib.TestE(t, f)
462 if err == nil {
463 t.Errorf("expected error")
464 } else {
465 if !strings.Contains(err.Error(), `invalid targetType: expected either slice or map, received foo`) {
466 t.Log(err.Error())
467 t.Errorf("error message does not match expected error message")
468 }
469 }
470
471 // targetType = map (error: expected at least a header row and one data row)
472 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
473 _, err = hugolib.TestE(t, f)
474 if err == nil {
475 t.Errorf("expected error")
476 } else {
477 if !strings.Contains(err.Error(), `expected at least a header row and one data row`) {
478 t.Log(err.Error())
479 t.Errorf("error message does not match expected error message")
480 }
481 }
482
483 // targetType = map (error: header row contains duplicate field names)
484 f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
485 f = strings.ReplaceAll(f, "DATA",
486 "name,name,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
487 )
488 _, err = hugolib.TestE(t, f)
489 if err == nil {
490 t.Errorf("expected error")
491 } else {
492 if !strings.Contains(err.Error(), `header row contains duplicate field names`) {
493 t.Log(err.Error())
494 t.Errorf("error message does not match expected error message")
495 }
496 }
497 }
498
499 // Issue 13729
500 func TestToMathStrictMode(t *testing.T) {
501 t.Parallel()
502
503 files := `
504 -- hugo.toml --
505 disableKinds = ['page','rss','section','sitemap','taxonomy','term']
506 -- layouts/all.html --
507 {{ transform.ToMath "a %" dict }}
508 -- foo --
509 `
510
511 // strict mode: default
512 f := strings.ReplaceAll(files, "dict", "")
513 b, err := hugolib.TestE(t, f)
514 b.Assert(err.Error(), qt.Contains, "[commentAtEnd]")
515
516 // strict mode: error
517 f = strings.ReplaceAll(files, "dict", `(dict "strict" "error")`)
518 b, err = hugolib.TestE(t, f)
519 b.Assert(err.Error(), qt.Contains, "[commentAtEnd]")
520
521 // strict mode: ignore
522 f = strings.ReplaceAll(files, "dict", `(dict "strict" "ignore")`)
523 b = hugolib.Test(t, f, hugolib.TestOptWarn())
524 b.AssertLogMatches("")
525 b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
526
527 // strict: warn
528 f = strings.ReplaceAll(files, "dict", `(dict "strict" "warn")`)
529 b = hugolib.Test(t, f, hugolib.TestOptWarn())
530 b.AssertLogMatches("[commentAtEnd]")
531 b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
532
533 // strict mode: invalid value
534 f = strings.ReplaceAll(files, "dict", `(dict "strict" "foo")`)
535 b, err = hugolib.TestE(t, f)
536 b.Assert(err.Error(), qt.Contains, "invalid strict mode")
537 }