github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/hugolib/page_test.go (about)

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