site_test.go (34199B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package hugolib
15
16 import (
17 "encoding/json"
18 "fmt"
19 "io/ioutil"
20 "os"
21 "path/filepath"
22 "strings"
23 "testing"
24
25 "github.com/gobuffalo/flect"
26 "github.com/gohugoio/hugo/config"
27 "github.com/gohugoio/hugo/publisher"
28
29 qt "github.com/frankban/quicktest"
30 "github.com/gohugoio/hugo/deps"
31 "github.com/gohugoio/hugo/resources/page"
32 )
33
34 const (
35 templateMissingFunc = "{{ .Title | funcdoesnotexists }}"
36 templateWithURLAbs = "<a href=\"/foobar.jpg\">Going</a>"
37 )
38
39 func TestRenderWithInvalidTemplate(t *testing.T) {
40 t.Parallel()
41 cfg, fs := newTestCfg()
42
43 writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
44
45 withTemplate := createWithTemplateFromNameValues("missing", templateMissingFunc)
46
47 buildSingleSiteExpected(t, true, false, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
48 }
49
50 func TestDraftAndFutureRender(t *testing.T) {
51 t.Parallel()
52 sources := [][2]string{
53 {filepath.FromSlash("sect/doc1.md"), "---\ntitle: doc1\ndraft: true\npublishdate: \"2414-05-29\"\n---\n# doc1\n*some content*"},
54 {filepath.FromSlash("sect/doc2.md"), "---\ntitle: doc2\ndraft: true\npublishdate: \"2012-05-29\"\n---\n# doc2\n*some content*"},
55 {filepath.FromSlash("sect/doc3.md"), "---\ntitle: doc3\ndraft: false\npublishdate: \"2414-05-29\"\n---\n# doc3\n*some content*"},
56 {filepath.FromSlash("sect/doc4.md"), "---\ntitle: doc4\ndraft: false\npublishdate: \"2012-05-29\"\n---\n# doc4\n*some content*"},
57 }
58
59 siteSetup := func(t *testing.T, configKeyValues ...any) *Site {
60 cfg, fs := newTestCfg()
61
62 cfg.Set("baseURL", "http://auth/bub")
63
64 for i := 0; i < len(configKeyValues); i += 2 {
65 cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
66 }
67
68 for _, src := range sources {
69 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
70 }
71
72 return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
73 }
74
75 // Testing Defaults.. Only draft:true and publishDate in the past should be rendered
76 s := siteSetup(t)
77 if len(s.RegularPages()) != 1 {
78 t.Fatal("Draft or Future dated content published unexpectedly")
79 }
80
81 // only publishDate in the past should be rendered
82 s = siteSetup(t, "buildDrafts", true)
83 if len(s.RegularPages()) != 2 {
84 t.Fatal("Future Dated Posts published unexpectedly")
85 }
86
87 // drafts should not be rendered, but all dates should
88 s = siteSetup(t,
89 "buildDrafts", false,
90 "buildFuture", true)
91
92 if len(s.RegularPages()) != 2 {
93 t.Fatal("Draft posts published unexpectedly")
94 }
95
96 // all 4 should be included
97 s = siteSetup(t,
98 "buildDrafts", true,
99 "buildFuture", true)
100
101 if len(s.RegularPages()) != 4 {
102 t.Fatal("Drafts or Future posts not included as expected")
103 }
104 }
105
106 func TestFutureExpirationRender(t *testing.T) {
107 t.Parallel()
108 sources := [][2]string{
109 {filepath.FromSlash("sect/doc3.md"), "---\ntitle: doc1\nexpirydate: \"2400-05-29\"\n---\n# doc1\n*some content*"},
110 {filepath.FromSlash("sect/doc4.md"), "---\ntitle: doc2\nexpirydate: \"2000-05-29\"\n---\n# doc2\n*some content*"},
111 }
112
113 siteSetup := func(t *testing.T) *Site {
114 cfg, fs := newTestCfg()
115 cfg.Set("baseURL", "http://auth/bub")
116
117 for _, src := range sources {
118 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
119 }
120
121 return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
122 }
123
124 s := siteSetup(t)
125
126 if len(s.AllPages()) != 1 {
127 if len(s.RegularPages()) > 1 {
128 t.Fatal("Expired content published unexpectedly")
129 }
130
131 if len(s.RegularPages()) < 1 {
132 t.Fatal("Valid content expired unexpectedly")
133 }
134 }
135
136 if s.AllPages()[0].Title() == "doc2" {
137 t.Fatal("Expired content published unexpectedly")
138 }
139 }
140
141 func TestLastChange(t *testing.T) {
142 t.Parallel()
143
144 cfg, fs := newTestCfg()
145 c := qt.New(t)
146
147 writeSource(t, fs, filepath.Join("content", "sect/doc1.md"), "---\ntitle: doc1\nweight: 1\ndate: 2014-05-29\n---\n# doc1\n*some content*")
148 writeSource(t, fs, filepath.Join("content", "sect/doc2.md"), "---\ntitle: doc2\nweight: 2\ndate: 2015-05-29\n---\n# doc2\n*some content*")
149 writeSource(t, fs, filepath.Join("content", "sect/doc3.md"), "---\ntitle: doc3\nweight: 3\ndate: 2017-05-29\n---\n# doc3\n*some content*")
150 writeSource(t, fs, filepath.Join("content", "sect/doc4.md"), "---\ntitle: doc4\nweight: 4\ndate: 2016-05-29\n---\n# doc4\n*some content*")
151 writeSource(t, fs, filepath.Join("content", "sect/doc5.md"), "---\ntitle: doc5\nweight: 3\n---\n# doc5\n*some content*")
152
153 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
154
155 c.Assert(s.Info.LastChange().IsZero(), qt.Equals, false)
156 c.Assert(s.Info.LastChange().Year(), qt.Equals, 2017)
157 }
158
159 // Issue #_index
160 func TestPageWithUnderScoreIndexInFilename(t *testing.T) {
161 t.Parallel()
162
163 cfg, fs := newTestCfg()
164 c := qt.New(t)
165
166 writeSource(t, fs, filepath.Join("content", "sect/my_index_file.md"), "---\ntitle: doc1\nweight: 1\ndate: 2014-05-29\n---\n# doc1\n*some content*")
167
168 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
169
170 c.Assert(len(s.RegularPages()), qt.Equals, 1)
171 }
172
173 // Issue #957
174 func TestCrossrefs(t *testing.T) {
175 t.Parallel()
176 for _, uglyURLs := range []bool{true, false} {
177 for _, relative := range []bool{true, false} {
178 doTestCrossrefs(t, relative, uglyURLs)
179 }
180 }
181 }
182
183 func doTestCrossrefs(t *testing.T, relative, uglyURLs bool) {
184 c := qt.New(t)
185
186 baseURL := "http://foo/bar"
187
188 var refShortcode string
189 var expectedBase string
190 var expectedURLSuffix string
191 var expectedPathSuffix string
192
193 if relative {
194 refShortcode = "relref"
195 expectedBase = "/bar"
196 } else {
197 refShortcode = "ref"
198 expectedBase = baseURL
199 }
200
201 if uglyURLs {
202 expectedURLSuffix = ".html"
203 expectedPathSuffix = ".html"
204 } else {
205 expectedURLSuffix = "/"
206 expectedPathSuffix = "/index.html"
207 }
208
209 doc3Slashed := filepath.FromSlash("/sect/doc3.md")
210
211 sources := [][2]string{
212 {
213 filepath.FromSlash("sect/doc1.md"),
214 fmt.Sprintf(`Ref 2: {{< %s "sect/doc2.md" >}}`, refShortcode),
215 },
216 // Issue #1148: Make sure that no P-tags is added around shortcodes.
217 {
218 filepath.FromSlash("sect/doc2.md"),
219 fmt.Sprintf(`**Ref 1:**
220
221 {{< %s "sect/doc1.md" >}}
222
223 THE END.`, refShortcode),
224 },
225 // Issue #1753: Should not add a trailing newline after shortcode.
226 {
227 filepath.FromSlash("sect/doc3.md"),
228 fmt.Sprintf(`**Ref 1:** {{< %s "sect/doc3.md" >}}.`, refShortcode),
229 },
230 // Issue #3703
231 {
232 filepath.FromSlash("sect/doc4.md"),
233 fmt.Sprintf(`**Ref 1:** {{< %s "%s" >}}.`, refShortcode, doc3Slashed),
234 },
235 }
236
237 cfg, fs := newTestCfg()
238
239 cfg.Set("baseURL", baseURL)
240 cfg.Set("uglyURLs", uglyURLs)
241 cfg.Set("verbose", true)
242
243 for _, src := range sources {
244 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
245 }
246
247 s := buildSingleSite(
248 t,
249 deps.DepsCfg{
250 Fs: fs,
251 Cfg: cfg,
252 WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}"),
253 },
254 BuildCfg{})
255
256 c.Assert(len(s.RegularPages()), qt.Equals, 4)
257
258 th := newTestHelper(s.Cfg, s.Fs, t)
259
260 tests := []struct {
261 doc string
262 expected string
263 }{
264 {filepath.FromSlash(fmt.Sprintf("public/sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)},
265 {filepath.FromSlash(fmt.Sprintf("public/sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n%s/sect/doc1%s\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)},
266 {filepath.FromSlash(fmt.Sprintf("public/sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong> %s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
267 {filepath.FromSlash(fmt.Sprintf("public/sect/doc4%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong> %s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
268 }
269
270 for _, test := range tests {
271 th.assertFileContent(test.doc, test.expected)
272 }
273 }
274
275 // Issue #939
276 // Issue #1923
277 func TestShouldAlwaysHaveUglyURLs(t *testing.T) {
278 t.Parallel()
279 for _, uglyURLs := range []bool{true, false} {
280 doTestShouldAlwaysHaveUglyURLs(t, uglyURLs)
281 }
282 }
283
284 func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
285 cfg, fs := newTestCfg()
286 c := qt.New(t)
287
288 cfg.Set("verbose", true)
289 cfg.Set("baseURL", "http://auth/bub")
290 cfg.Set("uglyURLs", uglyURLs)
291
292 sources := [][2]string{
293 {filepath.FromSlash("sect/doc1.md"), "---\nmarkup: markdown\n---\n# title\nsome *content*"},
294 {filepath.FromSlash("sect/doc2.md"), "---\nurl: /ugly.html\nmarkup: markdown\n---\n# title\ndoc2 *content*"},
295 }
296
297 for _, src := range sources {
298 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
299 }
300
301 writeSource(t, fs, filepath.Join("layouts", "index.html"), "Home Sweet {{ if.IsHome }}Home{{ end }}.")
302 writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}{{ if.IsHome }}This is not home!{{ end }}")
303 writeSource(t, fs, filepath.Join("layouts", "404.html"), "Page Not Found.{{ if.IsHome }}This is not home!{{ end }}")
304 writeSource(t, fs, filepath.Join("layouts", "rss.xml"), "<root>RSS</root>")
305 writeSource(t, fs, filepath.Join("layouts", "sitemap.xml"), "<root>SITEMAP</root>")
306
307 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
308
309 var expectedPagePath string
310 if uglyURLs {
311 expectedPagePath = "public/sect/doc1.html"
312 } else {
313 expectedPagePath = "public/sect/doc1/index.html"
314 }
315
316 tests := []struct {
317 doc string
318 expected string
319 }{
320 {filepath.FromSlash("public/index.html"), "Home Sweet Home."},
321 {filepath.FromSlash(expectedPagePath), "<h1 id=\"title\">title</h1>\n<p>some <em>content</em></p>\n"},
322 {filepath.FromSlash("public/404.html"), "Page Not Found."},
323 {filepath.FromSlash("public/index.xml"), "<root>RSS</root>"},
324 {filepath.FromSlash("public/sitemap.xml"), "<root>SITEMAP</root>"},
325 // Issue #1923
326 {filepath.FromSlash("public/ugly.html"), "<h1 id=\"title\">title</h1>\n<p>doc2 <em>content</em></p>\n"},
327 }
328
329 for _, p := range s.RegularPages() {
330 c.Assert(p.IsHome(), qt.Equals, false)
331 }
332
333 for _, test := range tests {
334 content := readWorkingDir(t, fs, test.doc)
335
336 if content != test.expected {
337 t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
338 }
339 }
340 }
341
342 // Issue #3355
343 func TestShouldNotWriteZeroLengthFilesToDestination(t *testing.T) {
344 cfg, fs := newTestCfg()
345
346 writeSource(t, fs, filepath.Join("content", "simple.html"), "simple")
347 writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
348 writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "")
349
350 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
351 th := newTestHelper(s.Cfg, s.Fs, t)
352
353 th.assertFileNotExist(filepath.Join("public", "index.html"))
354 }
355
356 func TestMainSections(t *testing.T) {
357 c := qt.New(t)
358 for _, paramSet := range []bool{false, true} {
359 c.Run(fmt.Sprintf("param-%t", paramSet), func(c *qt.C) {
360 v := config.NewWithTestDefaults()
361 if paramSet {
362 v.Set("params", map[string]any{
363 "mainSections": []string{"a1", "a2"},
364 })
365 }
366
367 b := newTestSitesBuilder(c).WithViper(v)
368
369 for i := 0; i < 20; i++ {
370 b.WithContent(fmt.Sprintf("page%d.md", i), `---
371 title: "Page"
372 ---
373 `)
374 }
375
376 for i := 0; i < 5; i++ {
377 b.WithContent(fmt.Sprintf("blog/page%d.md", i), `---
378 title: "Page"
379 tags: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
380 ---
381 `)
382 }
383
384 for i := 0; i < 3; i++ {
385 b.WithContent(fmt.Sprintf("docs/page%d.md", i), `---
386 title: "Page"
387 ---
388 `)
389 }
390
391 b.WithTemplates("index.html", `
392 mainSections: {{ .Site.Params.mainSections }}
393
394 {{ range (where .Site.RegularPages "Type" "in" .Site.Params.mainSections) }}
395 Main section page: {{ .RelPermalink }}
396 {{ end }}
397 `)
398
399 b.Build(BuildCfg{})
400
401 if paramSet {
402 b.AssertFileContent("public/index.html", "mainSections: [a1 a2]")
403 } else {
404 b.AssertFileContent("public/index.html", "mainSections: [blog]", "Main section page: /blog/page3/")
405 }
406 })
407 }
408 }
409
410 // Issue #1176
411 func TestSectionNaming(t *testing.T) {
412 for _, canonify := range []bool{true, false} {
413 for _, uglify := range []bool{true, false} {
414 for _, pluralize := range []bool{true, false} {
415 canonify := canonify
416 uglify := uglify
417 pluralize := pluralize
418 t.Run(fmt.Sprintf("canonify=%t,uglify=%t,pluralize=%t", canonify, uglify, pluralize), func(t *testing.T) {
419 t.Parallel()
420 doTestSectionNaming(t, canonify, uglify, pluralize)
421 })
422 }
423 }
424 }
425 }
426
427 func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
428 c := qt.New(t)
429
430 var expectedPathSuffix string
431
432 if uglify {
433 expectedPathSuffix = ".html"
434 } else {
435 expectedPathSuffix = "/index.html"
436 }
437
438 sources := [][2]string{
439 {filepath.FromSlash("sect/doc1.html"), "doc1"},
440 // Add one more page to sect to make sure sect is picked in mainSections
441 {filepath.FromSlash("sect/sect.html"), "sect"},
442 {filepath.FromSlash("Fish and Chips/doc2.html"), "doc2"},
443 {filepath.FromSlash("ラーメン/doc3.html"), "doc3"},
444 }
445
446 cfg, fs := newTestCfg()
447
448 cfg.Set("baseURL", "http://auth/sub/")
449 cfg.Set("uglyURLs", uglify)
450 cfg.Set("pluralizeListTitles", pluralize)
451 cfg.Set("canonifyURLs", canonify)
452
453 for _, src := range sources {
454 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
455 }
456
457 writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
458 writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{ .Kind }}|{{.Title}}")
459
460 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
461
462 mainSections, err := s.Info.Param("mainSections")
463 c.Assert(err, qt.IsNil)
464 c.Assert(mainSections, qt.DeepEquals, []string{"sect"})
465
466 th := newTestHelper(s.Cfg, s.Fs, t)
467 tests := []struct {
468 doc string
469 pluralAware bool
470 expected string
471 }{
472 {filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), false, "doc1"},
473 {filepath.FromSlash(fmt.Sprintf("sect%s", expectedPathSuffix)), true, "Sect"},
474 {filepath.FromSlash(fmt.Sprintf("fish-and-chips/doc2%s", expectedPathSuffix)), false, "doc2"},
475 {filepath.FromSlash(fmt.Sprintf("fish-and-chips%s", expectedPathSuffix)), true, "Fish and Chips"},
476 {filepath.FromSlash(fmt.Sprintf("ラーメン/doc3%s", expectedPathSuffix)), false, "doc3"},
477 {filepath.FromSlash(fmt.Sprintf("ラーメン%s", expectedPathSuffix)), true, "ラーメン"},
478 }
479
480 for _, test := range tests {
481
482 if test.pluralAware && pluralize {
483 test.expected = flect.Pluralize(test.expected)
484 }
485
486 th.assertFileContent(filepath.Join("public", test.doc), test.expected)
487 }
488 }
489
490 func TestAbsURLify(t *testing.T) {
491 t.Parallel()
492 sources := [][2]string{
493 {filepath.FromSlash("sect/doc1.html"), "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
494 {filepath.FromSlash("blue/doc2.html"), "---\nf: t\n---\n<!doctype html><html><body>more content</body></html>"},
495 }
496 for _, baseURL := range []string{"http://auth/bub", "http://base", "//base"} {
497 for _, canonify := range []bool{true, false} {
498
499 cfg, fs := newTestCfg()
500
501 cfg.Set("uglyURLs", true)
502 cfg.Set("canonifyURLs", canonify)
503 cfg.Set("baseURL", baseURL)
504
505 for _, src := range sources {
506 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
507 }
508
509 writeSource(t, fs, filepath.Join("layouts", "blue/single.html"), templateWithURLAbs)
510
511 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
512 th := newTestHelper(s.Cfg, s.Fs, t)
513
514 tests := []struct {
515 file, expected string
516 }{
517 {"public/blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"},
518 {"public/sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
519 }
520
521 for _, test := range tests {
522
523 expected := test.expected
524
525 if strings.Contains(expected, "%s") {
526 expected = fmt.Sprintf(expected, baseURL)
527 }
528
529 if !canonify {
530 expected = strings.Replace(expected, baseURL, "", -1)
531 }
532
533 th.assertFileContent(test.file, expected)
534
535 }
536 }
537 }
538 }
539
540 var weightedPage1 = `+++
541 weight = "2"
542 title = "One"
543 my_param = "foo"
544 my_date = 1979-05-27T07:32:00Z
545 +++
546 Front Matter with Ordered Pages`
547
548 var weightedPage2 = `+++
549 weight = "6"
550 title = "Two"
551 publishdate = "2012-03-05"
552 my_param = "foo"
553 +++
554 Front Matter with Ordered Pages 2`
555
556 var weightedPage3 = `+++
557 weight = "4"
558 title = "Three"
559 date = "2012-04-06"
560 publishdate = "2012-04-06"
561 my_param = "bar"
562 only_one = "yes"
563 my_date = 2010-05-27T07:32:00Z
564 +++
565 Front Matter with Ordered Pages 3`
566
567 var weightedPage4 = `+++
568 weight = "4"
569 title = "Four"
570 date = "2012-01-01"
571 publishdate = "2012-01-01"
572 my_param = "baz"
573 my_date = 2010-05-27T07:32:00Z
574 summary = "A _custom_ summary"
575 categories = [ "hugo" ]
576 +++
577 Front Matter with Ordered Pages 4. This is longer content`
578
579 var weightedPage5 = `+++
580 weight = "5"
581 title = "Five"
582
583 [_build]
584 render = "never"
585 +++
586 Front Matter with Ordered Pages 5`
587
588 var weightedSources = [][2]string{
589 {filepath.FromSlash("sect/doc1.md"), weightedPage1},
590 {filepath.FromSlash("sect/doc2.md"), weightedPage2},
591 {filepath.FromSlash("sect/doc3.md"), weightedPage3},
592 {filepath.FromSlash("sect/doc4.md"), weightedPage4},
593 {filepath.FromSlash("sect/doc5.md"), weightedPage5},
594 }
595
596 func TestOrderedPages(t *testing.T) {
597 t.Parallel()
598 cfg, fs := newTestCfg()
599 cfg.Set("baseURL", "http://auth/bub")
600
601 for _, src := range weightedSources {
602 writeSource(t, fs, filepath.Join("content", src[0]), src[1])
603 }
604
605 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
606
607 if s.getPage(page.KindSection, "sect").Pages()[1].Title() != "Three" || s.getPage(page.KindSection, "sect").Pages()[2].Title() != "Four" {
608 t.Error("Pages in unexpected order.")
609 }
610
611 bydate := s.RegularPages().ByDate()
612
613 if bydate[0].Title() != "One" {
614 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bydate[0].Title())
615 }
616
617 rev := bydate.Reverse()
618 if rev[0].Title() != "Three" {
619 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].Title())
620 }
621
622 bypubdate := s.RegularPages().ByPublishDate()
623
624 if bypubdate[0].Title() != "One" {
625 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].Title())
626 }
627
628 rbypubdate := bypubdate.Reverse()
629 if rbypubdate[0].Title() != "Three" {
630 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].Title())
631 }
632
633 bylength := s.RegularPages().ByLength()
634 if bylength[0].Title() != "One" {
635 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].Title())
636 }
637
638 rbylength := bylength.Reverse()
639 if rbylength[0].Title() != "Four" {
640 t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Four", rbylength[0].Title())
641 }
642 }
643
644 var groupedSources = [][2]string{
645 {filepath.FromSlash("sect1/doc1.md"), weightedPage1},
646 {filepath.FromSlash("sect1/doc2.md"), weightedPage2},
647 {filepath.FromSlash("sect2/doc3.md"), weightedPage3},
648 {filepath.FromSlash("sect3/doc4.md"), weightedPage4},
649 }
650
651 func TestGroupedPages(t *testing.T) {
652 t.Parallel()
653 defer func() {
654 if r := recover(); r != nil {
655 fmt.Println("Recovered in f", r)
656 }
657 }()
658
659 cfg, fs := newTestCfg()
660 cfg.Set("baseURL", "http://auth/bub")
661
662 writeSourcesToSource(t, "content", fs, groupedSources...)
663 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
664
665 rbysection, err := s.RegularPages().GroupBy("Section", "desc")
666 if err != nil {
667 t.Fatalf("Unable to make PageGroup array: %s", err)
668 }
669
670 if rbysection[0].Key != "sect3" {
671 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "sect3", rbysection[0].Key)
672 }
673 if rbysection[1].Key != "sect2" {
674 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "sect2", rbysection[1].Key)
675 }
676 if rbysection[2].Key != "sect1" {
677 t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect1", rbysection[2].Key)
678 }
679 if rbysection[0].Pages[0].Title() != "Four" {
680 t.Errorf("PageGroup has an unexpected page. First group's pages should have '%s', got '%s'", "Four", rbysection[0].Pages[0].Title())
681 }
682 if len(rbysection[2].Pages) != 2 {
683 t.Errorf("PageGroup has unexpected number of pages. Third group should have '%d' pages, got '%d' pages", 2, len(rbysection[2].Pages))
684 }
685
686 bytype, err := s.RegularPages().GroupBy("Type", "asc")
687 if err != nil {
688 t.Fatalf("Unable to make PageGroup array: %s", err)
689 }
690 if bytype[0].Key != "sect1" {
691 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "sect1", bytype[0].Key)
692 }
693 if bytype[1].Key != "sect2" {
694 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "sect2", bytype[1].Key)
695 }
696 if bytype[2].Key != "sect3" {
697 t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect3", bytype[2].Key)
698 }
699 if bytype[2].Pages[0].Title() != "Four" {
700 t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].Title())
701 }
702 if len(bytype[0].Pages) != 2 {
703 t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(bytype[2].Pages))
704 }
705
706 bydate, err := s.RegularPages().GroupByDate("2006-01", "asc")
707 if err != nil {
708 t.Fatalf("Unable to make PageGroup array: %s", err)
709 }
710 if bydate[0].Key != "0001-01" {
711 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "0001-01", bydate[0].Key)
712 }
713 if bydate[1].Key != "2012-01" {
714 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "2012-01", bydate[1].Key)
715 }
716
717 bypubdate, err := s.RegularPages().GroupByPublishDate("2006")
718 if err != nil {
719 t.Fatalf("Unable to make PageGroup array: %s", err)
720 }
721 if bypubdate[0].Key != "2012" {
722 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "2012", bypubdate[0].Key)
723 }
724 if bypubdate[1].Key != "0001" {
725 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "0001", bypubdate[1].Key)
726 }
727 if bypubdate[0].Pages[0].Title() != "Three" {
728 t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].Title())
729 }
730 if len(bypubdate[0].Pages) != 3 {
731 t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 3, len(bypubdate[0].Pages))
732 }
733
734 byparam, err := s.RegularPages().GroupByParam("my_param", "desc")
735 if err != nil {
736 t.Fatalf("Unable to make PageGroup array: %s", err)
737 }
738 if byparam[0].Key != "foo" {
739 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "foo", byparam[0].Key)
740 }
741 if byparam[1].Key != "baz" {
742 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "baz", byparam[1].Key)
743 }
744 if byparam[2].Key != "bar" {
745 t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "bar", byparam[2].Key)
746 }
747 if byparam[2].Pages[0].Title() != "Three" {
748 t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].Title())
749 }
750 if len(byparam[0].Pages) != 2 {
751 t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byparam[0].Pages))
752 }
753
754 _, err = s.RegularPages().GroupByParam("not_exist")
755 if err == nil {
756 t.Errorf("GroupByParam didn't return an expected error")
757 }
758
759 byOnlyOneParam, err := s.RegularPages().GroupByParam("only_one")
760 if err != nil {
761 t.Fatalf("Unable to make PageGroup array: %s", err)
762 }
763 if len(byOnlyOneParam) != 1 {
764 t.Errorf("PageGroup array has unexpected elements. Group length should be '%d', got '%d'", 1, len(byOnlyOneParam))
765 }
766 if byOnlyOneParam[0].Key != "yes" {
767 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "yes", byOnlyOneParam[0].Key)
768 }
769
770 byParamDate, err := s.RegularPages().GroupByParamDate("my_date", "2006-01")
771 if err != nil {
772 t.Fatalf("Unable to make PageGroup array: %s", err)
773 }
774 if byParamDate[0].Key != "2010-05" {
775 t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "2010-05", byParamDate[0].Key)
776 }
777 if byParamDate[1].Key != "1979-05" {
778 t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "1979-05", byParamDate[1].Key)
779 }
780 if byParamDate[1].Pages[0].Title() != "One" {
781 t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].Title())
782 }
783 if len(byParamDate[0].Pages) != 2 {
784 t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byParamDate[2].Pages))
785 }
786 }
787
788 var pageWithWeightedTaxonomies1 = `+++
789 tags = [ "a", "b", "c" ]
790 tags_weight = 22
791 categories = ["d"]
792 title = "foo"
793 categories_weight = 44
794 +++
795 Front Matter with weighted tags and categories`
796
797 var pageWithWeightedTaxonomies2 = `+++
798 tags = "a"
799 tags_weight = 33
800 title = "bar"
801 categories = [ "d", "e" ]
802 categories_weight = 11.0
803 alias = "spf13"
804 date = 1979-05-27T07:32:00Z
805 +++
806 Front Matter with weighted tags and categories`
807
808 var pageWithWeightedTaxonomies3 = `+++
809 title = "bza"
810 categories = [ "e" ]
811 categories_weight = 11
812 alias = "spf13"
813 date = 2010-05-27T07:32:00Z
814 +++
815 Front Matter with weighted tags and categories`
816
817 func TestWeightedTaxonomies(t *testing.T) {
818 t.Parallel()
819 sources := [][2]string{
820 {filepath.FromSlash("sect/doc1.md"), pageWithWeightedTaxonomies2},
821 {filepath.FromSlash("sect/doc2.md"), pageWithWeightedTaxonomies1},
822 {filepath.FromSlash("sect/doc3.md"), pageWithWeightedTaxonomies3},
823 }
824 taxonomies := make(map[string]string)
825
826 taxonomies["tag"] = "tags"
827 taxonomies["category"] = "categories"
828
829 cfg, fs := newTestCfg()
830
831 cfg.Set("baseURL", "http://auth/bub")
832 cfg.Set("taxonomies", taxonomies)
833
834 writeSourcesToSource(t, "content", fs, sources...)
835 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
836
837 if s.Taxonomies()["tags"]["a"][0].Page.Title() != "foo" {
838 t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies()["tags"]["a"][0].Page.Title())
839 }
840
841 if s.Taxonomies()["categories"]["d"][0].Page.Title() != "bar" {
842 t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies()["categories"]["d"][0].Page.Title())
843 }
844
845 if s.Taxonomies()["categories"]["e"][0].Page.Title() != "bza" {
846 t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies()["categories"]["e"][0].Page.Title())
847 }
848 }
849
850 func setupLinkingMockSite(t *testing.T) *Site {
851 sources := [][2]string{
852 {filepath.FromSlash("level2/unique.md"), ""},
853 {filepath.FromSlash("_index.md"), ""},
854 {filepath.FromSlash("common.md"), ""},
855 {filepath.FromSlash("rootfile.md"), ""},
856 {filepath.FromSlash("root-image.png"), ""},
857
858 {filepath.FromSlash("level2/2-root.md"), ""},
859 {filepath.FromSlash("level2/common.md"), ""},
860
861 {filepath.FromSlash("level2/2-image.png"), ""},
862 {filepath.FromSlash("level2/common.png"), ""},
863
864 {filepath.FromSlash("level2/level3/start.md"), ""},
865 {filepath.FromSlash("level2/level3/_index.md"), ""},
866 {filepath.FromSlash("level2/level3/3-root.md"), ""},
867 {filepath.FromSlash("level2/level3/common.md"), ""},
868 {filepath.FromSlash("level2/level3/3-image.png"), ""},
869 {filepath.FromSlash("level2/level3/common.png"), ""},
870
871 {filepath.FromSlash("level2/level3/embedded.dot.md"), ""},
872
873 {filepath.FromSlash("leafbundle/index.md"), ""},
874 }
875
876 cfg, fs := newTestCfg()
877
878 cfg.Set("baseURL", "http://auth/")
879 cfg.Set("uglyURLs", false)
880 cfg.Set("outputs", map[string]any{
881 "page": []string{"HTML", "AMP"},
882 })
883 cfg.Set("pluralizeListTitles", false)
884 cfg.Set("canonifyURLs", false)
885 writeSourcesToSource(t, "content", fs, sources...)
886 return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
887 }
888
889 func TestRefLinking(t *testing.T) {
890 t.Parallel()
891 site := setupLinkingMockSite(t)
892
893 currentPage := site.getPage(page.KindPage, "level2/level3/start.md")
894 if currentPage == nil {
895 t.Fatalf("failed to find current page in site")
896 }
897
898 for i, test := range []struct {
899 link string
900 outputFormat string
901 relative bool
902 expected string
903 }{
904 // different refs resolving to the same unique filename:
905 {"/level2/unique.md", "", true, "/level2/unique/"},
906 {"../unique.md", "", true, "/level2/unique/"},
907 {"unique.md", "", true, "/level2/unique/"},
908
909 {"level2/common.md", "", true, "/level2/common/"},
910 {"3-root.md", "", true, "/level2/level3/3-root/"},
911 {"../..", "", true, "/"},
912
913 // different refs resolving to the same ambiguous top-level filename:
914 {"../../common.md", "", true, "/common/"},
915 {"/common.md", "", true, "/common/"},
916
917 // different refs resolving to the same ambiguous level-2 filename:
918 {"/level2/common.md", "", true, "/level2/common/"},
919 {"../common.md", "", true, "/level2/common/"},
920 {"common.md", "", true, "/level2/level3/common/"},
921
922 // different refs resolving to the same section:
923 {"/level2", "", true, "/level2/"},
924 {"..", "", true, "/level2/"},
925 {"../", "", true, "/level2/"},
926
927 // different refs resolving to the same subsection:
928 {"/level2/level3", "", true, "/level2/level3/"},
929 {"/level2/level3/_index.md", "", true, "/level2/level3/"},
930 {".", "", true, "/level2/level3/"},
931 {"./", "", true, "/level2/level3/"},
932
933 // try to confuse parsing
934 {"embedded.dot.md", "", true, "/level2/level3/embedded.dot/"},
935
936 // test empty link, as well as fragment only link
937 {"", "", true, ""},
938 } {
939 t.Run(fmt.Sprintf("t%dt", i), func(t *testing.T) {
940 checkLinkCase(site, test.link, currentPage, test.relative, test.outputFormat, test.expected, t, i)
941
942 // make sure fragment links are also handled
943 checkLinkCase(site, test.link+"#intro", currentPage, test.relative, test.outputFormat, test.expected+"#intro", t, i)
944 })
945 }
946
947 // TODO: and then the failure cases.
948 }
949
950 func checkLinkCase(site *Site, link string, currentPage page.Page, relative bool, outputFormat string, expected string, t *testing.T, i int) {
951 t.Helper()
952 if out, err := site.refLink(link, currentPage, relative, outputFormat); err != nil || out != expected {
953 t.Fatalf("[%d] Expected %q from %q to resolve to %q, got %q - error: %s", i, link, currentPage.Pathc(), expected, out, err)
954 }
955 }
956
957 // https://github.com/gohugoio/hugo/issues/6952
958 func TestRefIssues(t *testing.T) {
959 b := newTestSitesBuilder(t)
960 b.WithContent(
961 "post/b1/index.md", "---\ntitle: pb1\n---\nRef: {{< ref \"b2\" >}}",
962 "post/b2/index.md", "---\ntitle: pb2\n---\n",
963 "post/nested-a/content-a.md", "---\ntitle: ca\n---\n{{< ref \"content-b\" >}}",
964 "post/nested-b/content-b.md", "---\ntitle: ca\n---\n",
965 )
966 b.WithTemplates("index.html", `Home`)
967 b.WithTemplates("_default/single.html", `Content: {{ .Content }}`)
968
969 b.Build(BuildCfg{})
970
971 b.AssertFileContent("public/post/b1/index.html", `Content: <p>Ref: http://example.com/post/b2/</p>`)
972 b.AssertFileContent("public/post/nested-a/content-a/index.html", `Content: http://example.com/post/nested-b/content-b/`)
973 }
974
975 func TestClassCollector(t *testing.T) {
976 for _, minify := range []bool{false, true} {
977 t.Run(fmt.Sprintf("minify-%t", minify), func(t *testing.T) {
978 statsFilename := "hugo_stats.json"
979 defer os.Remove(statsFilename)
980
981 b := newTestSitesBuilder(t)
982 b.WithConfigFile("toml", fmt.Sprintf(`
983
984
985 minify = %t
986
987 [build]
988 writeStats = true
989
990 `, minify))
991
992 b.WithTemplates("index.html", `
993
994 <div id="el1" class="a b c">Foo</div>
995
996 Some text.
997
998 <div class="c d e" id="el2">Foo</div>
999
1000 <span class=z>FOO</span>
1001
1002 <a class="text-base hover:text-gradient inline-block px-3 pb-1 rounded lowercase" href="{{ .RelPermalink }}">{{ .Title }}</a>
1003
1004
1005 `)
1006
1007 b.WithContent("p1.md", "")
1008
1009 b.Build(BuildCfg{})
1010
1011 b.AssertFileContent("hugo_stats.json", `
1012 {
1013 "htmlElements": {
1014 "tags": [
1015 "a",
1016 "div",
1017 "span"
1018 ],
1019 "classes": [
1020 "a",
1021 "b",
1022 "c",
1023 "d",
1024 "e",
1025 "hover:text-gradient",
1026 "inline-block",
1027 "lowercase",
1028 "pb-1",
1029 "px-3",
1030 "rounded",
1031 "text-base",
1032 "z"
1033 ],
1034 "ids": [
1035 "el1",
1036 "el2"
1037 ]
1038 }
1039 }
1040 `)
1041 })
1042 }
1043 }
1044
1045 func TestClassCollectorStress(t *testing.T) {
1046 statsFilename := "hugo_stats.json"
1047 defer os.Remove(statsFilename)
1048
1049 b := newTestSitesBuilder(t)
1050 b.WithConfigFile("toml", `
1051
1052 disableKinds = ["home", "section", "term", "taxonomy" ]
1053
1054 [languages]
1055 [languages.en]
1056 [languages.nb]
1057 [languages.no]
1058 [languages.sv]
1059
1060
1061 [build]
1062 writeStats = true
1063
1064 `)
1065
1066 b.WithTemplates("_default/single.html", `
1067 <div class="c d e" id="el2">Foo</div>
1068
1069 Some text.
1070
1071 {{ $n := index (shuffle (seq 1 20)) 0 }}
1072
1073 {{ "<span class=_a>Foo</span>" | strings.Repeat $n | safeHTML }}
1074
1075 <div class="{{ .Title }}">
1076 ABC.
1077 </div>
1078
1079 <div class="f"></div>
1080
1081 {{ $n := index (shuffle (seq 1 5)) 0 }}
1082
1083 {{ "<hr class=p-3>" | safeHTML }}
1084
1085 `)
1086
1087 for _, lang := range []string{"en", "nb", "no", "sv"} {
1088 for i := 100; i <= 999; i++ {
1089 b.WithContent(fmt.Sprintf("p%d.%s.md", i, lang), fmt.Sprintf("---\ntitle: p%s%d\n---", lang, i))
1090 }
1091 }
1092
1093 b.Build(BuildCfg{})
1094
1095 contentMem := b.FileContent(statsFilename)
1096 cb, err := ioutil.ReadFile(statsFilename)
1097 b.Assert(err, qt.IsNil)
1098 contentFile := string(cb)
1099
1100 for _, content := range []string{contentMem, contentFile} {
1101
1102 stats := &publisher.PublishStats{}
1103 b.Assert(json.Unmarshal([]byte(content), stats), qt.IsNil)
1104
1105 els := stats.HTMLElements
1106
1107 b.Assert(els.Classes, qt.HasLen, 3606) // (4 * 900) + 4 +2
1108 b.Assert(els.Tags, qt.HasLen, 8)
1109 b.Assert(els.IDs, qt.HasLen, 1)
1110 }
1111 }