hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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.&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</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&ndash;as swift as the passage of light&ndash;would leap after me from the pit about the cylinder and strike me down. ## BB</p><h3 id=\"bbb\">BBB</h3> <p>&ldquo;You&rsquo;re a great Granser,&rdquo; he cried delightedly, &ldquo;always making believe them little marks mean something.&rdquo;</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 }