From 5e7099256e148074dbc2be29d380a2b7c659f39c Mon Sep 17 00:00:00 2001 From: Joe Mooring Date: Sat, 15 Aug 2026 08:47:41 -0700 Subject: [PATCH] hugolib: Fix ReadingTime and FuzzyWordCount calculations Closes #15206 --- hugolib/page__content.go | 6 +- hugolib/page_test.go | 122 ++++++++++++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/hugolib/page__content.go b/hugolib/page__content.go index c89d6b614..725576f38 100644 --- a/hugolib/page__content.go +++ b/hugolib/page__content.go @@ -807,13 +807,13 @@ func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlai // TODO(bep) is set in a test. Fix that. if result.fuzzyWordCount == 0 { - result.fuzzyWordCount = (result.wordCount + 100) / 100 * 100 + result.fuzzyWordCount = (result.wordCount + 99) / 100 * 100 } if isCJKLanguage { - result.readingTime = (result.wordCount + 500) / 501 + result.readingTime = (result.wordCount + 499) / 500 } else { - result.readingTime = (result.wordCount + 212) / 213 + result.readingTime = (result.wordCount + 211) / 212 } rs.Value = result diff --git a/hugolib/page_test.go b/hugolib/page_test.go index cad453d5b..635358590 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -412,7 +412,8 @@ baseURL = "http://example.com/" p := b.H.Sites[0].RegularPages()[0] b.Assert(p.Summary(context.Background()), qt.Equals, template.HTML( - "

The best static site generator.1

")) + "

The best static site generator.1

", + )) cnt := content(p) b.Assert(cnt, qt.Equals, "

The best static site generator.1

\n
\n
\n
    \n
  1. \n

    Many people say so. ↩︎

    \n
  2. \n
\n
") @@ -730,7 +731,8 @@ This is **content**. Summary: {{ .Summary }}|Truncated: {{ .Truncated }}| Content: {{ .Content }}| -`).AssertFileContent("public/simple/index.html", +`).AssertFileContent( + "public/simple/index.html", "Summary:

This is summary.

|", "Truncated: true|", "Content:

This is summary.

\n

This is content.

|", @@ -1286,13 +1288,15 @@ AllTranslations: {{ range .AllTranslations }}{{ .Language.Lang }}|{{ end }}| ` b := Test(t, files) - b.AssertFileContent("public/en/sect/p1/index.html", + b.AssertFileContent( + "public/en/sect/p1/index.html", "TranslationKey: adfasdf|", "AllTranslations: en|nn||", "Translations: nn||", ) - b.AssertFileContent("public/nn/sect/p1/index.html", + b.AssertFileContent( + "public/nn/sect/p1/index.html", "TranslationKey: adfasdf|", "Translations: en||", "AllTranslations: en|nn||", @@ -1378,12 +1382,14 @@ Resources: {{ range .Resources }}{{ .RelPermalink }}|{{ .Content }}|{{ end }}| ` b := Test(t, files) - b.AssertFileContent("public/en/sect/mybundle_en/index.html", + b.AssertFileContent( + "public/en/sect/mybundle_en/index.html", "TranslationKey: adfasdf|", "Resources: /en/sect/mybundle_en/f1.txt|f1.en|/en/sect/mybundle_en/f2.txt|f2.en||", ) - b.AssertFileContent("public/nn/sect/mybundle_nn/index.html", + b.AssertFileContent( + "public/nn/sect/mybundle_nn/index.html", "TranslationKey: adfasdf|", "Title: mybundle nn|TranslationKey: adfasdf|\nResources: /en/sect/mybundle_en/f1.txt|f1.en|/nn/sect/mybundle_nn/f2.nn.txt|f2.nn||", ) @@ -1497,31 +1503,37 @@ CONTENT:{{ .Content }} ` b := Test(t, files) - b.AssertFileContent("public/page-md-shortcode/index.html", + b.AssertFileContent( + "public/page-md-shortcode/index.html", "SUMMARY:

This is a a shortcode.

:END", "CONTENT:

This is a a shortcode.

\n\n

Content.

\n", ) - b.AssertFileContent("public/page-md-shortcode-same-line/index.html", + b.AssertFileContent( + "public/page-md-shortcode-same-line/index.html", "SUMMARY:

This is a a shortcode

:END", "CONTENT:

This is a a shortcode

\n\n

Same line.

