github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/hugolib/page_test.go (about)

     1  // Copyright 2018 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  	"bytes"
    18  	"fmt"
    19  	"html/template"
    20  	"os"
    21  
    22  	"path/filepath"
    23  	"reflect"
    24  	"sort"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/gohugoio/hugo/hugofs"
    30  	"github.com/spf13/afero"
    31  
    32  	"github.com/spf13/viper"
    33  
    34  	"github.com/gohugoio/hugo/deps"
    35  	"github.com/gohugoio/hugo/helpers"
    36  	"github.com/spf13/cast"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  var emptyPage = ""
    42  
    43  const (
    44  	homePage                             = "---\ntitle: Home\n---\nHome Page Content\n"
    45  	simplePage                           = "---\ntitle: Simple\n---\nSimple Page\n"
    46  	renderNoFrontmatter                  = "<!doctype><html><head></head><body>This is a test</body></html>"
    47  	contentNoFrontmatter                 = "Page without front matter.\n"
    48  	contentWithCommentedFrontmatter      = "<!--\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n-->\n\n# Network configuration\n\n##\nSummary"
    49  	contentWithCommentedTextFrontmatter  = "<!--[metaData]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata]-->\n\n# Network configuration\n\n##\nSummary"
    50  	contentWithCommentedLongFrontmatter  = "<!--[metaData123456789012345678901234567890]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata]-->\n\n# Network configuration\n\n##\nSummary"
    51  	contentWithCommentedLong2Frontmatter = "<!--[metaData]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata123456789012345678901234567890]-->\n\n# Network configuration\n\n##\nSummary"
    52  	invalidFrontmatterShortDelim         = `
    53  --
    54  title: Short delim start
    55  ---
    56  Short Delim
    57  `
    58  
    59  	invalidFrontmatterShortDelimEnding = `
    60  ---
    61  title: Short delim ending
    62  --
    63  Short Delim
    64  `
    65  
    66  	invalidFrontmatterLadingWs = `
    67  
    68   ---
    69  title: Leading WS
    70  ---
    71  Leading
    72  `
    73  
    74  	simplePageJSON = `
    75  {
    76  "title": "spf13-vim 3.0 release and new website",
    77  "description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.",
    78  "tags": [ ".vimrc", "plugins", "spf13-vim", "VIm" ],
    79  "date": "2012-04-06",
    80  "categories": [
    81      "Development",
    82      "VIM"
    83  ],
    84  "slug": "-spf13-vim-3-0-release-and-new-website-"
    85  }
    86  
    87  Content of the file goes Here
    88  `
    89  
    90  	simplePageRFC3339Date  = "---\ntitle: RFC3339 Date\ndate: \"2013-05-17T16:59:30Z\"\n---\nrfc3339 content"
    91  	simplePageJSONMultiple = `
    92  {
    93  	"title": "foobar",
    94  	"customData": { "foo": "bar" },
    95  	"date": "2012-08-06"
    96  }
    97  Some text
    98  `
    99  
   100  	simplePageWithSummaryDelimiter = `---
   101  title: Simple
   102  ---
   103  Summary Next Line
   104  
   105  <!--more-->
   106  Some more text
   107  `
   108  
   109  	simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder = `---
   110  title: Simple
   111  ---
   112  The [best static site generator][hugo].[^1]
   113  <!--more-->
   114  [hugo]: http://gohugo.io/
   115  [^1]: Many people say so.
   116  `
   117  	simplePageWithShortcodeInSummary = `---
   118  title: Simple
   119  ---
   120  Summary Next Line. {{<figure src="/not/real" >}}.
   121  More text here.
   122  
   123  Some more text
   124  `
   125  
   126  	simplePageWithEmbeddedScript = `---
   127  title: Simple
   128  ---
   129  <script type='text/javascript'>alert('the script tags are still there, right?');</script>
   130  `
   131  
   132  	simplePageWithSummaryDelimiterSameLine = `---
   133  title: Simple
   134  ---
   135  Summary Same Line<!--more-->
   136  
   137  Some more text
   138  `
   139  
   140  	simplePageWithSummaryDelimiterOnlySummary = `---
   141  title: Simple
   142  ---
   143  Summary text
   144  
   145  <!--more-->
   146  `
   147  
   148  	simplePageWithAllCJKRunes = `---
   149  title: Simple
   150  ---
   151  
   152  
   153  € € € € €
   154  你好
   155  도형이
   156  カテゴリー
   157  
   158  
   159  `
   160  
   161  	simplePageWithMainEnglishWithCJKRunes = `---
   162  title: Simple
   163  ---
   164  
   165  
   166  In Chinese, 好 means good.  In Chinese, 好 means good.
   167  In Chinese, 好 means good.  In Chinese, 好 means good.
   168  In Chinese, 好 means good.  In Chinese, 好 means good.
   169  In Chinese, 好 means good.  In Chinese, 好 means good.
   170  In Chinese, 好 means good.  In Chinese, 好 means good.
   171  In Chinese, 好 means good.  In Chinese, 好 means good.
   172  In Chinese, 好 means good.  In Chinese, 好 means good.
   173  More then 70 words.
   174  
   175  
   176  `
   177  	simplePageWithMainEnglishWithCJKRunesSummary = "In Chinese, 好 means good. In Chinese, 好 means good. " +
   178  		"In Chinese, 好 means good. In Chinese, 好 means good. " +
   179  		"In Chinese, 好 means good. In Chinese, 好 means good. " +
   180  		"In Chinese, 好 means good. In Chinese, 好 means good. " +
   181  		"In Chinese, 好 means good. In Chinese, 好 means good. " +
   182  		"In Chinese, 好 means good. In Chinese, 好 means good. " +
   183  		"In Chinese, 好 means good. In Chinese, 好 means good."
   184  
   185  	simplePageWithIsCJKLanguageFalse = `---
   186  title: Simple
   187  isCJKLanguage: false
   188  ---
   189  
   190  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   191  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   192  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   193  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   194  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   195  In Chinese, 好的啊 means good.  In Chinese, 好的呀 means good.
   196  In Chinese, 好的啊 means good.  In Chinese, 好的呀呀 means good enough.
   197  More then 70 words.
   198  
   199  
   200  `
   201  	simplePageWithIsCJKLanguageFalseSummary = "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   202  		"In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   203  		"In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   204  		"In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   205  		"In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   206  		"In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " +
   207  		"In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough."
   208  
   209  	simplePageWithLongContent = `---
   210  title: Simple
   211  ---
   212  
   213  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
   214  incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
   215  nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
   216  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
   217  fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
   218  culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
   219  amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
   220  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
   221  ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
   222  in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
   223  pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
   224  officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet,
   225  consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
   226  dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
   227  laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
   228  reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
   229  Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
   230  deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur
   231  adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
   232  aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
   233  ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
   234  voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
   235  occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
   236  id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
   237  do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
   238  veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
   239  consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
   240  cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
   241  proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem
   242  ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
   243  incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
   244  nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
   245  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
   246  fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
   247  culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
   248  amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
   249  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
   250  ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
   251  in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
   252  pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
   253  officia deserunt mollit anim id est laborum.`
   254  
   255  	pageWithToC = `---
   256  title: TOC
   257  ---
   258  For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
   259  
   260  ## AA
   261  
   262  I have no idea, of course, how long it took me to reach the limit of the plain,
   263  but at last I entered the foothills, following a pretty little canyon upward
   264  toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon
   265  its noisy way down to the silent sea. In its quieter pools I discovered many
   266  small fish, of four-or five-pound weight I should imagine. In appearance,
   267  except as to size and color, they were not unlike the whale of our own seas. As
   268  I watched them playing about I discovered, not only that they suckled their
   269  young, but that at intervals they rose to the surface to breathe as well as to
   270  feed upon certain grasses and a strange, scarlet lichen which grew upon the
   271  rocks just above the water line.
   272  
   273  ### AAA
   274  
   275  I remember I felt an extraordinary persuasion that I was being played with,
   276  that presently, when I was upon the very verge of safety, this mysterious
   277  death--as swift as the passage of light--would leap after me from the pit about
   278  the cylinder and strike me down. ## BB
   279  
   280  ### BBB
   281  
   282  "You're a great Granser," he cried delightedly, "always making believe them little marks mean something."
   283  `
   284  
   285  	simplePageWithAdditionalExtension = `+++
   286  [blackfriday]
   287    extensions = ["hardLineBreak"]
   288  +++
   289  first line.
   290  second line.
   291  
   292  fourth line.
   293  `
   294  
   295  	simplePageWithURL = `---
   296  title: Simple
   297  url: simple/url/
   298  ---
   299  Simple Page With URL`
   300  
   301  	simplePageWithSlug = `---
   302  title: Simple
   303  slug: simple-slug
   304  ---
   305  Simple Page With Slug`
   306  
   307  	simplePageWithDate = `---
   308  title: Simple
   309  date: '2013-10-15T06:16:13'
   310  ---
   311  Simple Page With Date`
   312  
   313  	UTF8Page = `---
   314  title: ラーメン
   315  ---
   316  UTF8 Page`
   317  
   318  	UTF8PageWithURL = `---
   319  title: ラーメン
   320  url: ラーメン/url/
   321  ---
   322  UTF8 Page With URL`
   323  
   324  	UTF8PageWithSlug = `---
   325  title: ラーメン
   326  slug: ラーメン-slug
   327  ---
   328  UTF8 Page With Slug`
   329  
   330  	UTF8PageWithDate = `---
   331  title: ラーメン
   332  date: '2013-10-15T06:16:13'
   333  ---
   334  UTF8 Page With Date`
   335  )
   336  
   337  var pageWithVariousFrontmatterTypes = `+++
   338  a_string = "bar"
   339  an_integer = 1
   340  a_float = 1.3
   341  a_bool = false
   342  a_date = 1979-05-27T07:32:00Z
   343  
   344  [a_table]
   345  a_key = "a_value"
   346  +++
   347  Front Matter with various frontmatter types`
   348  
   349  var pageWithCalendarYAMLFrontmatter = `---
   350  type: calendar
   351  weeks:
   352    -
   353      start: "Jan 5"
   354      days:
   355        - activity: class
   356          room: EN1000
   357        - activity: lab
   358        - activity: class
   359        - activity: lab
   360        - activity: class
   361    -
   362      start: "Jan 12"
   363      days:
   364        - activity: class
   365        - activity: lab
   366        - activity: class
   367        - activity: lab
   368        - activity: exam
   369  ---
   370  
   371  Hi.
   372  `
   373  
   374  var pageWithCalendarJSONFrontmatter = `{
   375    "type": "calendar",
   376    "weeks": [
   377      {
   378        "start": "Jan 5",
   379        "days": [
   380          { "activity": "class", "room": "EN1000" },
   381          { "activity": "lab" },
   382          { "activity": "class" },
   383          { "activity": "lab" },
   384          { "activity": "class" }
   385        ]
   386      },
   387      {
   388        "start": "Jan 12",
   389        "days": [
   390          { "activity": "class" },
   391          { "activity": "lab" },
   392          { "activity": "class" },
   393          { "activity": "lab" },
   394          { "activity": "exam" }
   395        ]
   396      }
   397    ]
   398  }
   399  
   400  Hi.
   401  `
   402  
   403  var pageWithCalendarTOMLFrontmatter = `+++
   404  type = "calendar"
   405  
   406  [[weeks]]
   407  start = "Jan 5"
   408  
   409  [[weeks.days]]
   410  activity = "class"
   411  room = "EN1000"
   412  
   413  [[weeks.days]]
   414  activity = "lab"
   415  
   416  [[weeks.days]]
   417  activity = "class"
   418  
   419  [[weeks.days]]
   420  activity = "lab"
   421  
   422  [[weeks.days]]
   423  activity = "class"
   424  
   425  [[weeks]]
   426  start = "Jan 12"
   427  
   428  [[weeks.days]]
   429  activity = "class"
   430  
   431  [[weeks.days]]
   432  activity = "lab"
   433  
   434  [[weeks.days]]
   435  activity = "class"
   436  
   437  [[weeks.days]]
   438  activity = "lab"
   439  
   440  [[weeks.days]]
   441  activity = "exam"
   442  +++
   443  
   444  Hi.
   445  `
   446  
   447  func checkError(t *testing.T, err error, expected string) {
   448  	if err == nil {
   449  		t.Fatalf("err is nil.  Expected: %s", expected)
   450  	}
   451  	if !strings.Contains(err.Error(), expected) {
   452  		t.Errorf("err.Error() returned: '%s'.  Expected: '%s'", err.Error(), expected)
   453  	}
   454  }
   455  
   456  func TestDegenerateEmptyPageZeroLengthName(t *testing.T) {
   457  	t.Parallel()
   458  	s := newTestSite(t)
   459  	_, err := s.NewPage("")
   460  	if err == nil {
   461  		t.Fatalf("A zero length page name must return an error")
   462  	}
   463  
   464  	checkError(t, err, "Zero length page name")
   465  }
   466  
   467  func TestDegenerateEmptyPage(t *testing.T) {
   468  	t.Parallel()
   469  	s := newTestSite(t)
   470  	_, err := s.NewPageFrom(strings.NewReader(emptyPage), "test")
   471  	if err != nil {
   472  		t.Fatalf("Empty files should not trigger an error. Should be able to touch a file while watching without erroring out.")
   473  	}
   474  }
   475  
   476  func checkPageTitle(t *testing.T, page *Page, title string) {
   477  	if page.title != title {
   478  		t.Fatalf("Page title is: %s.  Expected %s", page.title, title)
   479  	}
   480  }
   481  
   482  func checkPageContent(t *testing.T, page *Page, content string, msg ...interface{}) {
   483  	a := normalizeContent(content)
   484  	b := normalizeContent(string(page.content()))
   485  	if a != b {
   486  		t.Fatalf("Page content is:\n%q\nExpected:\n%q (%q)", b, a, msg)
   487  	}
   488  }
   489  
   490  func normalizeContent(c string) string {
   491  	norm := c
   492  	norm = strings.Replace(norm, "\n", " ", -1)
   493  	norm = strings.Replace(norm, "    ", " ", -1)
   494  	norm = strings.Replace(norm, "   ", " ", -1)
   495  	norm = strings.Replace(norm, "  ", " ", -1)
   496  	norm = strings.Replace(norm, "p> ", "p>", -1)
   497  	norm = strings.Replace(norm, ">  <", "> <", -1)
   498  	return strings.TrimSpace(norm)
   499  }
   500  
   501  func checkPageTOC(t *testing.T, page *Page, toc string) {
   502  	if page.TableOfContents != template.HTML(toc) {
   503  		t.Fatalf("Page TableOfContents is: %q.\nExpected %q", page.TableOfContents, toc)
   504  	}
   505  }
   506  
   507  func checkPageSummary(t *testing.T, page *Page, summary string, msg ...interface{}) {
   508  	a := normalizeContent(string(page.summary))
   509  	b := normalizeContent(summary)
   510  	if a != b {
   511  		t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg)
   512  	}
   513  }
   514  
   515  func checkPageType(t *testing.T, page *Page, pageType string) {
   516  	if page.Type() != pageType {
   517  		t.Fatalf("Page type is: %s.  Expected: %s", page.Type(), pageType)
   518  	}
   519  }
   520  
   521  func checkPageDate(t *testing.T, page *Page, time time.Time) {
   522  	if page.Date != time {
   523  		t.Fatalf("Page date is: %s.  Expected: %s", page.Date, time)
   524  	}
   525  }
   526  
   527  func checkTruncation(t *testing.T, page *Page, shouldBe bool, msg string) {
   528  	if page.Summary() == "" {
   529  		t.Fatal("page has no summary, can not check truncation")
   530  	}
   531  	if page.truncated != shouldBe {
   532  		if shouldBe {
   533  			t.Fatalf("page wasn't truncated: %s", msg)
   534  		} else {
   535  			t.Fatalf("page was truncated: %s", msg)
   536  		}
   537  	}
   538  }
   539  
   540  func normalizeExpected(ext, str string) string {
   541  	str = normalizeContent(str)
   542  	switch ext {
   543  	default:
   544  		return str
   545  	case "html":
   546  		return strings.Trim(helpers.StripHTML(str), " ")
   547  	case "ad":
   548  		paragraphs := strings.Split(str, "</p>")
   549  		expected := ""
   550  		for _, para := range paragraphs {
   551  			if para == "" {
   552  				continue
   553  			}
   554  			expected += fmt.Sprintf("<div class=\"paragraph\">\n%s</p></div>\n", para)
   555  		}
   556  		return expected
   557  	case "rst":
   558  		return fmt.Sprintf("<div class=\"document\">\n\n\n%s</div>", str)
   559  	}
   560  }
   561  
   562  func testAllMarkdownEnginesForPages(t *testing.T,
   563  	assertFunc func(t *testing.T, ext string, pages Pages), settings map[string]interface{}, pageSources ...string) {
   564  
   565  	engines := []struct {
   566  		ext           string
   567  		shouldExecute func() bool
   568  	}{
   569  		{"md", func() bool { return true }},
   570  		{"mmark", func() bool { return true }},
   571  		{"ad", func() bool { return helpers.HasAsciidoc() }},
   572  		// TODO(bep) figure a way to include this without too much work.{"html", func() bool { return true }},
   573  		{"rst", func() bool { return helpers.HasRst() }},
   574  	}
   575  
   576  	for _, e := range engines {
   577  		if !e.shouldExecute() {
   578  			continue
   579  		}
   580  
   581  		cfg, fs := newTestCfg()
   582  
   583  		if settings != nil {
   584  			for k, v := range settings {
   585  				cfg.Set(k, v)
   586  			}
   587  		}
   588  
   589  		contentDir := "content"
   590  
   591  		if s := cfg.GetString("contentDir"); s != "" {
   592  			contentDir = s
   593  		}
   594  
   595  		var fileSourcePairs []string
   596  
   597  		for i, source := range pageSources {
   598  			fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source)
   599  		}
   600  
   601  		for i := 0; i < len(fileSourcePairs); i += 2 {
   602  			writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
   603  		}
   604  
   605  		// Add a content page for the home page
   606  		homePath := fmt.Sprintf("_index.%s", e.ext)
   607  		writeSource(t, fs, filepath.Join(contentDir, homePath), homePage)
   608  
   609  		s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   610  
   611  		require.Len(t, s.RegularPages, len(pageSources))
   612  
   613  		assertFunc(t, e.ext, s.RegularPages)
   614  
   615  		home, err := s.Info.Home()
   616  		require.NoError(t, err)
   617  		require.NotNil(t, home)
   618  		require.Equal(t, homePath, home.Path())
   619  		require.Contains(t, home.content(), "Home Page Content")
   620  
   621  	}
   622  
   623  }
   624  
   625  func TestCreateNewPage(t *testing.T) {
   626  	t.Parallel()
   627  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   628  		p := pages[0]
   629  
   630  		// issue #2290: Path is relative to the content dir and will continue to be so.
   631  		require.Equal(t, filepath.FromSlash(fmt.Sprintf("p0.%s", ext)), p.Path())
   632  		assert.False(t, p.IsHome())
   633  		checkPageTitle(t, p, "Simple")
   634  		checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
   635  		checkPageSummary(t, p, "Simple Page")
   636  		checkPageType(t, p, "page")
   637  		checkTruncation(t, p, false, "simple short page")
   638  	}
   639  
   640  	settings := map[string]interface{}{
   641  		"contentDir": "mycontent",
   642  	}
   643  
   644  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage)
   645  }
   646  
   647  func TestSplitSummaryAndContent(t *testing.T) {
   648  	t.Parallel()
   649  	for i, this := range []struct {
   650  		markup          string
   651  		content         string
   652  		expectedSummary string
   653  		expectedContent string
   654  	}{
   655  		{"markdown", `<p>Summary Same LineHUGOMORE42</p>
   656  
   657  <p>Some more text</p>`, "<p>Summary Same Line</p>", "<p>Summary Same Line</p>\n\n<p>Some more text</p>"},
   658  		{"asciidoc", `<div class="paragraph"><p>sn</p></div><div class="paragraph"><p>HUGOMORE42Some more text</p></div>`,
   659  			"<div class=\"paragraph\"><p>sn</p></div>",
   660  			"<div class=\"paragraph\"><p>sn</p></div><div class=\"paragraph\"><p>Some more text</p></div>"},
   661  		{"rst",
   662  			"<div class=\"document\"><p>Summary Next Line</p><p>HUGOMORE42Some more text</p></div>",
   663  			"<div class=\"document\"><p>Summary Next Line</p></div>",
   664  			"<div class=\"document\"><p>Summary Next Line</p><p>Some more text</p></div>"},
   665  		{"markdown", "<p>a</p><p>b</p><p>HUGOMORE42c</p>", "<p>a</p><p>b</p>", "<p>a</p><p>b</p><p>c</p>"},
   666  		{"markdown", "<p>a</p><p>b</p><p>cHUGOMORE42</p>", "<p>a</p><p>b</p><p>c</p>", "<p>a</p><p>b</p><p>c</p>"},
   667  		{"markdown", "<p>a</p><p>bHUGOMORE42</p><p>c</p>", "<p>a</p><p>b</p>", "<p>a</p><p>b</p><p>c</p>"},
   668  		{"markdown", "<p>aHUGOMORE42</p><p>b</p><p>c</p>", "<p>a</p>", "<p>a</p><p>b</p><p>c</p>"},
   669  		{"markdown", "  HUGOMORE42 ", "", ""},
   670  		{"markdown", "HUGOMORE42", "", ""},
   671  		{"markdown", "<p>HUGOMORE42", "<p>", "<p>"},
   672  		{"markdown", "HUGOMORE42<p>", "", "<p>"},
   673  		{"markdown", "\n\n<p>HUGOMORE42</p>\n", "<p></p>", "<p></p>"},
   674  		// Issue #2586
   675  		// Note: Hugo will not split mid-sentence but will look for the closest
   676  		// paragraph end marker. This may be a change from Hugo 0.16, but it makes sense.
   677  		{"markdown", `<p>this is an example HUGOMORE42of the issue.</p>`,
   678  			"<p>this is an example of the issue.</p>",
   679  			"<p>this is an example of the issue.</p>"},
   680  		// Issue: #2538
   681  		{"markdown", fmt.Sprintf(` <p class="lead">%s</p>HUGOMORE42<p>%s</p>
   682  `,
   683  			strings.Repeat("A", 10), strings.Repeat("B", 31)),
   684  			fmt.Sprintf(`<p class="lead">%s</p>`, strings.Repeat("A", 10)),
   685  			fmt.Sprintf(`<p class="lead">%s</p><p>%s</p>`, strings.Repeat("A", 10), strings.Repeat("B", 31)),
   686  		},
   687  	} {
   688  
   689  		sc, err := splitUserDefinedSummaryAndContent(this.markup, []byte(this.content))
   690  
   691  		require.NoError(t, err)
   692  		require.NotNil(t, sc, fmt.Sprintf("[%d] Nil %s", i, this.markup))
   693  		require.Equal(t, this.expectedSummary, string(sc.summary), fmt.Sprintf("[%d] Summary markup %s", i, this.markup))
   694  		require.Equal(t, this.expectedContent, string(sc.content), fmt.Sprintf("[%d] Content markup %s", i, this.markup))
   695  	}
   696  }
   697  
   698  func TestPageWithDelimiter(t *testing.T) {
   699  	t.Parallel()
   700  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   701  		p := pages[0]
   702  		checkPageTitle(t, p, "Simple")
   703  		checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext)
   704  		checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext)
   705  		checkPageType(t, p, "page")
   706  		checkTruncation(t, p, true, "page with summary delimiter")
   707  	}
   708  
   709  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
   710  }
   711  
   712  // Issue #1076
   713  func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
   714  	t.Parallel()
   715  	cfg, fs := newTestCfg()
   716  
   717  	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
   718  
   719  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   720  
   721  	require.Len(t, s.RegularPages, 1)
   722  
   723  	p := s.RegularPages[0]
   724  
   725  	if p.Summary() != template.HTML("<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup class=\"footnote-ref\" id=\"fnref:1\"><a href=\"#fn:1\">1</a></sup>\n</p>") {
   726  		t.Fatalf("Got summary:\n%q", p.Summary())
   727  	}
   728  
   729  	if p.content() != template.HTML("<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup class=\"footnote-ref\" id=\"fnref:1\"><a href=\"#fn:1\">1</a></sup>\n</p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:1\">Many people say so.\n <a class=\"footnote-return\" href=\"#fnref:1\"><sup>[return]</sup></a></li>\n</ol>\n</div>") {
   730  		t.Fatalf("Got content:\n%q", p.content())
   731  	}
   732  }
   733  
   734  // Issue #3854
   735  // Also see https://github.com/gohugoio/hugo/issues/3977
   736  func TestPageWithDateFields(t *testing.T) {
   737  	assert := require.New(t)
   738  	pageWithDate := `---
   739  title: P%d
   740  weight: %d
   741  %s: 2017-10-13
   742  ---
   743  Simple Page With Some Date`
   744  
   745  	hasDate := func(p *Page) bool {
   746  		return p.Date.Year() == 2017
   747  	}
   748  
   749  	datePage := func(field string, weight int) string {
   750  		return fmt.Sprintf(pageWithDate, weight, weight, field)
   751  	}
   752  
   753  	t.Parallel()
   754  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   755  		assert.True(len(pages) > 0)
   756  		for _, p := range pages {
   757  			assert.True(hasDate(p))
   758  		}
   759  
   760  	}
   761  
   762  	fields := []string{"date", "publishdate", "pubdate", "published"}
   763  	pageContents := make([]string, len(fields))
   764  	for i, field := range fields {
   765  		pageContents[i] = datePage(field, i+1)
   766  	}
   767  
   768  	testAllMarkdownEnginesForPages(t, assertFunc, nil, pageContents...)
   769  }
   770  
   771  // Issue #2601
   772  func TestPageRawContent(t *testing.T) {
   773  	t.Parallel()
   774  	cfg, fs := newTestCfg()
   775  
   776  	writeSource(t, fs, filepath.Join("content", "raw.md"), `---
   777  title: Raw
   778  ---
   779  **Raw**`)
   780  
   781  	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
   782  
   783  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   784  
   785  	require.Len(t, s.RegularPages, 1)
   786  	p := s.RegularPages[0]
   787  
   788  	require.Contains(t, p.RawContent(), "**Raw**")
   789  
   790  }
   791  
   792  func TestPageWithShortCodeInSummary(t *testing.T) {
   793  	t.Parallel()
   794  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   795  		p := pages[0]
   796  		checkPageTitle(t, p, "Simple")
   797  		checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. \n<figure>\n    \n        <img src=\"/not/real\" />\n    \n    \n</figure>\n.\nMore text here.</p>\n\n<p>Some more text</p>\n"))
   798  		checkPageSummary(t, p, "Summary Next Line.  . More text here. Some more text")
   799  		checkPageType(t, p, "page")
   800  	}
   801  
   802  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
   803  }
   804  
   805  func TestPageWithEmbeddedScriptTag(t *testing.T) {
   806  	t.Parallel()
   807  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   808  		p := pages[0]
   809  		if ext == "ad" || ext == "rst" {
   810  			// TOD(bep)
   811  			return
   812  		}
   813  		checkPageContent(t, p, "<script type='text/javascript'>alert('the script tags are still there, right?');</script>\n", ext)
   814  	}
   815  
   816  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithEmbeddedScript)
   817  }
   818  
   819  func TestPageWithAdditionalExtension(t *testing.T) {
   820  	t.Parallel()
   821  	cfg, fs := newTestCfg()
   822  
   823  	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension)
   824  
   825  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   826  
   827  	require.Len(t, s.RegularPages, 1)
   828  
   829  	p := s.RegularPages[0]
   830  
   831  	checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n")
   832  }
   833  
   834  func TestTableOfContents(t *testing.T) {
   835  
   836  	cfg, fs := newTestCfg()
   837  
   838  	writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC)
   839  
   840  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   841  
   842  	require.Len(t, s.RegularPages, 1)
   843  
   844  	p := s.RegularPages[0]
   845  
   846  	checkPageContent(t, p, "\n\n<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>\n\n<h2 id=\"aa\">AA</h2>\n\n<p>I have no idea, of course, how long it took me to reach the limit of the plain,\nbut at last I entered the foothills, following a pretty little canyon upward\ntoward the mountains. Beside me frolicked a laughing brooklet, hurrying upon\nits noisy way down to the silent sea. In its quieter pools I discovered many\nsmall fish, of four-or five-pound weight I should imagine. In appearance,\nexcept as to size and color, they were not unlike the whale of our own seas. As\nI watched them playing about I discovered, not only that they suckled their\nyoung, but that at intervals they rose to the surface to breathe as well as to\nfeed upon certain grasses and a strange, scarlet lichen which grew upon the\nrocks just above the water line.</p>\n\n<h3 id=\"aaa\">AAA</h3>\n\n<p>I remember I felt an extraordinary persuasion that I was being played with,\nthat presently, when I was upon the very verge of safety, this mysterious\ndeath&ndash;as swift as the passage of light&ndash;would leap after me from the pit about\nthe cylinder and strike me down. ## BB</p>\n\n<h3 id=\"bbb\">BBB</h3>\n\n<p>&ldquo;You&rsquo;re a great Granser,&rdquo; he cried delightedly, &ldquo;always making believe them little marks mean something.&rdquo;</p>\n")
   847  	checkPageTOC(t, p, "<nav id=\"TableOfContents\">\n<ul>\n<li>\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></li>\n</ul></li>\n</ul>\n</nav>")
   848  }
   849  
   850  func TestPageWithMoreTag(t *testing.T) {
   851  	t.Parallel()
   852  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   853  		p := pages[0]
   854  		checkPageTitle(t, p, "Simple")
   855  		checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n"))
   856  		checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>"))
   857  		checkPageType(t, p, "page")
   858  
   859  	}
   860  
   861  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine)
   862  }
   863  
   864  func TestPageWithMoreTagOnlySummary(t *testing.T) {
   865  
   866  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   867  		p := pages[0]
   868  		checkTruncation(t, p, false, "page with summary delimiter at end")
   869  	}
   870  
   871  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterOnlySummary)
   872  }
   873  
   874  // #2973
   875  func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) {
   876  
   877  	assertFunc := func(t *testing.T, ext string, pages Pages) {
   878  		p := pages[0]
   879  		require.Contains(t, p.Summary(), "Happy new year everyone!")
   880  		require.NotContains(t, p.Summary(), "User interface")
   881  	}
   882  
   883  	testAllMarkdownEnginesForPages(t, assertFunc, nil, `---
   884  title: Simple
   885  ---
   886  Happy new year everyone!
   887  
   888  Here is the last report for commits in the year 2016. It covers hrev50718-hrev50829.
   889  
   890  <!--more-->
   891  
   892  <h3>User interface</h3>
   893  
   894  `)
   895  }
   896  
   897  func TestPageWithDate(t *testing.T) {
   898  	t.Parallel()
   899  	cfg, fs := newTestCfg()
   900  
   901  	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date)
   902  
   903  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   904  
   905  	require.Len(t, s.RegularPages, 1)
   906  
   907  	p := s.RegularPages[0]
   908  	d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z")
   909  
   910  	checkPageDate(t, p, d)
   911  }
   912  
   913  func TestPageWithLastmodFromGitInfo(t *testing.T) {
   914  	assrt := require.New(t)
   915  
   916  	// We need to use the OS fs for this.
   917  	cfg := viper.New()
   918  	fs := hugofs.NewFrom(hugofs.Os, cfg)
   919  	fs.Destination = &afero.MemMapFs{}
   920  
   921  	cfg.Set("frontmatter", map[string]interface{}{
   922  		"lastmod": []string{":git", "lastmod"},
   923  	})
   924  
   925  	cfg.Set("enableGitInfo", true)
   926  
   927  	assrt.NoError(loadDefaultSettingsFor(cfg))
   928  
   929  	wd, err := os.Getwd()
   930  	assrt.NoError(err)
   931  	cfg.Set("workingDir", filepath.Join(wd, "testsite"))
   932  
   933  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   934  
   935  	assrt.Len(s.RegularPages, 1)
   936  
   937  	// 2018-03-11 is the Git author date for testsite/content/first-post.md
   938  	assrt.Equal("2018-03-11", s.RegularPages[0].Lastmod.Format("2006-01-02"))
   939  }
   940  
   941  func TestPageWithFrontMatterConfig(t *testing.T) {
   942  	t.Parallel()
   943  
   944  	for _, dateHandler := range []string{":filename", ":fileModTime"} {
   945  		t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) {
   946  			assrt := require.New(t)
   947  			cfg, fs := newTestCfg()
   948  
   949  			pageTemplate := `
   950  ---
   951  title: Page
   952  weight: %d
   953  lastMod: 2018-02-28
   954  %s
   955  ---
   956  Content
   957  `
   958  
   959  			cfg.Set("frontmatter", map[string]interface{}{
   960  				"date": []string{dateHandler, "date"},
   961  			})
   962  
   963  			c1 := filepath.Join("content", "section", "2012-02-21-noslug.md")
   964  			c2 := filepath.Join("content", "section", "2012-02-22-slug.md")
   965  
   966  			writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, ""))
   967  			writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug"))
   968  
   969  			c1fi, err := fs.Source.Stat(c1)
   970  			assrt.NoError(err)
   971  			c2fi, err := fs.Source.Stat(c2)
   972  			assrt.NoError(err)
   973  
   974  			s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
   975  
   976  			assrt.Len(s.RegularPages, 2)
   977  
   978  			noSlug := s.RegularPages[0]
   979  			slug := s.RegularPages[1]
   980  
   981  			assrt.Equal(28, noSlug.Lastmod.Day())
   982  
   983  			switch strings.ToLower(dateHandler) {
   984  			case ":filename":
   985  				assrt.False(noSlug.Date.IsZero())
   986  				assrt.False(slug.Date.IsZero())
   987  				assrt.Equal(2012, noSlug.Date.Year())
   988  				assrt.Equal(2012, slug.Date.Year())
   989  				assrt.Equal("noslug", noSlug.Slug)
   990  				assrt.Equal("aslug", slug.Slug)
   991  			case ":filemodtime":
   992  				assrt.Equal(c1fi.ModTime().Year(), noSlug.Date.Year())
   993  				assrt.Equal(c2fi.ModTime().Year(), slug.Date.Year())
   994  				fallthrough
   995  			default:
   996  				assrt.Equal("", noSlug.Slug)
   997  				assrt.Equal("aslug", slug.Slug)
   998  
   999  			}
  1000  		})
  1001  	}
  1002  
  1003  }
  1004  
  1005  func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
  1006  	t.Parallel()
  1007  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1008  		p := pages[0]
  1009  		if p.WordCount() != 8 {
  1010  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount())
  1011  		}
  1012  	}
  1013  
  1014  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes)
  1015  }
  1016  
  1017  func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
  1018  	t.Parallel()
  1019  	settings := map[string]interface{}{"hasCJKLanguage": true}
  1020  
  1021  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1022  		p := pages[0]
  1023  		if p.WordCount() != 15 {
  1024  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount())
  1025  		}
  1026  	}
  1027  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes)
  1028  }
  1029  
  1030  func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
  1031  	t.Parallel()
  1032  	settings := map[string]interface{}{"hasCJKLanguage": true}
  1033  
  1034  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1035  		p := pages[0]
  1036  		if p.WordCount() != 74 {
  1037  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
  1038  		}
  1039  
  1040  		if p.summary != simplePageWithMainEnglishWithCJKRunesSummary {
  1041  			t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
  1042  				simplePageWithMainEnglishWithCJKRunesSummary, p.summary)
  1043  		}
  1044  	}
  1045  
  1046  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes)
  1047  }
  1048  
  1049  func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
  1050  	t.Parallel()
  1051  	settings := map[string]interface{}{
  1052  		"hasCJKLanguage": true,
  1053  	}
  1054  
  1055  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1056  		p := pages[0]
  1057  		if p.WordCount() != 75 {
  1058  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
  1059  		}
  1060  
  1061  		if p.summary != simplePageWithIsCJKLanguageFalseSummary {
  1062  			t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
  1063  				simplePageWithIsCJKLanguageFalseSummary, p.summary)
  1064  		}
  1065  	}
  1066  
  1067  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse)
  1068  
  1069  }
  1070  
  1071  func TestWordCount(t *testing.T) {
  1072  	t.Parallel()
  1073  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1074  		p := pages[0]
  1075  		if p.WordCount() != 483 {
  1076  			t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount())
  1077  		}
  1078  
  1079  		if p.FuzzyWordCount() != 500 {
  1080  			t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount())
  1081  		}
  1082  
  1083  		if p.ReadingTime() != 3 {
  1084  			t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime())
  1085  		}
  1086  
  1087  		checkTruncation(t, p, true, "long page")
  1088  	}
  1089  
  1090  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent)
  1091  }
  1092  
  1093  func TestCreatePage(t *testing.T) {
  1094  	t.Parallel()
  1095  	var tests = []struct {
  1096  		r string
  1097  	}{
  1098  		{simplePageJSON},
  1099  		{simplePageJSONMultiple},
  1100  		//{strings.NewReader(SIMPLE_PAGE_JSON_COMPACT)},
  1101  	}
  1102  
  1103  	for i, test := range tests {
  1104  		s := newTestSite(t)
  1105  		p, _ := s.NewPage("page")
  1106  		if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil {
  1107  			t.Fatalf("[%d] Unable to parse page: %s", i, err)
  1108  		}
  1109  	}
  1110  }
  1111  
  1112  func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) {
  1113  	t.Parallel()
  1114  	var tests = []struct {
  1115  		r   string
  1116  		err string
  1117  	}{
  1118  		{invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"},
  1119  	}
  1120  	for _, test := range tests {
  1121  		s := newTestSite(t)
  1122  		p, _ := s.NewPage("invalid/front/matter/short/delim")
  1123  		_, err := p.ReadFrom(strings.NewReader(test.r))
  1124  		checkError(t, err, test.err)
  1125  	}
  1126  }
  1127  
  1128  func TestShouldRenderContent(t *testing.T) {
  1129  	t.Parallel()
  1130  	var tests = []struct {
  1131  		text   string
  1132  		render bool
  1133  	}{
  1134  		{contentNoFrontmatter, true},
  1135  		// TODO how to deal with malformed frontmatter.  In this case it'll be rendered as markdown.
  1136  		{invalidFrontmatterShortDelim, true},
  1137  		{renderNoFrontmatter, false},
  1138  		{contentWithCommentedFrontmatter, true},
  1139  		{contentWithCommentedTextFrontmatter, true},
  1140  		{contentWithCommentedLongFrontmatter, false},
  1141  		{contentWithCommentedLong2Frontmatter, true},
  1142  	}
  1143  
  1144  	for _, test := range tests {
  1145  		s := newTestSite(t)
  1146  		p, _ := s.NewPage("render/front/matter")
  1147  		_, err := p.ReadFrom(strings.NewReader(test.text))
  1148  		p = pageMust(p, err)
  1149  		if p.IsRenderable() != test.render {
  1150  			t.Errorf("expected p.IsRenderable() == %t, got %t", test.render, p.IsRenderable())
  1151  		}
  1152  	}
  1153  }
  1154  
  1155  // Issue #768
  1156  func TestCalendarParamsVariants(t *testing.T) {
  1157  	t.Parallel()
  1158  	s := newTestSite(t)
  1159  	pageJSON, _ := s.NewPage("test/fileJSON.md")
  1160  	_, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter))
  1161  
  1162  	pageYAML, _ := s.NewPage("test/fileYAML.md")
  1163  	_, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter))
  1164  
  1165  	pageTOML, _ := s.NewPage("test/fileTOML.md")
  1166  	_, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter))
  1167  
  1168  	assert.True(t, compareObjects(pageJSON.params, pageYAML.params))
  1169  	assert.True(t, compareObjects(pageJSON.params, pageTOML.params))
  1170  
  1171  }
  1172  
  1173  func TestDifferentFrontMatterVarTypes(t *testing.T) {
  1174  	t.Parallel()
  1175  	s := newTestSite(t)
  1176  	page, _ := s.NewPage("test/file1.md")
  1177  	_, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes))
  1178  
  1179  	dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
  1180  	if page.getParamToLower("a_string") != "bar" {
  1181  		t.Errorf("frontmatter not handling strings correctly should be %s, got: %s", "bar", page.getParamToLower("a_string"))
  1182  	}
  1183  	if page.getParamToLower("an_integer") != 1 {
  1184  		t.Errorf("frontmatter not handling ints correctly should be %s, got: %s", "1", page.getParamToLower("an_integer"))
  1185  	}
  1186  	if page.getParamToLower("a_float") != 1.3 {
  1187  		t.Errorf("frontmatter not handling floats correctly should be %f, got: %s", 1.3, page.getParamToLower("a_float"))
  1188  	}
  1189  	if page.getParamToLower("a_bool") != false {
  1190  		t.Errorf("frontmatter not handling bools correctly should be %t, got: %s", false, page.getParamToLower("a_bool"))
  1191  	}
  1192  	if page.getParamToLower("a_date") != dateval {
  1193  		t.Errorf("frontmatter not handling dates correctly should be %s, got: %s", dateval, page.getParamToLower("a_date"))
  1194  	}
  1195  	param := page.getParamToLower("a_table")
  1196  	if param == nil {
  1197  		t.Errorf("frontmatter not handling tables correctly should be type of %v, got: type of %v", reflect.TypeOf(page.params["a_table"]), reflect.TypeOf(param))
  1198  	}
  1199  	if cast.ToStringMap(param)["a_key"] != "a_value" {
  1200  		t.Errorf("frontmatter not handling values inside a table correctly should be %s, got: %s", "a_value", cast.ToStringMap(page.params["a_table"])["a_key"])
  1201  	}
  1202  }
  1203  
  1204  func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) {
  1205  	t.Parallel()
  1206  	s := newTestSite(t)
  1207  	p, _ := s.NewPage("invalid/front/matter/leading/ws")
  1208  	_, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs))
  1209  	if err != nil {
  1210  		t.Fatalf("Unable to parse front matter given leading whitespace: %s", err)
  1211  	}
  1212  }
  1213  
  1214  func TestSectionEvaluation(t *testing.T) {
  1215  	t.Parallel()
  1216  	s := newTestSite(t)
  1217  	page, _ := s.NewPage(filepath.FromSlash("blue/file1.md"))
  1218  	page.ReadFrom(strings.NewReader(simplePage))
  1219  	if page.Section() != "blue" {
  1220  		t.Errorf("Section should be %s, got: %s", "blue", page.Section())
  1221  	}
  1222  }
  1223  
  1224  func TestSliceToLower(t *testing.T) {
  1225  	t.Parallel()
  1226  	tests := []struct {
  1227  		value    []string
  1228  		expected []string
  1229  	}{
  1230  		{[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
  1231  		{[]string{"a", "B", "c"}, []string{"a", "b", "c"}},
  1232  		{[]string{"A", "B", "C"}, []string{"a", "b", "c"}},
  1233  	}
  1234  
  1235  	for _, test := range tests {
  1236  		res := helpers.SliceToLower(test.value)
  1237  		for i, val := range res {
  1238  			if val != test.expected[i] {
  1239  				t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i])
  1240  			}
  1241  		}
  1242  	}
  1243  }
  1244  
  1245  func TestReplaceDivider(t *testing.T) {
  1246  	t.Parallel()
  1247  
  1248  	tests := []struct {
  1249  		content           string
  1250  		from              string
  1251  		to                string
  1252  		expectedContent   string
  1253  		expectedTruncated bool
  1254  	}{
  1255  		{"none", "a", "b", "none", false},
  1256  		{"summary <!--more--> content", "<!--more-->", "HUGO", "summary HUGO content", true},
  1257  		{"summary\n\ndivider", "divider", "HUGO", "summary\n\nHUGO", false},
  1258  		{"summary\n\ndivider\n\r", "divider", "HUGO", "summary\n\nHUGO\n\r", false},
  1259  	}
  1260  
  1261  	for i, test := range tests {
  1262  		replaced, truncated := replaceDivider([]byte(test.content), []byte(test.from), []byte(test.to))
  1263  
  1264  		if truncated != test.expectedTruncated {
  1265  			t.Fatalf("[%d] Expected truncated to be %t, was %t", i, test.expectedTruncated, truncated)
  1266  		}
  1267  
  1268  		if string(replaced) != test.expectedContent {
  1269  			t.Fatalf("[%d] Expected content to be %q, was %q", i, test.expectedContent, replaced)
  1270  		}
  1271  	}
  1272  }
  1273  
  1274  func BenchmarkReplaceDivider(b *testing.B) {
  1275  	divider := "HUGO_DIVIDER"
  1276  	from, to := []byte(divider), []byte("HUGO_REPLACED")
  1277  
  1278  	withDivider := make([][]byte, b.N)
  1279  	noDivider := make([][]byte, b.N)
  1280  
  1281  	for i := 0; i < b.N; i++ {
  1282  		withDivider[i] = []byte(strings.Repeat("Summary ", 5) + "\n" + divider + "\n" + strings.Repeat("Word ", 300))
  1283  		noDivider[i] = []byte(strings.Repeat("Word ", 300))
  1284  	}
  1285  
  1286  	b.ResetTimer()
  1287  	for i := 0; i < b.N; i++ {
  1288  		_, t1 := replaceDivider(withDivider[i], from, to)
  1289  		_, t2 := replaceDivider(noDivider[i], from, to)
  1290  		if !t1 {
  1291  			b.Fatal("Should be truncated")
  1292  		}
  1293  		if t2 {
  1294  			b.Fatal("Should not be truncated")
  1295  		}
  1296  	}
  1297  }
  1298  
  1299  func TestPagePaths(t *testing.T) {
  1300  	t.Parallel()
  1301  
  1302  	siteParmalinksSetting := map[string]string{
  1303  		"post": ":year/:month/:day/:title/",
  1304  	}
  1305  
  1306  	tests := []struct {
  1307  		content      string
  1308  		path         string
  1309  		hasPermalink bool
  1310  		expected     string
  1311  	}{
  1312  		{simplePage, "post/x.md", false, "post/x.html"},
  1313  		{simplePageWithURL, "post/x.md", false, "simple/url/index.html"},
  1314  		{simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"},
  1315  		{simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"},
  1316  		{UTF8Page, "post/x.md", false, "post/x.html"},
  1317  		{UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"},
  1318  		{UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"},
  1319  		{UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"},
  1320  	}
  1321  
  1322  	for _, test := range tests {
  1323  		cfg, fs := newTestCfg()
  1324  
  1325  		if test.hasPermalink {
  1326  			cfg.Set("permalinks", siteParmalinksSetting)
  1327  		}
  1328  
  1329  		writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content)
  1330  
  1331  		s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1332  		require.Len(t, s.RegularPages, 1)
  1333  
  1334  	}
  1335  }
  1336  
  1337  var pageWithDraftAndPublished = `---
  1338  title: broken
  1339  published: false
  1340  draft: true
  1341  ---
  1342  some content
  1343  `
  1344  
  1345  func TestDraftAndPublishedFrontMatterError(t *testing.T) {
  1346  	t.Parallel()
  1347  	s := newTestSite(t)
  1348  	_, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md")
  1349  	if err != ErrHasDraftAndPublished {
  1350  		t.Errorf("expected ErrHasDraftAndPublished, was %#v", err)
  1351  	}
  1352  }
  1353  
  1354  var pagesWithPublishedFalse = `---
  1355  title: okay
  1356  published: false
  1357  ---
  1358  some content
  1359  `
  1360  var pageWithPublishedTrue = `---
  1361  title: okay
  1362  published: true
  1363  ---
  1364  some content
  1365  `
  1366  
  1367  func TestPublishedFrontMatter(t *testing.T) {
  1368  	t.Parallel()
  1369  	s := newTestSite(t)
  1370  	p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md")
  1371  	if err != nil {
  1372  		t.Fatalf("err during parse: %s", err)
  1373  	}
  1374  	if !p.Draft {
  1375  		t.Errorf("expected true, got %t", p.Draft)
  1376  	}
  1377  	p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md")
  1378  	if err != nil {
  1379  		t.Fatalf("err during parse: %s", err)
  1380  	}
  1381  	if p.Draft {
  1382  		t.Errorf("expected false, got %t", p.Draft)
  1383  	}
  1384  }
  1385  
  1386  var pagesDraftTemplate = []string{`---
  1387  title: "okay"
  1388  draft: %t
  1389  ---
  1390  some content
  1391  `,
  1392  	`+++
  1393  title = "okay"
  1394  draft = %t
  1395  +++
  1396  
  1397  some content
  1398  `,
  1399  }
  1400  
  1401  func TestDraft(t *testing.T) {
  1402  	t.Parallel()
  1403  	s := newTestSite(t)
  1404  	for _, draft := range []bool{true, false} {
  1405  		for i, templ := range pagesDraftTemplate {
  1406  			pageContent := fmt.Sprintf(templ, draft)
  1407  			p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md")
  1408  			if err != nil {
  1409  				t.Fatalf("err during parse: %s", err)
  1410  			}
  1411  			if p.Draft != draft {
  1412  				t.Errorf("[%d] expected %t, got %t", i, draft, p.Draft)
  1413  			}
  1414  		}
  1415  	}
  1416  }
  1417  
  1418  var pagesParamsTemplate = []string{`+++
  1419  title = "okay"
  1420  draft = false
  1421  tags = [ "hugo", "web" ]
  1422  social= [
  1423    [ "a", "#" ],
  1424    [ "b", "#" ],
  1425  ]
  1426  +++
  1427  some content
  1428  `,
  1429  	`---
  1430  title: "okay"
  1431  draft: false
  1432  tags:
  1433    - hugo
  1434    - web
  1435  social:
  1436    - - a
  1437      - "#"
  1438    - - b
  1439      - "#"
  1440  ---
  1441  some content
  1442  `,
  1443  	`{
  1444  	"title": "okay",
  1445  	"draft": false,
  1446  	"tags": [ "hugo", "web" ],
  1447  	"social": [
  1448  		[ "a", "#" ],
  1449  		[ "b", "#" ]
  1450  	]
  1451  }
  1452  some content
  1453  `,
  1454  }
  1455  
  1456  func TestPageParams(t *testing.T) {
  1457  	t.Parallel()
  1458  	s := newTestSite(t)
  1459  	wantedMap := map[string]interface{}{
  1460  		"tags": []string{"hugo", "web"},
  1461  		// Issue #2752
  1462  		"social": []interface{}{
  1463  			[]interface{}{"a", "#"},
  1464  			[]interface{}{"b", "#"},
  1465  		},
  1466  	}
  1467  
  1468  	for i, c := range pagesParamsTemplate {
  1469  		p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
  1470  		require.NoError(t, err, "err during parse", "#%d", i)
  1471  		for key := range wantedMap {
  1472  			assert.Equal(t, wantedMap[key], p.params[key], "#%d", key)
  1473  		}
  1474  	}
  1475  }
  1476  
  1477  func TestTraverse(t *testing.T) {
  1478  	exampleParams := `---
  1479  rating: "5 stars"
  1480  tags:
  1481    - hugo
  1482    - web
  1483  social:
  1484    twitter: "@jxxf"
  1485    facebook: "https://example.com"
  1486  ---`
  1487  	t.Parallel()
  1488  	s := newTestSite(t)
  1489  	p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md")
  1490  
  1491  	topLevelKeyValue, _ := p.Param("rating")
  1492  	assert.Equal(t, "5 stars", topLevelKeyValue)
  1493  
  1494  	nestedStringKeyValue, _ := p.Param("social.twitter")
  1495  	assert.Equal(t, "@jxxf", nestedStringKeyValue)
  1496  
  1497  	nonexistentKeyValue, _ := p.Param("doesn't.exist")
  1498  	assert.Nil(t, nonexistentKeyValue)
  1499  }
  1500  
  1501  func TestPageSimpleMethods(t *testing.T) {
  1502  	t.Parallel()
  1503  	s := newTestSite(t)
  1504  	for i, this := range []struct {
  1505  		assertFunc func(p *Page) bool
  1506  	}{
  1507  		{func(p *Page) bool { return !p.IsNode() }},
  1508  		{func(p *Page) bool { return p.IsPage() }},
  1509  		{func(p *Page) bool { return p.Plain() == "Do Be Do Be Do" }},
  1510  		{func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }},
  1511  	} {
  1512  
  1513  		p, _ := s.NewPage("Test")
  1514  		p.workContent = []byte("<h1>Do Be Do Be Do</h1>")
  1515  		p.resetContent()
  1516  		if !this.assertFunc(p) {
  1517  			t.Errorf("[%d] Page method error", i)
  1518  		}
  1519  	}
  1520  }
  1521  
  1522  func TestIndexPageSimpleMethods(t *testing.T) {
  1523  	s := newTestSite(t)
  1524  	t.Parallel()
  1525  	for i, this := range []struct {
  1526  		assertFunc func(n *Page) bool
  1527  	}{
  1528  		{func(n *Page) bool { return n.IsNode() }},
  1529  		{func(n *Page) bool { return !n.IsPage() }},
  1530  		{func(n *Page) bool { return n.Scratch() != nil }},
  1531  		{func(n *Page) bool { return n.Hugo() != nil }},
  1532  	} {
  1533  
  1534  		n := s.newHomePage()
  1535  
  1536  		if !this.assertFunc(n) {
  1537  			t.Errorf("[%d] Node method error", i)
  1538  		}
  1539  	}
  1540  }
  1541  
  1542  func TestKind(t *testing.T) {
  1543  	t.Parallel()
  1544  	// Add tests for these constants to make sure they don't change
  1545  	require.Equal(t, "page", KindPage)
  1546  	require.Equal(t, "home", KindHome)
  1547  	require.Equal(t, "section", KindSection)
  1548  	require.Equal(t, "taxonomy", KindTaxonomy)
  1549  	require.Equal(t, "taxonomyTerm", KindTaxonomyTerm)
  1550  
  1551  }
  1552  
  1553  func TestTranslationKey(t *testing.T) {
  1554  	t.Parallel()
  1555  	assert := require.New(t)
  1556  	cfg, fs := newTestCfg()
  1557  
  1558  	writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n")
  1559  	writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n")
  1560  
  1561  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1562  
  1563  	require.Len(t, s.RegularPages, 2)
  1564  
  1565  	home, _ := s.Info.Home()
  1566  	assert.NotNil(home)
  1567  	assert.Equal("home", home.TranslationKey())
  1568  	assert.Equal("page/k1", s.RegularPages[0].TranslationKey())
  1569  	p2 := s.RegularPages[1]
  1570  
  1571  	assert.Equal("page/sect/simple", p2.TranslationKey())
  1572  
  1573  }
  1574  
  1575  func TestChompBOM(t *testing.T) {
  1576  	t.Parallel()
  1577  	const utf8BOM = "\xef\xbb\xbf"
  1578  
  1579  	cfg, fs := newTestCfg()
  1580  
  1581  	writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
  1582  
  1583  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1584  
  1585  	require.Len(t, s.RegularPages, 1)
  1586  
  1587  	p := s.RegularPages[0]
  1588  
  1589  	checkPageTitle(t, p, "Simple")
  1590  }
  1591  
  1592  // TODO(bep) this may be useful for other tests.
  1593  func compareObjects(a interface{}, b interface{}) bool {
  1594  	aStr := strings.Split(fmt.Sprintf("%v", a), "")
  1595  	sort.Strings(aStr)
  1596  
  1597  	bStr := strings.Split(fmt.Sprintf("%v", b), "")
  1598  	sort.Strings(bStr)
  1599  
  1600  	return strings.Join(aStr, "") == strings.Join(bStr, "")
  1601  }
  1602  
  1603  func TestShouldBuild(t *testing.T) {
  1604  	t.Parallel()
  1605  	var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
  1606  	var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
  1607  	var zero = time.Time{}
  1608  
  1609  	var publishSettings = []struct {
  1610  		buildFuture  bool
  1611  		buildExpired bool
  1612  		buildDrafts  bool
  1613  		draft        bool
  1614  		publishDate  time.Time
  1615  		expiryDate   time.Time
  1616  		out          bool
  1617  	}{
  1618  		// publishDate and expiryDate
  1619  		{false, false, false, false, zero, zero, true},
  1620  		{false, false, false, false, zero, future, true},
  1621  		{false, false, false, false, past, zero, true},
  1622  		{false, false, false, false, past, future, true},
  1623  		{false, false, false, false, past, past, false},
  1624  		{false, false, false, false, future, future, false},
  1625  		{false, false, false, false, future, past, false},
  1626  
  1627  		// buildFuture and buildExpired
  1628  		{false, true, false, false, past, past, true},
  1629  		{true, true, false, false, past, past, true},
  1630  		{true, false, false, false, past, past, false},
  1631  		{true, false, false, false, future, future, true},
  1632  		{true, true, false, false, future, future, true},
  1633  		{false, true, false, false, future, past, false},
  1634  
  1635  		// buildDrafts and draft
  1636  		{true, true, false, true, past, future, false},
  1637  		{true, true, true, true, past, future, true},
  1638  		{true, true, true, true, past, future, true},
  1639  	}
  1640  
  1641  	for _, ps := range publishSettings {
  1642  		s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
  1643  			ps.publishDate, ps.expiryDate)
  1644  		if s != ps.out {
  1645  			t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps)
  1646  		}
  1647  	}
  1648  }
  1649  
  1650  // "dot" in path: #1885 and #2110
  1651  // disablePathToLower regression: #3374
  1652  func TestPathIssues(t *testing.T) {
  1653  	t.Parallel()
  1654  	for _, disablePathToLower := range []bool{false, true} {
  1655  		for _, uglyURLs := range []bool{false, true} {
  1656  			t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) {
  1657  
  1658  				cfg, fs := newTestCfg()
  1659  				th := testHelper{cfg, fs, t}
  1660  
  1661  				cfg.Set("permalinks", map[string]string{
  1662  					"post": ":section/:title",
  1663  				})
  1664  
  1665  				cfg.Set("uglyURLs", uglyURLs)
  1666  				cfg.Set("disablePathToLower", disablePathToLower)
  1667  				cfg.Set("paginate", 1)
  1668  
  1669  				writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
  1670  				writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
  1671  					"<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>")
  1672  
  1673  				for i := 0; i < 3; i++ {
  1674  					writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)),
  1675  						fmt.Sprintf(`---
  1676  title: "test%d.dot"
  1677  tags:
  1678  - ".net"
  1679  ---
  1680  # doc1
  1681  *some content*`, i))
  1682  				}
  1683  
  1684  				writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"),
  1685  					fmt.Sprintf(`---
  1686  title: "testBlog"
  1687  tags:
  1688  - "Blog"
  1689  ---
  1690  # doc1
  1691  *some blog content*`))
  1692  
  1693  				s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
  1694  
  1695  				require.Len(t, s.RegularPages, 4)
  1696  
  1697  				pathFunc := func(s string) string {
  1698  					if uglyURLs {
  1699  						return strings.Replace(s, "/index.html", ".html", 1)
  1700  					}
  1701  					return s
  1702  				}
  1703  
  1704  				blog := "blog"
  1705  
  1706  				if disablePathToLower {
  1707  					blog = "Blog"
  1708  				}
  1709  
  1710  				th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content")
  1711  
  1712  				th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content")
  1713  
  1714  				if uglyURLs {
  1715  					th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`)
  1716  					th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`)
  1717  					th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`)
  1718  				} else {
  1719  					th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`)
  1720  					th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`)
  1721  					th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`)
  1722  					th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`)
  1723  
  1724  				}
  1725  
  1726  				p := s.RegularPages[0]
  1727  				if uglyURLs {
  1728  					require.Equal(t, "/post/test0.dot.html", p.RelPermalink())
  1729  				} else {
  1730  					require.Equal(t, "/post/test0.dot/", p.RelPermalink())
  1731  				}
  1732  
  1733  			})
  1734  		}
  1735  	}
  1736  }
  1737  
  1738  // https://github.com/gohugoio/hugo/issues/4675
  1739  func TestWordCountAndSimilarVsSummary(t *testing.T) {
  1740  
  1741  	t.Parallel()
  1742  	assert := require.New(t)
  1743  
  1744  	single := []string{"_default/single.html", `
  1745  WordCount: {{ .WordCount }}
  1746  FuzzyWordCount: {{ .FuzzyWordCount }}
  1747  ReadingTime: {{ .ReadingTime }}
  1748  Len Plain: {{ len .Plain }}
  1749  Len PlainWords: {{ len .PlainWords }}
  1750  Truncated: {{ .Truncated }}
  1751  Len Summary: {{ len .Summary }}
  1752  Len Content: {{ len .Content }}
  1753  `}
  1754  
  1755  	b := newTestSitesBuilder(t)
  1756  	b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`---
  1757  title: p1	
  1758  ---
  1759  
  1760  %s
  1761  
  1762  `, strings.Repeat("word ", 510)),
  1763  
  1764  		"p2.md", fmt.Sprintf(`---
  1765  title: p2
  1766  ---
  1767  This is a summary.
  1768  
  1769  <!--more-->
  1770  
  1771  %s
  1772  
  1773  `, strings.Repeat("word ", 310)),
  1774  		"p3.md", fmt.Sprintf(`---
  1775  title: p3
  1776  isCJKLanguage: true
  1777  ---
  1778  Summary: In Chinese, 好 means good.
  1779  
  1780  <!--more-->
  1781  
  1782  %s
  1783  
  1784  `, strings.Repeat("好", 200)),
  1785  		"p4.md", fmt.Sprintf(`---
  1786  title: p4
  1787  isCJKLanguage: false
  1788  ---
  1789  Summary: In Chinese, 好 means good.
  1790  
  1791  <!--more-->
  1792  
  1793  %s
  1794  
  1795  `, strings.Repeat("好", 200)),
  1796  
  1797  		"p5.md", fmt.Sprintf(`---
  1798  title: p4
  1799  isCJKLanguage: true
  1800  ---
  1801  Summary: In Chinese, 好 means good.
  1802  
  1803  %s
  1804  
  1805  `, strings.Repeat("好", 200)),
  1806  		"p6.md", fmt.Sprintf(`---
  1807  title: p4
  1808  isCJKLanguage: false
  1809  ---
  1810  Summary: In Chinese, 好 means good.
  1811  
  1812  %s
  1813  
  1814  `, strings.Repeat("好", 200)),
  1815  	)
  1816  
  1817  	b.CreateSites().Build(BuildCfg{})
  1818  
  1819  	assert.Equal(1, len(b.H.Sites))
  1820  	require.Len(t, b.H.Sites[0].RegularPages, 6)
  1821  
  1822  	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")
  1823  
  1824  	b.AssertFileContent("public/p2/index.html", "WordCount: 314\nFuzzyWordCount: 400\nReadingTime: 2\nLen Plain: 1570\nLen PlainWords: 314\nTruncated: true\nLen Summary: 34\nLen Content: 1592")
  1825  
  1826  	b.AssertFileContent("public/p3/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 639\nLen PlainWords: 7\nTruncated: true\nLen Summary: 52\nLen Content: 661")
  1827  	b.AssertFileContent("public/p4/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 639\nLen PlainWords: 7\nTruncated: true\nLen Summary: 52\nLen Content: 661")
  1828  	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: 653")
  1829  	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: 653")
  1830  
  1831  }
  1832  
  1833  func BenchmarkParsePage(b *testing.B) {
  1834  	s := newTestSite(b)
  1835  	f, _ := os.Open("testdata/redis.cn.md")
  1836  	var buf bytes.Buffer
  1837  	buf.ReadFrom(f)
  1838  	b.ResetTimer()
  1839  	for i := 0; i < b.N; i++ {
  1840  		page, _ := s.NewPage("bench")
  1841  		page.ReadFrom(bytes.NewReader(buf.Bytes()))
  1842  	}
  1843  }