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