\n", ) - b.AssertFileContent("public/page-md-shortcode-same-line-after/index.html", + b.AssertFileContent( + "public/page-md-shortcode-same-line-after/index.html", "SUMMARY:

Summary

:END", "CONTENT:

Summary

\n\na shortcode", ) - b.AssertFileContent("public/page-org-shortcode/index.html", + b.AssertFileContent( + "public/page-org-shortcode/index.html", "SUMMARY:

\nThis is a a shortcode.\n

:END", "CONTENT:

\nThis is a a shortcode.\n

\n

\nContent.\t\n

\n", ) - b.AssertFileContent("public/page-org-variant1/index.html", + b.AssertFileContent( + "public/page-org-variant1/index.html", "SUMMARY:

\nSummary.\n

:END", "CONTENT:

\nSummary.\n

\n

\nContent.\t\n

\n", ) - b.AssertFileContent("public/page-md-only-shortcode/index.html", + b.AssertFileContent( + "public/page-md-only-shortcode/index.html", "SUMMARY:a shortcode:END", "CONTENT:a shortcode\n\na shortcode\n", ) @@ -1731,7 +1743,8 @@ c: {{ .Scratch.Get "c" }} b := Test(t, files) - b.AssertFileContent("public/index.html", + b.AssertFileContent( + "public/index.html", ".Scratch eq .Store: true", "a: b", "c: d", @@ -1928,7 +1941,8 @@ Site: {{ site.Store.Get "Site" }}| b := TestRunning(t, files) - b.AssertFileContent("public/index.html", + b.AssertFileContent( + "public/index.html", ` Shortcode: sh-Home| Page: p-Home| @@ -1939,7 +1953,8 @@ Hugo: h-Home| b.EditFileReplaceAll("content/_index.md", "Home", "Homer").Build() - b.AssertFileContent("public/index.html", + b.AssertFileContent( + "public/index.html", ` Shortcode: sh-Homer| Page: p-Homer| @@ -2149,7 +2164,8 @@ EF b := Test(t, files) - b.AssertFileContent("public/s1/p1/index.html", + b.AssertFileContent( + "public/s1/p1/index.html", "ab|ab|ab", "cD|cD|cD", "EF|EF|EF", @@ -2368,3 +2384,77 @@ All. myothersection, _ := s.GetPage("myothersection") // backed by a content file. check(myothersection, true) } + +// See issue 15206. +func TestReadingTimeAndFuzzyWordCountBoundaries(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ["home", "section", "taxonomy", "term", "rss", "sitemap"] +-- content/p99.md -- +--- +title: p99 +--- +` + strings.Repeat("word ", 99) + ` +-- content/p100.md -- +--- +title: p100 +--- +` + strings.Repeat("word ", 100) + ` +-- content/p101.md -- +--- +title: p101 +--- +` + strings.Repeat("word ", 101) + ` +-- content/p211.md -- +--- +title: p211 +--- +` + strings.Repeat("word ", 211) + ` +-- content/p212.md -- +--- +title: p212 +--- +` + strings.Repeat("word ", 212) + ` +-- content/p213.md -- +--- +title: p213 +--- +` + strings.Repeat("word ", 213) + ` +-- content/p499.md -- +--- +title: p499 +isCJKLanguage: true +--- +` + strings.Repeat("你", 499) + ` +-- content/p500.md -- +--- +title: p500 +isCJKLanguage: true +--- +` + strings.Repeat("你", 500) + ` +-- content/p501.md -- +--- +title: p501 +isCJKLanguage: true +--- +` + strings.Repeat("你", 501) + ` +-- layouts/page.html -- +{{ .WordCount }}|{{ .FuzzyWordCount }}|{{ .ReadingTime }} +` + + b := Test(t, files) + + b.AssertFileContent("public/p99/index.html", "99|100|1") + b.AssertFileContent("public/p100/index.html", "100|100|1") + b.AssertFileContent("public/p101/index.html", "101|200|1") + + b.AssertFileContent("public/p211/index.html", "211|300|1") + b.AssertFileContent("public/p212/index.html", "212|300|1") + b.AssertFileContent("public/p213/index.html", "213|300|2") + + b.AssertFileContent("public/p499/index.html", "499|500|1") + b.AssertFileContent("public/p500/index.html", "500|500|1") + b.AssertFileContent("public/p501/index.html", "501|600|2") +}