github.com/schumacherfm/hugo@v0.47.1/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  	cfg.Set("defaultContentLanguage", "en")
   925  
   926  	langConfig := map[string]interface{}{
   927  		"en": map[string]interface{}{
   928  			"weight":       1,
   929  			"languageName": "English",
   930  			"contentDir":   "content",
   931  		},
   932  		"nn": map[string]interface{}{
   933  			"weight":       2,
   934  			"languageName": "Nynorsk",
   935  			"contentDir":   "content_nn",
   936  		},
   937  	}
   938  
   939  	cfg.Set("languages", langConfig)
   940  	cfg.Set("enableGitInfo", true)
   941  
   942  	assrt.NoError(loadDefaultSettingsFor(cfg))
   943  	assrt.NoError(loadLanguageSettings(cfg, nil))
   944  
   945  	wd, err := os.Getwd()
   946  	assrt.NoError(err)
   947  	cfg.Set("workingDir", filepath.Join(wd, "testsite"))
   948  
   949  	h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
   950  
   951  	assrt.NoError(err)
   952  	assrt.Len(h.Sites, 2)
   953  
   954  	require.NoError(t, h.Build(BuildCfg{SkipRender: true}))
   955  
   956  	enSite := h.Sites[0]
   957  	assrt.Len(enSite.RegularPages, 1)
   958  
   959  	// 2018-03-11 is the Git author date for testsite/content/first-post.md
   960  	assrt.Equal("2018-03-11", enSite.RegularPages[0].Lastmod.Format("2006-01-02"))
   961  
   962  	nnSite := h.Sites[1]
   963  	assrt.Len(nnSite.RegularPages, 1)
   964  
   965  	// 2018-08-11 is the Git author date for testsite/content_nn/first-post.md
   966  	assrt.Equal("2018-08-11", nnSite.RegularPages[0].Lastmod.Format("2006-01-02"))
   967  
   968  }
   969  
   970  func TestPageWithFrontMatterConfig(t *testing.T) {
   971  	t.Parallel()
   972  
   973  	for _, dateHandler := range []string{":filename", ":fileModTime"} {
   974  		t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) {
   975  			assrt := require.New(t)
   976  			cfg, fs := newTestCfg()
   977  
   978  			pageTemplate := `
   979  ---
   980  title: Page
   981  weight: %d
   982  lastMod: 2018-02-28
   983  %s
   984  ---
   985  Content
   986  `
   987  
   988  			cfg.Set("frontmatter", map[string]interface{}{
   989  				"date": []string{dateHandler, "date"},
   990  			})
   991  
   992  			c1 := filepath.Join("content", "section", "2012-02-21-noslug.md")
   993  			c2 := filepath.Join("content", "section", "2012-02-22-slug.md")
   994  
   995  			writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, ""))
   996  			writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug"))
   997  
   998  			c1fi, err := fs.Source.Stat(c1)
   999  			assrt.NoError(err)
  1000  			c2fi, err := fs.Source.Stat(c2)
  1001  			assrt.NoError(err)
  1002  
  1003  			s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1004  
  1005  			assrt.Len(s.RegularPages, 2)
  1006  
  1007  			noSlug := s.RegularPages[0]
  1008  			slug := s.RegularPages[1]
  1009  
  1010  			assrt.Equal(28, noSlug.Lastmod.Day())
  1011  
  1012  			switch strings.ToLower(dateHandler) {
  1013  			case ":filename":
  1014  				assrt.False(noSlug.Date.IsZero())
  1015  				assrt.False(slug.Date.IsZero())
  1016  				assrt.Equal(2012, noSlug.Date.Year())
  1017  				assrt.Equal(2012, slug.Date.Year())
  1018  				assrt.Equal("noslug", noSlug.Slug)
  1019  				assrt.Equal("aslug", slug.Slug)
  1020  			case ":filemodtime":
  1021  				assrt.Equal(c1fi.ModTime().Year(), noSlug.Date.Year())
  1022  				assrt.Equal(c2fi.ModTime().Year(), slug.Date.Year())
  1023  				fallthrough
  1024  			default:
  1025  				assrt.Equal("", noSlug.Slug)
  1026  				assrt.Equal("aslug", slug.Slug)
  1027  
  1028  			}
  1029  		})
  1030  	}
  1031  
  1032  }
  1033  
  1034  func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
  1035  	t.Parallel()
  1036  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1037  		p := pages[0]
  1038  		if p.WordCount() != 8 {
  1039  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount())
  1040  		}
  1041  	}
  1042  
  1043  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes)
  1044  }
  1045  
  1046  func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
  1047  	t.Parallel()
  1048  	settings := map[string]interface{}{"hasCJKLanguage": true}
  1049  
  1050  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1051  		p := pages[0]
  1052  		if p.WordCount() != 15 {
  1053  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount())
  1054  		}
  1055  	}
  1056  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes)
  1057  }
  1058  
  1059  func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
  1060  	t.Parallel()
  1061  	settings := map[string]interface{}{"hasCJKLanguage": true}
  1062  
  1063  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1064  		p := pages[0]
  1065  		if p.WordCount() != 74 {
  1066  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
  1067  		}
  1068  
  1069  		if p.summary != simplePageWithMainEnglishWithCJKRunesSummary {
  1070  			t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
  1071  				simplePageWithMainEnglishWithCJKRunesSummary, p.summary)
  1072  		}
  1073  	}
  1074  
  1075  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes)
  1076  }
  1077  
  1078  func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
  1079  	t.Parallel()
  1080  	settings := map[string]interface{}{
  1081  		"hasCJKLanguage": true,
  1082  	}
  1083  
  1084  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1085  		p := pages[0]
  1086  		if p.WordCount() != 75 {
  1087  			t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
  1088  		}
  1089  
  1090  		if p.summary != simplePageWithIsCJKLanguageFalseSummary {
  1091  			t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
  1092  				simplePageWithIsCJKLanguageFalseSummary, p.summary)
  1093  		}
  1094  	}
  1095  
  1096  	testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse)
  1097  
  1098  }
  1099  
  1100  func TestWordCount(t *testing.T) {
  1101  	t.Parallel()
  1102  	assertFunc := func(t *testing.T, ext string, pages Pages) {
  1103  		p := pages[0]
  1104  		if p.WordCount() != 483 {
  1105  			t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount())
  1106  		}
  1107  
  1108  		if p.FuzzyWordCount() != 500 {
  1109  			t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount())
  1110  		}
  1111  
  1112  		if p.ReadingTime() != 3 {
  1113  			t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime())
  1114  		}
  1115  
  1116  		checkTruncation(t, p, true, "long page")
  1117  	}
  1118  
  1119  	testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent)
  1120  }
  1121  
  1122  func TestCreatePage(t *testing.T) {
  1123  	t.Parallel()
  1124  	var tests = []struct {
  1125  		r string
  1126  	}{
  1127  		{simplePageJSON},
  1128  		{simplePageJSONMultiple},
  1129  		//{strings.NewReader(SIMPLE_PAGE_JSON_COMPACT)},
  1130  	}
  1131  
  1132  	for i, test := range tests {
  1133  		s := newTestSite(t)
  1134  		p, _ := s.NewPage("page")
  1135  		if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil {
  1136  			t.Fatalf("[%d] Unable to parse page: %s", i, err)
  1137  		}
  1138  	}
  1139  }
  1140  
  1141  func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) {
  1142  	t.Parallel()
  1143  	var tests = []struct {
  1144  		r   string
  1145  		err string
  1146  	}{
  1147  		{invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"},
  1148  	}
  1149  	for _, test := range tests {
  1150  		s := newTestSite(t)
  1151  		p, _ := s.NewPage("invalid/front/matter/short/delim")
  1152  		_, err := p.ReadFrom(strings.NewReader(test.r))
  1153  		checkError(t, err, test.err)
  1154  	}
  1155  }
  1156  
  1157  func TestShouldRenderContent(t *testing.T) {
  1158  	t.Parallel()
  1159  	var tests = []struct {
  1160  		text   string
  1161  		render bool
  1162  	}{
  1163  		{contentNoFrontmatter, true},
  1164  		// TODO how to deal with malformed frontmatter.  In this case it'll be rendered as markdown.
  1165  		{invalidFrontmatterShortDelim, true},
  1166  		{renderNoFrontmatter, false},
  1167  		{contentWithCommentedFrontmatter, true},
  1168  		{contentWithCommentedTextFrontmatter, true},
  1169  		{contentWithCommentedLongFrontmatter, false},
  1170  		{contentWithCommentedLong2Frontmatter, true},
  1171  	}
  1172  
  1173  	for _, test := range tests {
  1174  		s := newTestSite(t)
  1175  		p, _ := s.NewPage("render/front/matter")
  1176  		_, err := p.ReadFrom(strings.NewReader(test.text))
  1177  		p = pageMust(p, err)
  1178  		if p.IsRenderable() != test.render {
  1179  			t.Errorf("expected p.IsRenderable() == %t, got %t", test.render, p.IsRenderable())
  1180  		}
  1181  	}
  1182  }
  1183  
  1184  // Issue #768
  1185  func TestCalendarParamsVariants(t *testing.T) {
  1186  	t.Parallel()
  1187  	s := newTestSite(t)
  1188  	pageJSON, _ := s.NewPage("test/fileJSON.md")
  1189  	_, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter))
  1190  
  1191  	pageYAML, _ := s.NewPage("test/fileYAML.md")
  1192  	_, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter))
  1193  
  1194  	pageTOML, _ := s.NewPage("test/fileTOML.md")
  1195  	_, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter))
  1196  
  1197  	assert.True(t, compareObjects(pageJSON.params, pageYAML.params))
  1198  	assert.True(t, compareObjects(pageJSON.params, pageTOML.params))
  1199  
  1200  }
  1201  
  1202  func TestDifferentFrontMatterVarTypes(t *testing.T) {
  1203  	t.Parallel()
  1204  	s := newTestSite(t)
  1205  	page, _ := s.NewPage("test/file1.md")
  1206  	_, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes))
  1207  
  1208  	dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
  1209  	if page.getParamToLower("a_string") != "bar" {
  1210  		t.Errorf("frontmatter not handling strings correctly should be %s, got: %s", "bar", page.getParamToLower("a_string"))
  1211  	}
  1212  	if page.getParamToLower("an_integer") != 1 {
  1213  		t.Errorf("frontmatter not handling ints correctly should be %s, got: %s", "1", page.getParamToLower("an_integer"))
  1214  	}
  1215  	if page.getParamToLower("a_float") != 1.3 {
  1216  		t.Errorf("frontmatter not handling floats correctly should be %f, got: %s", 1.3, page.getParamToLower("a_float"))
  1217  	}
  1218  	if page.getParamToLower("a_bool") != false {
  1219  		t.Errorf("frontmatter not handling bools correctly should be %t, got: %s", false, page.getParamToLower("a_bool"))
  1220  	}
  1221  	if page.getParamToLower("a_date") != dateval {
  1222  		t.Errorf("frontmatter not handling dates correctly should be %s, got: %s", dateval, page.getParamToLower("a_date"))
  1223  	}
  1224  	param := page.getParamToLower("a_table")
  1225  	if param == nil {
  1226  		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))
  1227  	}
  1228  	if cast.ToStringMap(param)["a_key"] != "a_value" {
  1229  		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"])
  1230  	}
  1231  }
  1232  
  1233  func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) {
  1234  	t.Parallel()
  1235  	s := newTestSite(t)
  1236  	p, _ := s.NewPage("invalid/front/matter/leading/ws")
  1237  	_, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs))
  1238  	if err != nil {
  1239  		t.Fatalf("Unable to parse front matter given leading whitespace: %s", err)
  1240  	}
  1241  }
  1242  
  1243  func TestSectionEvaluation(t *testing.T) {
  1244  	t.Parallel()
  1245  	s := newTestSite(t)
  1246  	page, _ := s.NewPage(filepath.FromSlash("blue/file1.md"))
  1247  	page.ReadFrom(strings.NewReader(simplePage))
  1248  	if page.Section() != "blue" {
  1249  		t.Errorf("Section should be %s, got: %s", "blue", page.Section())
  1250  	}
  1251  }
  1252  
  1253  func TestSliceToLower(t *testing.T) {
  1254  	t.Parallel()
  1255  	tests := []struct {
  1256  		value    []string
  1257  		expected []string
  1258  	}{
  1259  		{[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
  1260  		{[]string{"a", "B", "c"}, []string{"a", "b", "c"}},
  1261  		{[]string{"A", "B", "C"}, []string{"a", "b", "c"}},
  1262  	}
  1263  
  1264  	for _, test := range tests {
  1265  		res := helpers.SliceToLower(test.value)
  1266  		for i, val := range res {
  1267  			if val != test.expected[i] {
  1268  				t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i])
  1269  			}
  1270  		}
  1271  	}
  1272  }
  1273  
  1274  func TestReplaceDivider(t *testing.T) {
  1275  	t.Parallel()
  1276  
  1277  	tests := []struct {
  1278  		content           string
  1279  		from              string
  1280  		to                string
  1281  		expectedContent   string
  1282  		expectedTruncated bool
  1283  	}{
  1284  		{"none", "a", "b", "none", false},
  1285  		{"summary <!--more--> content", "<!--more-->", "HUGO", "summary HUGO content", true},
  1286  		{"summary\n\ndivider", "divider", "HUGO", "summary\n\nHUGO", false},
  1287  		{"summary\n\ndivider\n\r", "divider", "HUGO", "summary\n\nHUGO\n\r", false},
  1288  	}
  1289  
  1290  	for i, test := range tests {
  1291  		replaced, truncated := replaceDivider([]byte(test.content), []byte(test.from), []byte(test.to))
  1292  
  1293  		if truncated != test.expectedTruncated {
  1294  			t.Fatalf("[%d] Expected truncated to be %t, was %t", i, test.expectedTruncated, truncated)
  1295  		}
  1296  
  1297  		if string(replaced) != test.expectedContent {
  1298  			t.Fatalf("[%d] Expected content to be %q, was %q", i, test.expectedContent, replaced)
  1299  		}
  1300  	}
  1301  }
  1302  
  1303  func BenchmarkReplaceDivider(b *testing.B) {
  1304  	divider := "HUGO_DIVIDER"
  1305  	from, to := []byte(divider), []byte("HUGO_REPLACED")
  1306  
  1307  	withDivider := make([][]byte, b.N)
  1308  	noDivider := make([][]byte, b.N)
  1309  
  1310  	for i := 0; i < b.N; i++ {
  1311  		withDivider[i] = []byte(strings.Repeat("Summary ", 5) + "\n" + divider + "\n" + strings.Repeat("Word ", 300))
  1312  		noDivider[i] = []byte(strings.Repeat("Word ", 300))
  1313  	}
  1314  
  1315  	b.ResetTimer()
  1316  	for i := 0; i < b.N; i++ {
  1317  		_, t1 := replaceDivider(withDivider[i], from, to)
  1318  		_, t2 := replaceDivider(noDivider[i], from, to)
  1319  		if !t1 {
  1320  			b.Fatal("Should be truncated")
  1321  		}
  1322  		if t2 {
  1323  			b.Fatal("Should not be truncated")
  1324  		}
  1325  	}
  1326  }
  1327  
  1328  func TestPagePaths(t *testing.T) {
  1329  	t.Parallel()
  1330  
  1331  	siteParmalinksSetting := map[string]string{
  1332  		"post": ":year/:month/:day/:title/",
  1333  	}
  1334  
  1335  	tests := []struct {
  1336  		content      string
  1337  		path         string
  1338  		hasPermalink bool
  1339  		expected     string
  1340  	}{
  1341  		{simplePage, "post/x.md", false, "post/x.html"},
  1342  		{simplePageWithURL, "post/x.md", false, "simple/url/index.html"},
  1343  		{simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"},
  1344  		{simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"},
  1345  		{UTF8Page, "post/x.md", false, "post/x.html"},
  1346  		{UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"},
  1347  		{UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"},
  1348  		{UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"},
  1349  	}
  1350  
  1351  	for _, test := range tests {
  1352  		cfg, fs := newTestCfg()
  1353  
  1354  		if test.hasPermalink {
  1355  			cfg.Set("permalinks", siteParmalinksSetting)
  1356  		}
  1357  
  1358  		writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content)
  1359  
  1360  		s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1361  		require.Len(t, s.RegularPages, 1)
  1362  
  1363  	}
  1364  }
  1365  
  1366  var pageWithDraftAndPublished = `---
  1367  title: broken
  1368  published: false
  1369  draft: true
  1370  ---
  1371  some content
  1372  `
  1373  
  1374  func TestDraftAndPublishedFrontMatterError(t *testing.T) {
  1375  	t.Parallel()
  1376  	s := newTestSite(t)
  1377  	_, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md")
  1378  	if err != ErrHasDraftAndPublished {
  1379  		t.Errorf("expected ErrHasDraftAndPublished, was %#v", err)
  1380  	}
  1381  }
  1382  
  1383  var pagesWithPublishedFalse = `---
  1384  title: okay
  1385  published: false
  1386  ---
  1387  some content
  1388  `
  1389  var pageWithPublishedTrue = `---
  1390  title: okay
  1391  published: true
  1392  ---
  1393  some content
  1394  `
  1395  
  1396  func TestPublishedFrontMatter(t *testing.T) {
  1397  	t.Parallel()
  1398  	s := newTestSite(t)
  1399  	p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md")
  1400  	if err != nil {
  1401  		t.Fatalf("err during parse: %s", err)
  1402  	}
  1403  	if !p.Draft {
  1404  		t.Errorf("expected true, got %t", p.Draft)
  1405  	}
  1406  	p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md")
  1407  	if err != nil {
  1408  		t.Fatalf("err during parse: %s", err)
  1409  	}
  1410  	if p.Draft {
  1411  		t.Errorf("expected false, got %t", p.Draft)
  1412  	}
  1413  }
  1414  
  1415  var pagesDraftTemplate = []string{`---
  1416  title: "okay"
  1417  draft: %t
  1418  ---
  1419  some content
  1420  `,
  1421  	`+++
  1422  title = "okay"
  1423  draft = %t
  1424  +++
  1425  
  1426  some content
  1427  `,
  1428  }
  1429  
  1430  func TestDraft(t *testing.T) {
  1431  	t.Parallel()
  1432  	s := newTestSite(t)
  1433  	for _, draft := range []bool{true, false} {
  1434  		for i, templ := range pagesDraftTemplate {
  1435  			pageContent := fmt.Sprintf(templ, draft)
  1436  			p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md")
  1437  			if err != nil {
  1438  				t.Fatalf("err during parse: %s", err)
  1439  			}
  1440  			if p.Draft != draft {
  1441  				t.Errorf("[%d] expected %t, got %t", i, draft, p.Draft)
  1442  			}
  1443  		}
  1444  	}
  1445  }
  1446  
  1447  var pagesParamsTemplate = []string{`+++
  1448  title = "okay"
  1449  draft = false
  1450  tags = [ "hugo", "web" ]
  1451  social= [
  1452    [ "a", "#" ],
  1453    [ "b", "#" ],
  1454  ]
  1455  +++
  1456  some content
  1457  `,
  1458  	`---
  1459  title: "okay"
  1460  draft: false
  1461  tags:
  1462    - hugo
  1463    - web
  1464  social:
  1465    - - a
  1466      - "#"
  1467    - - b
  1468      - "#"
  1469  ---
  1470  some content
  1471  `,
  1472  	`{
  1473  	"title": "okay",
  1474  	"draft": false,
  1475  	"tags": [ "hugo", "web" ],
  1476  	"social": [
  1477  		[ "a", "#" ],
  1478  		[ "b", "#" ]
  1479  	]
  1480  }
  1481  some content
  1482  `,
  1483  }
  1484  
  1485  func TestPageParams(t *testing.T) {
  1486  	t.Parallel()
  1487  	s := newTestSite(t)
  1488  	wantedMap := map[string]interface{}{
  1489  		"tags": []string{"hugo", "web"},
  1490  		// Issue #2752
  1491  		"social": []interface{}{
  1492  			[]interface{}{"a", "#"},
  1493  			[]interface{}{"b", "#"},
  1494  		},
  1495  	}
  1496  
  1497  	for i, c := range pagesParamsTemplate {
  1498  		p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
  1499  		require.NoError(t, err, "err during parse", "#%d", i)
  1500  		for key := range wantedMap {
  1501  			assert.Equal(t, wantedMap[key], p.params[key], "#%d", key)
  1502  		}
  1503  	}
  1504  }
  1505  
  1506  func TestTraverse(t *testing.T) {
  1507  	exampleParams := `---
  1508  rating: "5 stars"
  1509  tags:
  1510    - hugo
  1511    - web
  1512  social:
  1513    twitter: "@jxxf"
  1514    facebook: "https://example.com"
  1515  ---`
  1516  	t.Parallel()
  1517  	s := newTestSite(t)
  1518  	p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md")
  1519  
  1520  	topLevelKeyValue, _ := p.Param("rating")
  1521  	assert.Equal(t, "5 stars", topLevelKeyValue)
  1522  
  1523  	nestedStringKeyValue, _ := p.Param("social.twitter")
  1524  	assert.Equal(t, "@jxxf", nestedStringKeyValue)
  1525  
  1526  	nonexistentKeyValue, _ := p.Param("doesn't.exist")
  1527  	assert.Nil(t, nonexistentKeyValue)
  1528  }
  1529  
  1530  func TestPageSimpleMethods(t *testing.T) {
  1531  	t.Parallel()
  1532  	s := newTestSite(t)
  1533  	for i, this := range []struct {
  1534  		assertFunc func(p *Page) bool
  1535  	}{
  1536  		{func(p *Page) bool { return !p.IsNode() }},
  1537  		{func(p *Page) bool { return p.IsPage() }},
  1538  		{func(p *Page) bool { return p.Plain() == "Do Be Do Be Do" }},
  1539  		{func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }},
  1540  	} {
  1541  
  1542  		p, _ := s.NewPage("Test")
  1543  		p.workContent = []byte("<h1>Do Be Do Be Do</h1>")
  1544  		p.resetContent()
  1545  		if !this.assertFunc(p) {
  1546  			t.Errorf("[%d] Page method error", i)
  1547  		}
  1548  	}
  1549  }
  1550  
  1551  func TestIndexPageSimpleMethods(t *testing.T) {
  1552  	s := newTestSite(t)
  1553  	t.Parallel()
  1554  	for i, this := range []struct {
  1555  		assertFunc func(n *Page) bool
  1556  	}{
  1557  		{func(n *Page) bool { return n.IsNode() }},
  1558  		{func(n *Page) bool { return !n.IsPage() }},
  1559  		{func(n *Page) bool { return n.Scratch() != nil }},
  1560  		{func(n *Page) bool { return n.Hugo() != nil }},
  1561  	} {
  1562  
  1563  		n := s.newHomePage()
  1564  
  1565  		if !this.assertFunc(n) {
  1566  			t.Errorf("[%d] Node method error", i)
  1567  		}
  1568  	}
  1569  }
  1570  
  1571  func TestKind(t *testing.T) {
  1572  	t.Parallel()
  1573  	// Add tests for these constants to make sure they don't change
  1574  	require.Equal(t, "page", KindPage)
  1575  	require.Equal(t, "home", KindHome)
  1576  	require.Equal(t, "section", KindSection)
  1577  	require.Equal(t, "taxonomy", KindTaxonomy)
  1578  	require.Equal(t, "taxonomyTerm", KindTaxonomyTerm)
  1579  
  1580  }
  1581  
  1582  func TestTranslationKey(t *testing.T) {
  1583  	t.Parallel()
  1584  	assert := require.New(t)
  1585  	cfg, fs := newTestCfg()
  1586  
  1587  	writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n")
  1588  	writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n")
  1589  
  1590  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1591  
  1592  	require.Len(t, s.RegularPages, 2)
  1593  
  1594  	home, _ := s.Info.Home()
  1595  	assert.NotNil(home)
  1596  	assert.Equal("home", home.TranslationKey())
  1597  	assert.Equal("page/k1", s.RegularPages[0].TranslationKey())
  1598  	p2 := s.RegularPages[1]
  1599  
  1600  	assert.Equal("page/sect/simple", p2.TranslationKey())
  1601  
  1602  }
  1603  
  1604  func TestChompBOM(t *testing.T) {
  1605  	t.Parallel()
  1606  	const utf8BOM = "\xef\xbb\xbf"
  1607  
  1608  	cfg, fs := newTestCfg()
  1609  
  1610  	writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
  1611  
  1612  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
  1613  
  1614  	require.Len(t, s.RegularPages, 1)
  1615  
  1616  	p := s.RegularPages[0]
  1617  
  1618  	checkPageTitle(t, p, "Simple")
  1619  }
  1620  
  1621  // TODO(bep) this may be useful for other tests.
  1622  func compareObjects(a interface{}, b interface{}) bool {
  1623  	aStr := strings.Split(fmt.Sprintf("%v", a), "")
  1624  	sort.Strings(aStr)
  1625  
  1626  	bStr := strings.Split(fmt.Sprintf("%v", b), "")
  1627  	sort.Strings(bStr)
  1628  
  1629  	return strings.Join(aStr, "") == strings.Join(bStr, "")
  1630  }
  1631  
  1632  func TestShouldBuild(t *testing.T) {
  1633  	t.Parallel()
  1634  	var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
  1635  	var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
  1636  	var zero = time.Time{}
  1637  
  1638  	var publishSettings = []struct {
  1639  		buildFuture  bool
  1640  		buildExpired bool
  1641  		buildDrafts  bool
  1642  		draft        bool
  1643  		publishDate  time.Time
  1644  		expiryDate   time.Time
  1645  		out          bool
  1646  	}{
  1647  		// publishDate and expiryDate
  1648  		{false, false, false, false, zero, zero, true},
  1649  		{false, false, false, false, zero, future, true},
  1650  		{false, false, false, false, past, zero, true},
  1651  		{false, false, false, false, past, future, true},
  1652  		{false, false, false, false, past, past, false},
  1653  		{false, false, false, false, future, future, false},
  1654  		{false, false, false, false, future, past, false},
  1655  
  1656  		// buildFuture and buildExpired
  1657  		{false, true, false, false, past, past, true},
  1658  		{true, true, false, false, past, past, true},
  1659  		{true, false, false, false, past, past, false},
  1660  		{true, false, false, false, future, future, true},
  1661  		{true, true, false, false, future, future, true},
  1662  		{false, true, false, false, future, past, false},
  1663  
  1664  		// buildDrafts and draft
  1665  		{true, true, false, true, past, future, false},
  1666  		{true, true, true, true, past, future, true},
  1667  		{true, true, true, true, past, future, true},
  1668  	}
  1669  
  1670  	for _, ps := range publishSettings {
  1671  		s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
  1672  			ps.publishDate, ps.expiryDate)
  1673  		if s != ps.out {
  1674  			t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps)
  1675  		}
  1676  	}
  1677  }
  1678  
  1679  // "dot" in path: #1885 and #2110
  1680  // disablePathToLower regression: #3374
  1681  func TestPathIssues(t *testing.T) {
  1682  	t.Parallel()
  1683  	for _, disablePathToLower := range []bool{false, true} {
  1684  		for _, uglyURLs := range []bool{false, true} {
  1685  			t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) {
  1686  
  1687  				cfg, fs := newTestCfg()
  1688  				th := testHelper{cfg, fs, t}
  1689  
  1690  				cfg.Set("permalinks", map[string]string{
  1691  					"post": ":section/:title",
  1692  				})
  1693  
  1694  				cfg.Set("uglyURLs", uglyURLs)
  1695  				cfg.Set("disablePathToLower", disablePathToLower)
  1696  				cfg.Set("paginate", 1)
  1697  
  1698  				writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
  1699  				writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
  1700  					"<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>")
  1701  
  1702  				for i := 0; i < 3; i++ {
  1703  					writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)),
  1704  						fmt.Sprintf(`---
  1705  title: "test%d.dot"
  1706  tags:
  1707  - ".net"
  1708  ---
  1709  # doc1
  1710  *some content*`, i))
  1711  				}
  1712  
  1713  				writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"),
  1714  					fmt.Sprintf(`---
  1715  title: "testBlog"
  1716  tags:
  1717  - "Blog"
  1718  ---
  1719  # doc1
  1720  *some blog content*`))
  1721  
  1722  				s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
  1723  
  1724  				require.Len(t, s.RegularPages, 4)
  1725  
  1726  				pathFunc := func(s string) string {
  1727  					if uglyURLs {
  1728  						return strings.Replace(s, "/index.html", ".html", 1)
  1729  					}
  1730  					return s
  1731  				}
  1732  
  1733  				blog := "blog"
  1734  
  1735  				if disablePathToLower {
  1736  					blog = "Blog"
  1737  				}
  1738  
  1739  				th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content")
  1740  
  1741  				th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content")
  1742  
  1743  				if uglyURLs {
  1744  					th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`)
  1745  					th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`)
  1746  					th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`)
  1747  				} else {
  1748  					th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`)
  1749  					th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`)
  1750  					th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`)
  1751  					th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`)
  1752  
  1753  				}
  1754  
  1755  				p := s.RegularPages[0]
  1756  				if uglyURLs {
  1757  					require.Equal(t, "/post/test0.dot.html", p.RelPermalink())
  1758  				} else {
  1759  					require.Equal(t, "/post/test0.dot/", p.RelPermalink())
  1760  				}
  1761  
  1762  			})
  1763  		}
  1764  	}
  1765  }
  1766  
  1767  // https://github.com/gohugoio/hugo/issues/4675
  1768  func TestWordCountAndSimilarVsSummary(t *testing.T) {
  1769  
  1770  	t.Parallel()
  1771  	assert := require.New(t)
  1772  
  1773  	single := []string{"_default/single.html", `
  1774  WordCount: {{ .WordCount }}
  1775  FuzzyWordCount: {{ .FuzzyWordCount }}
  1776  ReadingTime: {{ .ReadingTime }}
  1777  Len Plain: {{ len .Plain }}
  1778  Len PlainWords: {{ len .PlainWords }}
  1779  Truncated: {{ .Truncated }}
  1780  Len Summary: {{ len .Summary }}
  1781  Len Content: {{ len .Content }}
  1782  `}
  1783  
  1784  	b := newTestSitesBuilder(t)
  1785  	b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`---
  1786  title: p1	
  1787  ---
  1788  
  1789  %s
  1790  
  1791  `, strings.Repeat("word ", 510)),
  1792  
  1793  		"p2.md", fmt.Sprintf(`---
  1794  title: p2
  1795  ---
  1796  This is a summary.
  1797  
  1798  <!--more-->
  1799  
  1800  %s
  1801  
  1802  `, strings.Repeat("word ", 310)),
  1803  		"p3.md", fmt.Sprintf(`---
  1804  title: p3
  1805  isCJKLanguage: true
  1806  ---
  1807  Summary: In Chinese, 好 means good.
  1808  
  1809  <!--more-->
  1810  
  1811  %s
  1812  
  1813  `, strings.Repeat("好", 200)),
  1814  		"p4.md", fmt.Sprintf(`---
  1815  title: p4
  1816  isCJKLanguage: false
  1817  ---
  1818  Summary: In Chinese, 好 means good.
  1819  
  1820  <!--more-->
  1821  
  1822  %s
  1823  
  1824  `, strings.Repeat("好", 200)),
  1825  
  1826  		"p5.md", fmt.Sprintf(`---
  1827  title: p4
  1828  isCJKLanguage: true
  1829  ---
  1830  Summary: In Chinese, 好 means good.
  1831  
  1832  %s
  1833  
  1834  `, strings.Repeat("好", 200)),
  1835  		"p6.md", fmt.Sprintf(`---
  1836  title: p4
  1837  isCJKLanguage: false
  1838  ---
  1839  Summary: In Chinese, 好 means good.
  1840  
  1841  %s
  1842  
  1843  `, strings.Repeat("好", 200)),
  1844  	)
  1845  
  1846  	b.CreateSites().Build(BuildCfg{})
  1847  
  1848  	assert.Equal(1, len(b.H.Sites))
  1849  	require.Len(t, b.H.Sites[0].RegularPages, 6)
  1850  
  1851  	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")
  1852  
  1853  	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")
  1854  
  1855  	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")
  1856  	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")
  1857  	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")
  1858  	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")
  1859  
  1860  }
  1861  
  1862  func TestScratchSite(t *testing.T) {
  1863  	t.Parallel()
  1864  
  1865  	b := newTestSitesBuilder(t)
  1866  	b.WithSimpleConfigFile().WithTemplatesAdded("index.html", `
  1867  {{ .Scratch.Set "b" "bv" }}
  1868  B: {{ .Scratch.Get "b" }}
  1869  `,
  1870  		"shortcodes/scratch.html", `
  1871  {{ .Scratch.Set "c" "cv" }}
  1872  C: {{ .Scratch.Get "c" }}
  1873  `,
  1874  	)
  1875  
  1876  	b.WithContentAdded("scratchme.md", `
  1877  ---
  1878  title: Scratch Me!
  1879  ---
  1880  
  1881  {{< scratch >}}
  1882  `)
  1883  	b.Build(BuildCfg{})
  1884  
  1885  	b.AssertFileContent("public/index.html", "B: bv")
  1886  	b.AssertFileContent("public/scratchme/index.html", "C: cv")
  1887  }
  1888  
  1889  func BenchmarkParsePage(b *testing.B) {
  1890  	s := newTestSite(b)
  1891  	f, _ := os.Open("testdata/redis.cn.md")
  1892  	var buf bytes.Buffer
  1893  	buf.ReadFrom(f)
  1894  	b.ResetTimer()
  1895  	for i := 0; i < b.N; i++ {
  1896  		page, _ := s.NewPage("bench")
  1897  		page.ReadFrom(bytes.NewReader(buf.Bytes()))
  1898  	}
  1899  }