page_test.go (57555B)
1 // Copyright 2019 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 hugolib
15
16 import (
17 "fmt"
18 "html/template"
19 "os"
20 "path/filepath"
21 "strings"
22 "testing"
23 "time"
24
25 "github.com/bep/clock"
26 "github.com/gohugoio/hugo/htesting"
27 "github.com/gohugoio/hugo/markup/asciidocext"
28 "github.com/gohugoio/hugo/markup/rst"
29 "github.com/gohugoio/hugo/tpl"
30
31 "github.com/gohugoio/hugo/config"
32
33 "github.com/gohugoio/hugo/common/htime"
34 "github.com/gohugoio/hugo/common/loggers"
35
36 "github.com/gohugoio/hugo/hugofs"
37
38 "github.com/gohugoio/hugo/resources/page"
39 "github.com/gohugoio/hugo/resources/resource"
40 "github.com/spf13/jwalterweatherman"
41
42 qt "github.com/frankban/quicktest"
43 "github.com/gohugoio/hugo/deps"
44 )
45
46 const (
47 homePage = "---\ntitle: Home\n---\nHome Page Content\n"
48 simplePage = "---\ntitle: Simple\n---\nSimple Page\n"
49
50 simplePageRFC3339Date = "---\ntitle: RFC3339 Date\ndate: \"2013-05-17T16:59:30Z\"\n---\nrfc3339 content"
51
52 simplePageWithoutSummaryDelimiter = `---
53 title: SimpleWithoutSummaryDelimiter
54 ---
55 [Lorem ipsum](https://lipsum.com/) dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
56
57 Additional text.
58
59 Further text.
60 `
61
62 simplePageWithSummaryDelimiter = `---
63 title: Simple
64 ---
65 Summary Next Line
66
67 <!--more-->
68 Some more text
69 `
70
71 simplePageWithSummaryParameter = `---
72 title: SimpleWithSummaryParameter
73 summary: "Page with summary parameter and [a link](http://www.example.com/)"
74 ---
75
76 Some text.
77
78 Some more text.
79 `
80
81 simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder = `---
82 title: Simple
83 ---
84 The [best static site generator][hugo].[^1]
85 <!--more-->
86 [hugo]: http://gohugo.io/
87 [^1]: Many people say so.
88 `
89 simplePageWithShortcodeInSummary = `---
90 title: Simple
91 ---
92 Summary Next Line. {{<figure src="/not/real" >}}.
93 More text here.
94
95 Some more text
96 `
97
98 simplePageWithSummaryDelimiterSameLine = `---
99 title: Simple
100 ---
101 Summary Same Line<!--more-->
102
103 Some more text
104 `
105
106 simplePageWithAllCJKRunes = `---
107 title: Simple
108 ---
109
110
111 € € € € €
112 你好
113 도형이
114 カテゴリー
115
116
117 `
118
119 simplePageWithMainEnglishWithCJKRunes = `---
120 title: Simple
121 ---
122
123
124 In Chinese, 好 means good. In Chinese, 好 means good.
125 In Chinese, 好 means good. In Chinese, 好 means good.
126 In Chinese, 好 means good. In Chinese, 好 means good.
127 In Chinese, 好 means good. In Chinese, 好 means good.
128 In Chinese, 好 means good. In Chinese, 好 means good.
129 In Chinese, 好 means good. In Chinese, 好 means good.
130 In Chinese, 好 means good. In Chinese, 好 means good.
131 More then 70 words.
132
133
134 `
135 simplePageWithMainEnglishWithCJKRunesSummary = "In Chinese, 好 means good. In Chinese, 好 means good. " +
136 "In Chinese, 好 means good. In Chinese, 好 means good. " +
137 "In Chinese, 好 means good. In Chinese, 好 means good. " +
138 "In Chinese, 好 means good. In Chinese, 好 means good. " +
139 "In Chinese, 好 means good. In Chinese, 好 means good. " +
140 "In Chinese, 好 means good. In Chinese, 好 means good. " +
141 "In Chinese, 好 means good. In Chinese, 好 means good."
142
143 simplePageWithIsCJKLanguageFalse = `---
144 title: Simple
145 isCJKLanguage: false
146 ---
147
148 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
149 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
150 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
151 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
152 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
153 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good.
154 In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough.
155 More then 70 words.
156
157
158 `
159 simplePageWithIsCJKLanguageFalseSummary = "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
160 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
161 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
162 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
163 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
164 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
165 "In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough."
166
167 simplePageWithLongContent = `---
168 title: Simple
169 ---
170
171 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
172 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
173 nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
174 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
175 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
176 culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
177 amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
178 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
179 ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
180 in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
181 pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
182 officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet,
183 consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
184 dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
185 laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
186 reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
187 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
188 deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur
189 adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
190 aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
191 ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
192 voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
193 occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
194 id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
195 do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
196 veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
197 consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
198 cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
199 proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem
200 ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
201 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
202 nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
203 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
204 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
205 culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
206 amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
207 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
208 ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
209 in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
210 pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
211 officia deserunt mollit anim id est laborum.`
212
213 pageWithToC = `---
214 title: TOC
215 ---
216 For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
217
218 ## AA
219
220 I have no idea, of course, how long it took me to reach the limit of the plain,
221 but at last I entered the foothills, following a pretty little canyon upward
222 toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon
223 its noisy way down to the silent sea. In its quieter pools I discovered many
224 small fish, of four-or five-pound weight I should imagine. In appearance,
225 except as to size and color, they were not unlike the whale of our own seas. As
226 I watched them playing about I discovered, not only that they suckled their
227 young, but that at intervals they rose to the surface to breathe as well as to
228 feed upon certain grasses and a strange, scarlet lichen which grew upon the
229 rocks just above the water line.
230
231 ### AAA
232
233 I remember I felt an extraordinary persuasion that I was being played with,
234 that presently, when I was upon the very verge of safety, this mysterious
235 death--as swift as the passage of light--would leap after me from the pit about
236 the cylinder and strike me down. ## BB
237
238 ### BBB
239
240 "You're a great Granser," he cried delightedly, "always making believe them little marks mean something."
241 `
242
243 simplePageWithURL = `---
244 title: Simple
245 url: simple/url/
246 ---
247 Simple Page With URL`
248
249 simplePageWithSlug = `---
250 title: Simple
251 slug: simple-slug
252 ---
253 Simple Page With Slug`
254
255 simplePageWithDate = `---
256 title: Simple
257 date: '2013-10-15T06:16:13'
258 ---
259 Simple Page With Date`
260
261 UTF8Page = `---
262 title: ラーメン
263 ---
264 UTF8 Page`
265
266 UTF8PageWithURL = `---
267 title: ラーメン
268 url: ラーメン/url/
269 ---
270 UTF8 Page With URL`
271
272 UTF8PageWithSlug = `---
273 title: ラーメン
274 slug: ラーメン-slug
275 ---
276 UTF8 Page With Slug`
277
278 UTF8PageWithDate = `---
279 title: ラーメン
280 date: '2013-10-15T06:16:13'
281 ---
282 UTF8 Page With Date`
283 )
284
285 func checkPageTitle(t *testing.T, page page.Page, title string) {
286 if page.Title() != title {
287 t.Fatalf("Page title is: %s. Expected %s", page.Title(), title)
288 }
289 }
290
291 func checkPageContent(t *testing.T, page page.Page, expected string, msg ...any) {
292 t.Helper()
293 a := normalizeContent(expected)
294 b := normalizeContent(content(page))
295 if a != b {
296 t.Fatalf("Page content is:\n%q\nExpected:\n%q (%q)", b, a, msg)
297 }
298 }
299
300 func normalizeContent(c string) string {
301 norm := c
302 norm = strings.Replace(norm, "\n", " ", -1)
303 norm = strings.Replace(norm, " ", " ", -1)
304 norm = strings.Replace(norm, " ", " ", -1)
305 norm = strings.Replace(norm, " ", " ", -1)
306 norm = strings.Replace(norm, "p> ", "p>", -1)
307 norm = strings.Replace(norm, "> <", "> <", -1)
308 return strings.TrimSpace(norm)
309 }
310
311 func checkPageTOC(t *testing.T, page page.Page, toc string) {
312 t.Helper()
313 if page.TableOfContents() != template.HTML(toc) {
314 t.Fatalf("Page TableOfContents is:\n%q.\nExpected %q", page.TableOfContents(), toc)
315 }
316 }
317
318 func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...any) {
319 a := normalizeContent(string(page.Summary()))
320 b := normalizeContent(summary)
321 if a != b {
322 t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg)
323 }
324 }
325
326 func checkPageType(t *testing.T, page page.Page, pageType string) {
327 if page.Type() != pageType {
328 t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType)
329 }
330 }
331
332 func checkPageDate(t *testing.T, page page.Page, time time.Time) {
333 if page.Date() != time {
334 t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time)
335 }
336 }
337
338 func normalizeExpected(ext, str string) string {
339 str = normalizeContent(str)
340 switch ext {
341 default:
342 return str
343 case "html":
344 return strings.Trim(tpl.StripHTML(str), " ")
345 case "ad":
346 paragraphs := strings.Split(str, "</p>")
347 expected := ""
348 for _, para := range paragraphs {
349 if para == "" {
350 continue
351 }
352 expected += fmt.Sprintf("<div class=\"paragraph\">\n%s</p></div>\n", para)
353 }
354
355 return expected
356 case "rst":
357 return fmt.Sprintf("<div class=\"document\">\n\n\n%s</div>", str)
358 }
359 }
360
361 func testAllMarkdownEnginesForPages(t *testing.T,
362 assertFunc func(t *testing.T, ext string, pages page.Pages), settings map[string]any, pageSources ...string) {
363
364 engines := []struct {
365 ext string
366 shouldExecute func() bool
367 }{
368 {"md", func() bool { return true }},
369 {"ad", func() bool { return asciidocext.Supports() }},
370 {"rst", func() bool { return rst.Supports() }},
371 }
372
373 for _, e := range engines {
374 if !e.shouldExecute() {
375 continue
376 }
377
378 t.Run(e.ext, func(t *testing.T) {
379 cfg, fs := newTestCfg(func(cfg config.Provider) error {
380 for k, v := range settings {
381 cfg.Set(k, v)
382 }
383 return nil
384 })
385
386 contentDir := "content"
387
388 if s := cfg.GetString("contentDir"); s != "" {
389 contentDir = s
390 }
391
392 cfg.Set("security", map[string]any{
393 "exec": map[string]any{
394 "allow": []string{"^python$", "^rst2html.*", "^asciidoctor$"},
395 },
396 })
397
398 var fileSourcePairs []string
399
400 for i, source := range pageSources {
401 fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source)
402 }
403
404 for i := 0; i < len(fileSourcePairs); i += 2 {
405 writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
406 }
407
408 // Add a content page for the home page
409 homePath := fmt.Sprintf("_index.%s", e.ext)
410 writeSource(t, fs, filepath.Join(contentDir, homePath), homePage)
411
412 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
413 b.Build(BuildCfg{})
414
415 s := b.H.Sites[0]
416
417 b.Assert(len(s.RegularPages()), qt.Equals, len(pageSources))
418
419 assertFunc(t, e.ext, s.RegularPages())
420
421 home := s.Info.Home()
422 b.Assert(home, qt.Not(qt.IsNil))
423 b.Assert(home.File().Path(), qt.Equals, homePath)
424 b.Assert(content(home), qt.Contains, "Home Page Content")
425 })
426
427 }
428 }
429
430 // Issue #1076
431 func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
432 t.Parallel()
433 cfg, fs := newTestCfg()
434
435 c := qt.New(t)
436
437 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
438
439 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
440
441 c.Assert(len(s.RegularPages()), qt.Equals, 1)
442
443 p := s.RegularPages()[0]
444
445 if p.Summary() != template.HTML(
446 "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>") {
447 t.Fatalf("Got summary:\n%q", p.Summary())
448 }
449
450 cnt := content(p)
451 if cnt != "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Many people say so. <a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>" {
452 t.Fatalf("Got content:\n%q", cnt)
453 }
454 }
455
456 func TestPageDatesAllKinds(t *testing.T) {
457 t.Parallel()
458
459 pageContent := `
460 ---
461 title: Page
462 date: 2017-01-15
463 tags: ["hugo"]
464 categories: ["cool stuff"]
465 ---
466 `
467
468 b := newTestSitesBuilder(t)
469 b.WithSimpleConfigFile().WithContent("page.md", pageContent)
470 b.WithContent("blog/page.md", pageContent)
471
472 b.CreateSites().Build(BuildCfg{})
473
474 b.Assert(len(b.H.Sites), qt.Equals, 1)
475 s := b.H.Sites[0]
476
477 checkDate := func(t time.Time, msg string) {
478 b.Assert(t.Year(), qt.Equals, 2017, qt.Commentf(msg))
479 }
480
481 checkDated := func(d resource.Dated, msg string) {
482 checkDate(d.Date(), "date: "+msg)
483 checkDate(d.Lastmod(), "lastmod: "+msg)
484 }
485 for _, p := range s.Pages() {
486 checkDated(p, p.Kind())
487 }
488 checkDate(s.Info.LastChange(), "site")
489 }
490
491 func TestPageDatesSections(t *testing.T) {
492 t.Parallel()
493
494 b := newTestSitesBuilder(t)
495 b.WithSimpleConfigFile().WithContent("no-index/page.md", `
496 ---
497 title: Page
498 date: 2017-01-15
499 ---
500 `, "with-index-no-date/_index.md", `---
501 title: No Date
502 ---
503
504 `,
505 // https://github.com/gohugoio/hugo/issues/5854
506 "with-index-date/_index.md", `---
507 title: Date
508 date: 2018-01-15
509 ---
510
511 `, "with-index-date/p1.md", `---
512 title: Date
513 date: 2018-01-15
514 ---
515
516 `, "with-index-date/p1.md", `---
517 title: Date
518 date: 2018-01-15
519 ---
520
521 `)
522
523 for i := 1; i <= 20; i++ {
524 b.WithContent(fmt.Sprintf("main-section/p%d.md", i), `---
525 title: Date
526 date: 2012-01-12
527 ---
528
529 `)
530 }
531
532 b.CreateSites().Build(BuildCfg{})
533
534 b.Assert(len(b.H.Sites), qt.Equals, 1)
535 s := b.H.Sites[0]
536
537 checkDate := func(p page.Page, year int) {
538 b.Assert(p.Date().Year(), qt.Equals, year)
539 b.Assert(p.Lastmod().Year(), qt.Equals, year)
540 }
541
542 checkDate(s.getPage("/"), 2018)
543 checkDate(s.getPage("/no-index"), 2017)
544 b.Assert(s.getPage("/with-index-no-date").Date().IsZero(), qt.Equals, true)
545 checkDate(s.getPage("/with-index-date"), 2018)
546
547 b.Assert(s.Site.LastChange().Year(), qt.Equals, 2018)
548 }
549
550 func TestCreateNewPage(t *testing.T) {
551 t.Parallel()
552 c := qt.New(t)
553 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
554 p := pages[0]
555
556 // issue #2290: Path is relative to the content dir and will continue to be so.
557 c.Assert(p.File().Path(), qt.Equals, fmt.Sprintf("p0.%s", ext))
558 c.Assert(p.IsHome(), qt.Equals, false)
559 checkPageTitle(t, p, "Simple")
560 checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
561 checkPageSummary(t, p, "Simple Page")
562 checkPageType(t, p, "page")
563 }
564
565 settings := map[string]any{
566 "contentDir": "mycontent",
567 }
568
569 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage)
570 }
571
572 func TestPageSummary(t *testing.T) {
573 t.Parallel()
574 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
575 p := pages[0]
576 checkPageTitle(t, p, "SimpleWithoutSummaryDelimiter")
577 // Source is not Asciidoctor- or RST-compatible so don't test them
578 if ext != "ad" && ext != "rst" {
579 checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\n"), ext)
580 checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext)
581 }
582 checkPageType(t, p, "page")
583 }
584
585 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithoutSummaryDelimiter)
586 }
587
588 func TestPageWithDelimiter(t *testing.T) {
589 t.Parallel()
590 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
591 p := pages[0]
592 checkPageTitle(t, p, "Simple")
593 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext)
594 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext)
595 checkPageType(t, p, "page")
596 }
597
598 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
599 }
600
601 func TestPageWithSummaryParameter(t *testing.T) {
602 t.Parallel()
603 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
604 p := pages[0]
605 checkPageTitle(t, p, "SimpleWithSummaryParameter")
606 checkPageContent(t, p, normalizeExpected(ext, "<p>Some text.</p>\n\n<p>Some more text.</p>\n"), ext)
607 // Summary is not Asciidoctor- or RST-compatible so don't test them
608 if ext != "ad" && ext != "rst" {
609 checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and <a href=\"http://www.example.com/\">a link</a>"), ext)
610 }
611 checkPageType(t, p, "page")
612 }
613
614 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter)
615 }
616
617 // Issue #3854
618 // Also see https://github.com/gohugoio/hugo/issues/3977
619 func TestPageWithDateFields(t *testing.T) {
620 c := qt.New(t)
621 pageWithDate := `---
622 title: P%d
623 weight: %d
624 %s: 2017-10-13
625 ---
626 Simple Page With Some Date`
627
628 hasDate := func(p page.Page) bool {
629 return p.Date().Year() == 2017
630 }
631
632 datePage := func(field string, weight int) string {
633 return fmt.Sprintf(pageWithDate, weight, weight, field)
634 }
635
636 t.Parallel()
637 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
638 c.Assert(len(pages) > 0, qt.Equals, true)
639 for _, p := range pages {
640 c.Assert(hasDate(p), qt.Equals, true)
641 }
642 }
643
644 fields := []string{"date", "publishdate", "pubdate", "published"}
645 pageContents := make([]string, len(fields))
646 for i, field := range fields {
647 pageContents[i] = datePage(field, i+1)
648 }
649
650 testAllMarkdownEnginesForPages(t, assertFunc, nil, pageContents...)
651 }
652
653 // Issue #2601
654 func TestPageRawContent(t *testing.T) {
655 t.Parallel()
656 cfg, fs := newTestCfg()
657 c := qt.New(t)
658
659 writeSource(t, fs, filepath.Join("content", "raw.md"), `---
660 title: Raw
661 ---
662 **Raw**`)
663
664 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
665
666 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
667
668 c.Assert(len(s.RegularPages()), qt.Equals, 1)
669 p := s.RegularPages()[0]
670
671 c.Assert("**Raw**", qt.Equals, p.RawContent())
672 }
673
674 func TestPageWithShortCodeInSummary(t *testing.T) {
675 t.Parallel()
676 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
677 p := pages[0]
678 checkPageTitle(t, p, "Simple")
679 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
680 checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
681 checkPageType(t, p, "page")
682 }
683
684 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
685 }
686
687 func TestTableOfContents(t *testing.T) {
688 cfg, fs := newTestCfg()
689 c := qt.New(t)
690
691 writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC)
692
693 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
694
695 c.Assert(len(s.RegularPages()), qt.Equals, 1)
696
697 p := s.RegularPages()[0]
698
699 checkPageContent(t, p, "<p>For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.</p><h2 id=\"aa\">AA</h2> <p>I have no idea, of course, how long it took me to reach the limit of the plain, but at last I entered the foothills, following a pretty little canyon upward toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon its noisy way down to the silent sea. In its quieter pools I discovered many small fish, of four-or five-pound weight I should imagine. In appearance, except as to size and color, they were not unlike the whale of our own seas. As I watched them playing about I discovered, not only that they suckled their young, but that at intervals they rose to the surface to breathe as well as to feed upon certain grasses and a strange, scarlet lichen which grew upon the rocks just above the water line.</p><h3 id=\"aaa\">AAA</h3> <p>I remember I felt an extraordinary persuasion that I was being played with, that presently, when I was upon the very verge of safety, this mysterious death–as swift as the passage of light–would leap after me from the pit about the cylinder and strike me down. ## BB</p><h3 id=\"bbb\">BBB</h3> <p>“You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”</p>")
700 checkPageTOC(t, p, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#aa\">AA</a>\n <ul>\n <li><a href=\"#aaa\">AAA</a></li>\n <li><a href=\"#bbb\">BBB</a></li>\n </ul>\n </li>\n </ul>\n</nav>")
701 }
702
703 func TestPageWithMoreTag(t *testing.T) {
704 t.Parallel()
705 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
706 p := pages[0]
707 checkPageTitle(t, p, "Simple")
708 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n"))
709 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>"))
710 checkPageType(t, p, "page")
711 }
712
713 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine)
714 }
715
716 // #2973
717 func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) {
718 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
719 c := qt.New(t)
720 p := pages[0]
721 s := string(p.Summary())
722 c.Assert(s, qt.Contains, "Happy new year everyone!")
723 c.Assert(s, qt.Not(qt.Contains), "User interface")
724 }
725
726 testAllMarkdownEnginesForPages(t, assertFunc, nil, `---
727 title: Simple
728 ---
729 Happy new year everyone!
730
731 Here is the last report for commits in the year 2016. It covers hrev50718-hrev50829.
732
733 <!--more-->
734
735 <h3>User interface</h3>
736
737 `)
738 }
739
740 // Issue 9383
741 func TestRenderStringForRegularPageTranslations(t *testing.T) {
742 c := qt.New(t)
743 b := newTestSitesBuilder(t)
744 b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelError, os.Stderr))
745
746 b.WithConfigFile("toml",
747 `baseurl = "https://example.org/"
748 title = "My Site"
749
750 defaultContentLanguage = "ru"
751 defaultContentLanguageInSubdir = true
752
753 [languages.ru]
754 contentDir = 'content/ru'
755 weight = 1
756
757 [languages.en]
758 weight = 2
759 contentDir = 'content/en'
760
761 [outputs]
762 home = ["HTML", "JSON"]`)
763
764 b.WithTemplates("index.html", `
765 {{- range .Site.Home.Translations -}}
766 <p>{{- .RenderString "foo" -}}</p>
767 {{- end -}}
768 {{- range .Site.Home.AllTranslations -}}
769 <p>{{- .RenderString "bar" -}}</p>
770 {{- end -}}
771 `, "_default/single.html",
772 `{{ .Content }}`,
773 "index.json",
774 `{"Title": "My Site"}`,
775 )
776
777 b.WithContent(
778 "ru/a.md",
779 "",
780 "en/a.md",
781 "",
782 )
783
784 err := b.BuildE(BuildCfg{})
785 c.Assert(err, qt.Equals, nil)
786
787 b.AssertFileContent("public/ru/index.html", `
788 <p>foo</p>
789 <p>foo</p>
790 <p>bar</p>
791 <p>bar</p>
792 `)
793
794 b.AssertFileContent("public/en/index.html", `
795 <p>foo</p>
796 <p>foo</p>
797 <p>bar</p>
798 <p>bar</p>
799 `)
800 }
801
802 // Issue 8919
803 func TestContentProviderWithCustomOutputFormat(t *testing.T) {
804 b := newTestSitesBuilder(t)
805 b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelDebug, os.Stderr))
806 b.WithConfigFile("toml", `baseURL = 'http://example.org/'
807 title = 'My New Hugo Site'
808
809 timeout = 600000 # ten minutes in case we want to pause and debug
810
811 defaultContentLanguage = "en"
812
813 [languages]
814 [languages.en]
815 title = "Repro"
816 languageName = "English"
817 contentDir = "content/en"
818
819 [languages.zh_CN]
820 title = "Repro"
821 languageName = "简体中文"
822 contentDir = "content/zh_CN"
823
824 [outputFormats]
825 [outputFormats.metadata]
826 baseName = "metadata"
827 mediaType = "text/html"
828 isPlainText = true
829 notAlternative = true
830
831 [outputs]
832 home = ["HTML", "metadata"]`)
833
834 b.WithTemplates("home.metadata.html", `<h2>Translations metadata</h2>
835 <ul>
836 {{ $p := .Page }}
837 {{ range $p.Translations}}
838 <li>Title: {{ .Title }}, {{ .Summary }}</li>
839 <li>Content: {{ .Content }}</li>
840 <li>Plain: {{ .Plain }}</li>
841 <li>PlainWords: {{ .PlainWords }}</li>
842 <li>Summary: {{ .Summary }}</li>
843 <li>Truncated: {{ .Truncated }}</li>
844 <li>FuzzyWordCount: {{ .FuzzyWordCount }}</li>
845 <li>ReadingTime: {{ .ReadingTime }}</li>
846 <li>Len: {{ .Len }}</li>
847 {{ end }}
848 </ul>`)
849
850 b.WithTemplates("_default/baseof.html", `<html>
851
852 <body>
853 {{ block "main" . }}{{ end }}
854 </body>
855
856 </html>`)
857
858 b.WithTemplates("_default/home.html", `{{ define "main" }}
859 <h2>Translations</h2>
860 <ul>
861 {{ $p := .Page }}
862 {{ range $p.Translations}}
863 <li>Title: {{ .Title }}, {{ .Summary }}</li>
864 <li>Content: {{ .Content }}</li>
865 <li>Plain: {{ .Plain }}</li>
866 <li>PlainWords: {{ .PlainWords }}</li>
867 <li>Summary: {{ .Summary }}</li>
868 <li>Truncated: {{ .Truncated }}</li>
869 <li>FuzzyWordCount: {{ .FuzzyWordCount }}</li>
870 <li>ReadingTime: {{ .ReadingTime }}</li>
871 <li>Len: {{ .Len }}</li>
872 {{ end }}
873 </ul>
874 {{ end }}`)
875
876 b.WithContent("en/_index.md", `---
877 title: Title (en)
878 summary: Summary (en)
879 ---
880
881 Here is some content.
882 `)
883
884 b.WithContent("zh_CN/_index.md", `---
885 title: Title (zh)
886 summary: Summary (zh)
887 ---
888
889 这是一些内容
890 `)
891
892 b.Build(BuildCfg{})
893
894 b.AssertFileContent("public/index.html", `<html>
895
896 <body>
897
898 <h2>Translations</h2>
899 <ul>
900
901
902 <li>Title: Title (zh), Summary (zh)</li>
903 <li>Content: <p>这是一些内容</p>
904 </li>
905 <li>Plain: 这是一些内容
906 </li>
907 <li>PlainWords: [这是一些内容]</li>
908 <li>Summary: Summary (zh)</li>
909 <li>Truncated: false</li>
910 <li>FuzzyWordCount: 100</li>
911 <li>ReadingTime: 1</li>
912 <li>Len: 26</li>
913
914 </ul>
915
916 </body>
917
918 </html>`)
919 b.AssertFileContent("public/metadata.html", `<h2>Translations metadata</h2>
920 <ul>
921
922
923 <li>Title: Title (zh), Summary (zh)</li>
924 <li>Content: <p>这是一些内容</p>
925 </li>
926 <li>Plain: 这是一些内容
927 </li>
928 <li>PlainWords: [这是一些内容]</li>
929 <li>Summary: Summary (zh)</li>
930 <li>Truncated: false</li>
931 <li>FuzzyWordCount: 100</li>
932 <li>ReadingTime: 1</li>
933 <li>Len: 26</li>
934
935 </ul>`)
936 b.AssertFileContent("public/zh_cn/index.html", `<html>
937
938 <body>
939
940 <h2>Translations</h2>
941 <ul>
942
943
944 <li>Title: Title (en), Summary (en)</li>
945 <li>Content: <p>Here is some content.</p>
946 </li>
947 <li>Plain: Here is some content.
948 </li>
949 <li>PlainWords: [Here is some content.]</li>
950 <li>Summary: Summary (en)</li>
951 <li>Truncated: false</li>
952 <li>FuzzyWordCount: 100</li>
953 <li>ReadingTime: 1</li>
954 <li>Len: 29</li>
955
956 </ul>
957
958 </body>
959
960 </html>`)
961 b.AssertFileContent("public/zh_cn/metadata.html", `<h2>Translations metadata</h2>
962 <ul>
963
964
965 <li>Title: Title (en), Summary (en)</li>
966 <li>Content: <p>Here is some content.</p>
967 </li>
968 <li>Plain: Here is some content.
969 </li>
970 <li>PlainWords: [Here is some content.]</li>
971 <li>Summary: Summary (en)</li>
972 <li>Truncated: false</li>
973 <li>FuzzyWordCount: 100</li>
974 <li>ReadingTime: 1</li>
975 <li>Len: 29</li>
976
977 </ul>`)
978 }
979
980 func TestPageWithDate(t *testing.T) {
981 t.Parallel()
982 cfg, fs := newTestCfg()
983 c := qt.New(t)
984
985 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date)
986
987 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
988
989 c.Assert(len(s.RegularPages()), qt.Equals, 1)
990
991 p := s.RegularPages()[0]
992 d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z")
993
994 checkPageDate(t, p, d)
995 }
996
997 func TestPageWithLastmodFromGitInfo(t *testing.T) {
998 if htesting.IsCI() {
999 // TODO(bep) figure out why this fails on GitHub actions.
1000 t.Skip("Skip GitInfo test on CI")
1001 }
1002 c := qt.New(t)
1003
1004 wd, err := os.Getwd()
1005 c.Assert(err, qt.IsNil)
1006
1007 // We need to use the OS fs for this.
1008 cfg := config.NewWithTestDefaults()
1009 cfg.Set("workingDir", filepath.Join(wd, "testsite"))
1010 fs := hugofs.NewFrom(hugofs.Os, cfg)
1011
1012 cfg.Set("frontmatter", map[string]any{
1013 "lastmod": []string{":git", "lastmod"},
1014 })
1015 cfg.Set("defaultContentLanguage", "en")
1016
1017 langConfig := map[string]any{
1018 "en": map[string]any{
1019 "weight": 1,
1020 "languageName": "English",
1021 "contentDir": "content",
1022 },
1023 "nn": map[string]any{
1024 "weight": 2,
1025 "languageName": "Nynorsk",
1026 "contentDir": "content_nn",
1027 },
1028 }
1029
1030 cfg.Set("languages", langConfig)
1031 cfg.Set("enableGitInfo", true)
1032
1033 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
1034
1035 b.Build(BuildCfg{SkipRender: true})
1036 h := b.H
1037
1038 c.Assert(len(h.Sites), qt.Equals, 2)
1039
1040 enSite := h.Sites[0]
1041 c.Assert(len(enSite.RegularPages()), qt.Equals, 1)
1042
1043 // 2018-03-11 is the Git author date for testsite/content/first-post.md
1044 c.Assert(enSite.RegularPages()[0].Lastmod().Format("2006-01-02"), qt.Equals, "2018-03-11")
1045 c.Assert(enSite.RegularPages()[0].CodeOwners()[0], qt.Equals, "@bep")
1046
1047 nnSite := h.Sites[1]
1048 c.Assert(len(nnSite.RegularPages()), qt.Equals, 1)
1049
1050 // 2018-08-11 is the Git author date for testsite/content_nn/first-post.md
1051 c.Assert(nnSite.RegularPages()[0].Lastmod().Format("2006-01-02"), qt.Equals, "2018-08-11")
1052 c.Assert(enSite.RegularPages()[0].CodeOwners()[0], qt.Equals, "@bep")
1053 }
1054
1055 func TestPageWithFrontMatterConfig(t *testing.T) {
1056 for _, dateHandler := range []string{":filename", ":fileModTime"} {
1057 dateHandler := dateHandler
1058 t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) {
1059 t.Parallel()
1060 c := qt.New(t)
1061 cfg, fs := newTestCfg()
1062
1063 pageTemplate := `
1064 ---
1065 title: Page
1066 weight: %d
1067 lastMod: 2018-02-28
1068 %s
1069 ---
1070 Content
1071 `
1072
1073 cfg.Set("frontmatter", map[string]any{
1074 "date": []string{dateHandler, "date"},
1075 })
1076
1077 c1 := filepath.Join("content", "section", "2012-02-21-noslug.md")
1078 c2 := filepath.Join("content", "section", "2012-02-22-slug.md")
1079
1080 writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, ""))
1081 writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug"))
1082
1083 c1fi, err := fs.Source.Stat(c1)
1084 c.Assert(err, qt.IsNil)
1085 c2fi, err := fs.Source.Stat(c2)
1086 c.Assert(err, qt.IsNil)
1087
1088 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
1089 b.Build(BuildCfg{SkipRender: true})
1090
1091 s := b.H.Sites[0]
1092 c.Assert(len(s.RegularPages()), qt.Equals, 2)
1093
1094 noSlug := s.RegularPages()[0]
1095 slug := s.RegularPages()[1]
1096
1097 c.Assert(noSlug.Lastmod().Day(), qt.Equals, 28)
1098
1099 switch strings.ToLower(dateHandler) {
1100 case ":filename":
1101 c.Assert(noSlug.Date().IsZero(), qt.Equals, false)
1102 c.Assert(slug.Date().IsZero(), qt.Equals, false)
1103 c.Assert(noSlug.Date().Year(), qt.Equals, 2012)
1104 c.Assert(slug.Date().Year(), qt.Equals, 2012)
1105 c.Assert(noSlug.Slug(), qt.Equals, "noslug")
1106 c.Assert(slug.Slug(), qt.Equals, "aslug")
1107 case ":filemodtime":
1108 c.Assert(noSlug.Date().Year(), qt.Equals, c1fi.ModTime().Year())
1109 c.Assert(slug.Date().Year(), qt.Equals, c2fi.ModTime().Year())
1110 fallthrough
1111 default:
1112 c.Assert(noSlug.Slug(), qt.Equals, "")
1113 c.Assert(slug.Slug(), qt.Equals, "aslug")
1114
1115 }
1116 })
1117 }
1118 }
1119
1120 func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
1121 t.Parallel()
1122 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
1123 p := pages[0]
1124 if p.WordCount() != 8 {
1125 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 8, p.WordCount())
1126 }
1127 }
1128
1129 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes)
1130 }
1131
1132 func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
1133 t.Parallel()
1134 settings := map[string]any{"hasCJKLanguage": true}
1135
1136 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
1137 p := pages[0]
1138 if p.WordCount() != 15 {
1139 t.Fatalf("[%s] incorrect word count, expected %v, got %v", ext, 15, p.WordCount())
1140 }
1141 }
1142 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes)
1143 }
1144
1145 func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
1146 t.Parallel()
1147 settings := map[string]any{"hasCJKLanguage": true}
1148
1149 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
1150 p := pages[0]
1151 if p.WordCount() != 74 {
1152 t.Fatalf("[%s] incorrect word count, expected %v, got %v", ext, 74, p.WordCount())
1153 }
1154
1155 if p.Summary() != simplePageWithMainEnglishWithCJKRunesSummary {
1156 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.Plain(),
1157 simplePageWithMainEnglishWithCJKRunesSummary, p.Summary())
1158 }
1159 }
1160
1161 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes)
1162 }
1163
1164 func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
1165 t.Parallel()
1166 settings := map[string]any{
1167 "hasCJKLanguage": true,
1168 }
1169
1170 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
1171 p := pages[0]
1172 if p.WordCount() != 75 {
1173 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.Plain(), 74, p.WordCount())
1174 }
1175
1176 if p.Summary() != simplePageWithIsCJKLanguageFalseSummary {
1177 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.Plain(),
1178 simplePageWithIsCJKLanguageFalseSummary, p.Summary())
1179 }
1180 }
1181
1182 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse)
1183 }
1184
1185 func TestWordCount(t *testing.T) {
1186 t.Parallel()
1187 assertFunc := func(t *testing.T, ext string, pages page.Pages) {
1188 p := pages[0]
1189 if p.WordCount() != 483 {
1190 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount())
1191 }
1192
1193 if p.FuzzyWordCount() != 500 {
1194 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.FuzzyWordCount())
1195 }
1196
1197 if p.ReadingTime() != 3 {
1198 t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime())
1199 }
1200 }
1201
1202 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent)
1203 }
1204
1205 func TestPagePaths(t *testing.T) {
1206 t.Parallel()
1207 c := qt.New(t)
1208
1209 siteParmalinksSetting := map[string]string{
1210 "post": ":year/:month/:day/:title/",
1211 }
1212
1213 tests := []struct {
1214 content string
1215 path string
1216 hasPermalink bool
1217 expected string
1218 }{
1219 {simplePage, "post/x.md", false, "post/x.html"},
1220 {simplePageWithURL, "post/x.md", false, "simple/url/index.html"},
1221 {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"},
1222 {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"},
1223 {UTF8Page, "post/x.md", false, "post/x.html"},
1224 {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"},
1225 {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"},
1226 {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"},
1227 }
1228
1229 for _, test := range tests {
1230 cfg, fs := newTestCfg()
1231
1232 if test.hasPermalink {
1233 cfg.Set("permalinks", siteParmalinksSetting)
1234 }
1235
1236 writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content)
1237
1238 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
1239 c.Assert(len(s.RegularPages()), qt.Equals, 1)
1240
1241 }
1242 }
1243
1244 func TestTranslationKey(t *testing.T) {
1245 t.Parallel()
1246 c := qt.New(t)
1247 cfg, fs := newTestCfg()
1248
1249 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n")
1250 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n")
1251
1252 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
1253
1254 c.Assert(len(s.RegularPages()), qt.Equals, 2)
1255
1256 home := s.Info.Home()
1257 c.Assert(home, qt.Not(qt.IsNil))
1258 c.Assert(home.TranslationKey(), qt.Equals, "home")
1259 c.Assert(s.RegularPages()[0].TranslationKey(), qt.Equals, "page/k1")
1260 p2 := s.RegularPages()[1]
1261
1262 c.Assert(p2.TranslationKey(), qt.Equals, "page/sect/simple")
1263 }
1264
1265 func TestChompBOM(t *testing.T) {
1266 t.Parallel()
1267 c := qt.New(t)
1268 const utf8BOM = "\xef\xbb\xbf"
1269
1270 cfg, fs := newTestCfg()
1271
1272 writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
1273
1274 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
1275
1276 c.Assert(len(s.RegularPages()), qt.Equals, 1)
1277
1278 p := s.RegularPages()[0]
1279
1280 checkPageTitle(t, p, "Simple")
1281 }
1282
1283 func TestPageWithEmoji(t *testing.T) {
1284 for _, enableEmoji := range []bool{true, false} {
1285 v := config.NewWithTestDefaults()
1286 v.Set("enableEmoji", enableEmoji)
1287
1288 b := newTestSitesBuilder(t).WithViper(v)
1289
1290 b.WithContent("page-emoji.md", `---
1291 title: "Hugo Smile"
1292 ---
1293 This is a :smile:.
1294 <!--more-->
1295
1296 Another :smile: This is :not: :an: :emoji:.
1297
1298 O :christmas_tree:
1299
1300 Write me an :e-mail: or :email:?
1301
1302 Too many colons: :: ::: :::: :?: :!: :.:
1303
1304 If you dislike this video, you can hit that :-1: button :stuck_out_tongue_winking_eye:,
1305 but if you like it, hit :+1: and get subscribed!
1306 `)
1307
1308 b.CreateSites().Build(BuildCfg{})
1309
1310 if enableEmoji {
1311 b.AssertFileContent("public/page-emoji/index.html",
1312 "This is a 😄",
1313 "Another 😄",
1314 "This is :not: :an: :emoji:.",
1315 "O 🎄",
1316 "Write me an 📧 or ✉️?",
1317 "Too many colons: :: ::: :::: :?: :!: :.:",
1318 "you can hit that 👎 button 😜,",
1319 "hit 👍 and get subscribed!",
1320 )
1321 } else {
1322 b.AssertFileContent("public/page-emoji/index.html",
1323 "This is a :smile:",
1324 "Another :smile:",
1325 "This is :not: :an: :emoji:.",
1326 "O :christmas_tree:",
1327 "Write me an :e-mail: or :email:?",
1328 "Too many colons: :: ::: :::: :?: :!: :.:",
1329 "you can hit that :-1: button :stuck_out_tongue_winking_eye:,",
1330 "hit :+1: and get subscribed!",
1331 )
1332 }
1333
1334 }
1335 }
1336
1337 func TestPageHTMLContent(t *testing.T) {
1338 b := newTestSitesBuilder(t)
1339 b.WithSimpleConfigFile()
1340
1341 frontmatter := `---
1342 title: "HTML Content"
1343 ---
1344 `
1345 b.WithContent("regular.html", frontmatter+`<h1>Hugo</h1>`)
1346 b.WithContent("nomarkdownforyou.html", frontmatter+`**Hugo!**`)
1347 b.WithContent("manualsummary.html", frontmatter+`
1348 <p>This is summary</p>
1349 <!--more-->
1350 <p>This is the main content.</p>`)
1351
1352 b.Build(BuildCfg{})
1353
1354 b.AssertFileContent(
1355 "public/regular/index.html",
1356 "Single: HTML Content|Hello|en|RelPermalink: /regular/|",
1357 "Summary: Hugo|Truncated: false")
1358
1359 b.AssertFileContent(
1360 "public/nomarkdownforyou/index.html",
1361 "Permalink: http://example.com/nomarkdownforyou/|**Hugo!**|",
1362 )
1363
1364 // https://github.com/gohugoio/hugo/issues/5723
1365 b.AssertFileContent(
1366 "public/manualsummary/index.html",
1367 "Single: HTML Content|Hello|en|RelPermalink: /manualsummary/|",
1368 "Summary: \n<p>This is summary</p>\n|Truncated: true",
1369 "|<p>This is the main content.</p>|",
1370 )
1371 }
1372
1373 // https://github.com/gohugoio/hugo/issues/5381
1374 func TestPageManualSummary(t *testing.T) {
1375 b := newTestSitesBuilder(t)
1376 b.WithSimpleConfigFile()
1377
1378 b.WithContent("page-md-shortcode.md", `---
1379 title: "Hugo"
1380 ---
1381 This is a {{< sc >}}.
1382 <!--more-->
1383 Content.
1384 `)
1385
1386 // https://github.com/gohugoio/hugo/issues/5464
1387 b.WithContent("page-md-only-shortcode.md", `---
1388 title: "Hugo"
1389 ---
1390 {{< sc >}}
1391 <!--more-->
1392 {{< sc >}}
1393 `)
1394
1395 b.WithContent("page-md-shortcode-same-line.md", `---
1396 title: "Hugo"
1397 ---
1398 This is a {{< sc >}}<!--more-->Same line.
1399 `)
1400
1401 b.WithContent("page-md-shortcode-same-line-after.md", `---
1402 title: "Hugo"
1403 ---
1404 Summary<!--more-->{{< sc >}}
1405 `)
1406
1407 b.WithContent("page-org-shortcode.org", `#+TITLE: T1
1408 #+AUTHOR: A1
1409 #+DESCRIPTION: D1
1410 This is a {{< sc >}}.
1411 # more
1412 Content.
1413 `)
1414
1415 b.WithContent("page-org-variant1.org", `#+TITLE: T1
1416 Summary.
1417
1418 # more
1419
1420 Content.
1421 `)
1422
1423 b.WithTemplatesAdded("layouts/shortcodes/sc.html", "a shortcode")
1424 b.WithTemplatesAdded("layouts/_default/single.html", `
1425 SUMMARY:{{ .Summary }}:END
1426 --------------------------
1427 CONTENT:{{ .Content }}
1428 `)
1429
1430 b.CreateSites().Build(BuildCfg{})
1431
1432 b.AssertFileContent("public/page-md-shortcode/index.html",
1433 "SUMMARY:<p>This is a a shortcode.</p>:END",
1434 "CONTENT:<p>This is a a shortcode.</p>\n\n<p>Content.</p>\n",
1435 )
1436
1437 b.AssertFileContent("public/page-md-shortcode-same-line/index.html",
1438 "SUMMARY:<p>This is a a shortcode</p>:END",
1439 "CONTENT:<p>This is a a shortcode</p>\n\n<p>Same line.</p>\n",
1440 )
1441
1442 b.AssertFileContent("public/page-md-shortcode-same-line-after/index.html",
1443 "SUMMARY:<p>Summary</p>:END",
1444 "CONTENT:<p>Summary</p>\n\na shortcode",
1445 )
1446
1447 b.AssertFileContent("public/page-org-shortcode/index.html",
1448 "SUMMARY:<p>\nThis is a a shortcode.\n</p>:END",
1449 "CONTENT:<p>\nThis is a a shortcode.\n</p>\n<p>\nContent.\t\n</p>\n",
1450 )
1451 b.AssertFileContent("public/page-org-variant1/index.html",
1452 "SUMMARY:<p>\nSummary.\n</p>:END",
1453 "CONTENT:<p>\nSummary.\n</p>\n<p>\nContent.\t\n</p>\n",
1454 )
1455
1456 b.AssertFileContent("public/page-md-only-shortcode/index.html",
1457 "SUMMARY:a shortcode:END",
1458 "CONTENT:a shortcode\n\na shortcode\n",
1459 )
1460 }
1461
1462 // https://github.com/gohugoio/hugo/issues/5478
1463 func TestPageWithCommentedOutFrontMatter(t *testing.T) {
1464 b := newTestSitesBuilder(t)
1465 b.WithSimpleConfigFile()
1466
1467 b.WithContent("page.md", `<!--
1468 +++
1469 title = "hello"
1470 +++
1471 -->
1472 This is the content.
1473 `)
1474
1475 b.WithTemplatesAdded("layouts/_default/single.html", `
1476 Title: {{ .Title }}
1477 Content:{{ .Content }}
1478 `)
1479
1480 b.CreateSites().Build(BuildCfg{})
1481
1482 b.AssertFileContent("public/page/index.html",
1483 "Title: hello",
1484 "Content:<p>This is the content.</p>",
1485 )
1486 }
1487
1488 // https://github.com/gohugoio/hugo/issues/5781
1489 func TestPageWithZeroFile(t *testing.T) {
1490 newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger()).WithSimpleConfigFile().
1491 WithTemplatesAdded("index.html", "{{ .File.Filename }}{{ with .File }}{{ .Dir }}{{ end }}").Build(BuildCfg{})
1492 }
1493
1494 func TestHomePageWithNoTitle(t *testing.T) {
1495 b := newTestSitesBuilder(t).WithConfigFile("toml", `
1496 title = "Site Title"
1497 `)
1498 b.WithTemplatesAdded("index.html", "Title|{{ with .Title }}{{ . }}{{ end }}|")
1499 b.WithContent("_index.md", `---
1500 description: "No title for you!"
1501 ---
1502
1503 Content.
1504 `)
1505
1506 b.Build(BuildCfg{})
1507 b.AssertFileContent("public/index.html", "Title||")
1508 }
1509
1510 func TestShouldBuild(t *testing.T) {
1511 past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
1512 future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
1513 zero := time.Time{}
1514
1515 publishSettings := []struct {
1516 buildFuture bool
1517 buildExpired bool
1518 buildDrafts bool
1519 draft bool
1520 publishDate time.Time
1521 expiryDate time.Time
1522 out bool
1523 }{
1524 // publishDate and expiryDate
1525 {false, false, false, false, zero, zero, true},
1526 {false, false, false, false, zero, future, true},
1527 {false, false, false, false, past, zero, true},
1528 {false, false, false, false, past, future, true},
1529 {false, false, false, false, past, past, false},
1530 {false, false, false, false, future, future, false},
1531 {false, false, false, false, future, past, false},
1532
1533 // buildFuture and buildExpired
1534 {false, true, false, false, past, past, true},
1535 {true, true, false, false, past, past, true},
1536 {true, false, false, false, past, past, false},
1537 {true, false, false, false, future, future, true},
1538 {true, true, false, false, future, future, true},
1539 {false, true, false, false, future, past, false},
1540
1541 // buildDrafts and draft
1542 {true, true, false, true, past, future, false},
1543 {true, true, true, true, past, future, true},
1544 {true, true, true, true, past, future, true},
1545 }
1546
1547 for _, ps := range publishSettings {
1548 s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
1549 ps.publishDate, ps.expiryDate)
1550 if s != ps.out {
1551 t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps)
1552 }
1553 }
1554 }
1555
1556 func TestShouldBuildWithClock(t *testing.T) {
1557 htime.Clock = clock.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
1558 t.Cleanup(func() { htime.Clock = clock.System() })
1559 past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
1560 future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
1561 zero := time.Time{}
1562
1563 publishSettings := []struct {
1564 buildFuture bool
1565 buildExpired bool
1566 buildDrafts bool
1567 draft bool
1568 publishDate time.Time
1569 expiryDate time.Time
1570 out bool
1571 }{
1572 // publishDate and expiryDate
1573 {false, false, false, false, zero, zero, true},
1574 {false, false, false, false, zero, future, true},
1575 {false, false, false, false, past, zero, true},
1576 {false, false, false, false, past, future, true},
1577 {false, false, false, false, past, past, false},
1578 {false, false, false, false, future, future, false},
1579 {false, false, false, false, future, past, false},
1580
1581 // buildFuture and buildExpired
1582 {false, true, false, false, past, past, true},
1583 {true, true, false, false, past, past, true},
1584 {true, false, false, false, past, past, false},
1585 {true, false, false, false, future, future, true},
1586 {true, true, false, false, future, future, true},
1587 {false, true, false, false, future, past, false},
1588
1589 // buildDrafts and draft
1590 {true, true, false, true, past, future, false},
1591 {true, true, true, true, past, future, true},
1592 {true, true, true, true, past, future, true},
1593 }
1594
1595 for _, ps := range publishSettings {
1596 s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
1597 ps.publishDate, ps.expiryDate)
1598 if s != ps.out {
1599 t.Errorf("AssertShouldBuildWithClock unexpected output with params: %+v", ps)
1600 }
1601 }
1602 }
1603
1604 // "dot" in path: #1885 and #2110
1605 // disablePathToLower regression: #3374
1606 func TestPathIssues(t *testing.T) {
1607 for _, disablePathToLower := range []bool{false, true} {
1608 for _, uglyURLs := range []bool{false, true} {
1609 disablePathToLower := disablePathToLower
1610 uglyURLs := uglyURLs
1611 t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) {
1612 t.Parallel()
1613 cfg, fs := newTestCfg()
1614 th := newTestHelper(cfg, fs, t)
1615 c := qt.New(t)
1616
1617 cfg.Set("permalinks", map[string]string{
1618 "post": ":section/:title",
1619 })
1620
1621 cfg.Set("uglyURLs", uglyURLs)
1622 cfg.Set("disablePathToLower", disablePathToLower)
1623 cfg.Set("paginate", 1)
1624
1625 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
1626 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
1627 "<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>")
1628
1629 for i := 0; i < 3; i++ {
1630 writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)),
1631 fmt.Sprintf(`---
1632 title: "test%d.dot"
1633 tags:
1634 - ".net"
1635 ---
1636 # doc1
1637 *some content*`, i))
1638 }
1639
1640 writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"),
1641 fmt.Sprintf(`---
1642 title: "testBlog"
1643 tags:
1644 - "Blog"
1645 ---
1646 # doc1
1647 *some blog content*`))
1648
1649 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
1650
1651 c.Assert(len(s.RegularPages()), qt.Equals, 4)
1652
1653 pathFunc := func(s string) string {
1654 if uglyURLs {
1655 return strings.Replace(s, "/index.html", ".html", 1)
1656 }
1657 return s
1658 }
1659
1660 blog := "blog"
1661
1662 if disablePathToLower {
1663 blog = "Blog"
1664 }
1665
1666 th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content")
1667
1668 th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content")
1669
1670 if uglyURLs {
1671 th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"`)
1672 th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`)
1673 th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`)
1674 } else {
1675 th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"`)
1676 th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`)
1677 th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`)
1678 th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`)
1679
1680 }
1681
1682 p := s.RegularPages()[0]
1683 if uglyURLs {
1684 c.Assert(p.RelPermalink(), qt.Equals, "/post/test0.dot.html")
1685 } else {
1686 c.Assert(p.RelPermalink(), qt.Equals, "/post/test0.dot/")
1687 }
1688 })
1689 }
1690 }
1691 }
1692
1693 // https://github.com/gohugoio/hugo/issues/4675
1694 func TestWordCountAndSimilarVsSummary(t *testing.T) {
1695 t.Parallel()
1696 c := qt.New(t)
1697
1698 single := []string{"_default/single.html", `
1699 WordCount: {{ .WordCount }}
1700 FuzzyWordCount: {{ .FuzzyWordCount }}
1701 ReadingTime: {{ .ReadingTime }}
1702 Len Plain: {{ len .Plain }}
1703 Len PlainWords: {{ len .PlainWords }}
1704 Truncated: {{ .Truncated }}
1705 Len Summary: {{ len .Summary }}
1706 Len Content: {{ len .Content }}
1707
1708 SUMMARY:{{ .Summary }}:{{ len .Summary }}:END
1709
1710 `}
1711
1712 b := newTestSitesBuilder(t)
1713 b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`---
1714 title: p1
1715 ---
1716
1717 %s
1718
1719 `, strings.Repeat("word ", 510)),
1720
1721 "p2.md", fmt.Sprintf(`---
1722 title: p2
1723 ---
1724 This is a summary.
1725
1726 <!--more-->
1727
1728 %s
1729
1730 `, strings.Repeat("word ", 310)),
1731 "p3.md", fmt.Sprintf(`---
1732 title: p3
1733 isCJKLanguage: true
1734 ---
1735 Summary: In Chinese, 好 means good.
1736
1737 <!--more-->
1738
1739 %s
1740
1741 `, strings.Repeat("好", 200)),
1742 "p4.md", fmt.Sprintf(`---
1743 title: p4
1744 isCJKLanguage: false
1745 ---
1746 Summary: In Chinese, 好 means good.
1747
1748 <!--more-->
1749
1750 %s
1751
1752 `, strings.Repeat("好", 200)),
1753
1754 "p5.md", fmt.Sprintf(`---
1755 title: p4
1756 isCJKLanguage: true
1757 ---
1758 Summary: In Chinese, 好 means good.
1759
1760 %s
1761
1762 `, strings.Repeat("好", 200)),
1763 "p6.md", fmt.Sprintf(`---
1764 title: p4
1765 isCJKLanguage: false
1766 ---
1767 Summary: In Chinese, 好 means good.
1768
1769 %s
1770
1771 `, strings.Repeat("好", 200)),
1772 )
1773
1774 b.CreateSites().Build(BuildCfg{})
1775
1776 c.Assert(len(b.H.Sites), qt.Equals, 1)
1777 c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 6)
1778
1779 b.AssertFileContent("public/p1/index.html", "WordCount: 510\nFuzzyWordCount: 600\nReadingTime: 3\nLen Plain: 2550\nLen PlainWords: 510\nTruncated: false\nLen Summary: 2549\nLen Content: 2557")
1780
1781 b.AssertFileContent("public/p2/index.html", "WordCount: 314\nFuzzyWordCount: 400\nReadingTime: 2\nLen Plain: 1569\nLen PlainWords: 314\nTruncated: true\nLen Summary: 25\nLen Content: 1582")
1782
1783 b.AssertFileContent("public/p3/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651")
1784 b.AssertFileContent("public/p4/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651")
1785 b.AssertFileContent("public/p5/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 229\nLen Content: 652")
1786 b.AssertFileContent("public/p6/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: false\nLen Summary: 637\nLen Content: 652")
1787 }
1788
1789 func TestScratch(t *testing.T) {
1790 t.Parallel()
1791
1792 b := newTestSitesBuilder(t)
1793 b.WithSimpleConfigFile().WithTemplatesAdded("index.html", `
1794 {{ .Scratch.Set "b" "bv" }}
1795 B: {{ .Scratch.Get "b" }}
1796 `,
1797 "shortcodes/scratch.html", `
1798 {{ .Scratch.Set "c" "cv" }}
1799 C: {{ .Scratch.Get "c" }}
1800 `,
1801 )
1802
1803 b.WithContentAdded("scratchme.md", `
1804 ---
1805 title: Scratch Me!
1806 ---
1807
1808 {{< scratch >}}
1809 `)
1810 b.Build(BuildCfg{})
1811
1812 b.AssertFileContent("public/index.html", "B: bv")
1813 b.AssertFileContent("public/scratchme/index.html", "C: cv")
1814 }
1815
1816 func TestScratchRebuild(t *testing.T) {
1817 t.Parallel()
1818
1819 files := `
1820 -- config.toml --
1821 -- content/p1.md --
1822 ---
1823 title: "p1"
1824 ---
1825 {{< scratchme >}}
1826 -- layouts/shortcodes/foo.html --
1827 notused
1828 -- layouts/shortcodes/scratchme.html --
1829 {{ .Page.Scratch.Set "scratch" "foo" }}
1830 {{ .Page.Store.Set "scratch" "bar" }}
1831 -- layouts/_default/single.html --
1832 {{ .Content }}
1833 Scratch: {{ .Scratch.Get "scratch" }}|
1834 Store: {{ .Store.Get "scratch" }}|
1835 `
1836
1837 b := NewIntegrationTestBuilder(
1838 IntegrationTestConfig{
1839 T: t,
1840 TxtarString: files,
1841 Running: true,
1842 },
1843 ).Build()
1844
1845 b.AssertFileContent("public/p1/index.html", `
1846 Scratch: foo|
1847 Store: bar|
1848 `)
1849
1850 b.EditFiles("layouts/shortcodes/foo.html", "edit")
1851
1852 b.Build()
1853
1854 b.AssertFileContent("public/p1/index.html", `
1855 Scratch: |
1856 Store: bar|
1857 `)
1858 }
1859
1860 func TestPageParam(t *testing.T) {
1861 t.Parallel()
1862
1863 b := newTestSitesBuilder(t).WithConfigFile("toml", `
1864
1865 baseURL = "https://example.org"
1866
1867 [params]
1868 [params.author]
1869 name = "Kurt Vonnegut"
1870
1871 `)
1872 b.WithTemplatesAdded("index.html", `
1873
1874 {{ $withParam := .Site.GetPage "withparam" }}
1875 {{ $noParam := .Site.GetPage "noparam" }}
1876 {{ $withStringParam := .Site.GetPage "withstringparam" }}
1877
1878 Author page: {{ $withParam.Param "author.name" }}
1879 Author name page string: {{ $withStringParam.Param "author.name" }}|
1880 Author page string: {{ $withStringParam.Param "author" }}|
1881 Author site config: {{ $noParam.Param "author.name" }}
1882
1883 `,
1884 )
1885
1886 b.WithContent("withparam.md", `
1887 +++
1888 title = "With Param!"
1889 [author]
1890 name = "Ernest Miller Hemingway"
1891
1892 +++
1893
1894 `,
1895
1896 "noparam.md", `
1897 ---
1898 title: "No Param!"
1899 ---
1900 `, "withstringparam.md", `
1901 +++
1902 title = "With string Param!"
1903 author = "Jo Nesbø"
1904
1905 +++
1906
1907 `)
1908 b.Build(BuildCfg{})
1909
1910 b.AssertFileContent("public/index.html",
1911 "Author page: Ernest Miller Hemingway",
1912 "Author name page string: Kurt Vonnegut|",
1913 "Author page string: Jo Nesbø|",
1914 "Author site config: Kurt Vonnegut")
1915 }
1916
1917 func TestGoldmark(t *testing.T) {
1918 t.Parallel()
1919
1920 b := newTestSitesBuilder(t).WithConfigFile("toml", `
1921 baseURL = "https://example.org"
1922
1923 [markup]
1924 defaultMarkdownHandler="goldmark"
1925 [markup.goldmark]
1926 [markup.goldmark.renderer]
1927 unsafe = false
1928 [markup.highlight]
1929 noClasses=false
1930
1931
1932 `)
1933 b.WithTemplatesAdded("_default/single.html", `
1934 Title: {{ .Title }}
1935 ToC: {{ .TableOfContents }}
1936 Content: {{ .Content }}
1937
1938 `, "shortcodes/t.html", `T-SHORT`, "shortcodes/s.html", `## Code
1939 {{ .Inner }}
1940 `)
1941
1942 content := `
1943 +++
1944 title = "A Page!"
1945 +++
1946
1947 ## Shortcode {{% t %}} in header
1948
1949 ## Code Fense in Shortcode
1950
1951 {{% s %}}
1952 $$$bash {hl_lines=[1]}
1953 SHORT
1954 $$$
1955 {{% /s %}}
1956
1957 ## Code Fence
1958
1959 $$$bash {hl_lines=[1]}
1960 MARKDOWN
1961 $$$
1962
1963 Link with URL as text
1964
1965 [https://google.com](https://google.com)
1966
1967
1968 `
1969 content = strings.ReplaceAll(content, "$$$", "```")
1970
1971 b.WithContent("page.md", content)
1972
1973 b.Build(BuildCfg{})
1974
1975 b.AssertFileContent("public/page/index.html",
1976 `<nav id="TableOfContents">
1977 <li><a href="#shortcode-t-short-in-header">Shortcode T-SHORT in header</a></li>
1978 <code class="language-bash" data-lang="bash"><span class="line hl"><span class="cl">SHORT
1979 <code class="language-bash" data-lang="bash"><span class="line hl"><span class="cl">MARKDOWN
1980 <p><a href="https://google.com">https://google.com</a></p>
1981 `)
1982 }
1983
1984 func TestPageCaseIssues(t *testing.T) {
1985 t.Parallel()
1986
1987 b := newTestSitesBuilder(t)
1988 b.WithConfigFile("toml", `defaultContentLanguage = "no"
1989 [languages]
1990 [languages.NO]
1991 title = "Norsk"
1992 `)
1993 b.WithContent("a/B/C/Page1.md", "---\ntitle: Page1\n---")
1994 b.WithTemplates("index.html", `
1995 {{ $p1 := site.GetPage "a/B/C/Page1" }}
1996 Lang: {{ .Lang }}
1997 Page1: {{ $p1.Path }}
1998 `)
1999
2000 b.Build(BuildCfg{})
2001
2002 b.AssertFileContent("public/index.html", "Lang: no", filepath.FromSlash("Page1: a/B/C/Page1.md"))
2003 }