site_sections_test.go (13645B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package hugolib
15
16 import (
17 "fmt"
18 "path/filepath"
19 "strings"
20 "testing"
21
22 qt "github.com/frankban/quicktest"
23 "github.com/gohugoio/hugo/deps"
24 "github.com/gohugoio/hugo/resources/page"
25 )
26
27 func TestNestedSections(t *testing.T) {
28 var (
29 c = qt.New(t)
30 cfg, fs = newTestCfg()
31 th = newTestHelper(cfg, fs, t)
32 )
33
34 cfg.Set("permalinks", map[string]string{
35 "perm a": ":sections/:title",
36 })
37
38 pageTemplate := `---
39 title: T%d_%d
40 ---
41 Content
42 `
43
44 // Home page
45 writeSource(t, fs, filepath.Join("content", "_index.md"), fmt.Sprintf(pageTemplate, -1, -1))
46
47 // Top level content page
48 writeSource(t, fs, filepath.Join("content", "mypage.md"), fmt.Sprintf(pageTemplate, 1234, 5))
49
50 // Top level section without index content page
51 writeSource(t, fs, filepath.Join("content", "top", "mypage2.md"), fmt.Sprintf(pageTemplate, 12345, 6))
52 // Just a page in a subfolder, i.e. not a section.
53 writeSource(t, fs, filepath.Join("content", "top", "folder", "mypage3.md"), fmt.Sprintf(pageTemplate, 12345, 67))
54
55 for level1 := 1; level1 < 3; level1++ {
56 writeSource(t, fs, filepath.Join("content", "l1", fmt.Sprintf("page_1_%d.md", level1)),
57 fmt.Sprintf(pageTemplate, 1, level1))
58 }
59
60 // Issue #3586
61 writeSource(t, fs, filepath.Join("content", "post", "0000.md"), fmt.Sprintf(pageTemplate, 1, 2))
62 writeSource(t, fs, filepath.Join("content", "post", "0000", "0001.md"), fmt.Sprintf(pageTemplate, 1, 3))
63 writeSource(t, fs, filepath.Join("content", "elsewhere", "0003.md"), fmt.Sprintf(pageTemplate, 1, 4))
64
65 // Empty nested section, i.e. no regular content pages.
66 writeSource(t, fs, filepath.Join("content", "empty1", "b", "c", "_index.md"), fmt.Sprintf(pageTemplate, 33, -1))
67 // Index content file a the end and in the middle.
68 writeSource(t, fs, filepath.Join("content", "empty2", "b", "_index.md"), fmt.Sprintf(pageTemplate, 40, -1))
69 writeSource(t, fs, filepath.Join("content", "empty2", "b", "c", "d", "_index.md"), fmt.Sprintf(pageTemplate, 41, -1))
70
71 // Empty with content file in the middle.
72 writeSource(t, fs, filepath.Join("content", "empty3", "b", "c", "d", "_index.md"), fmt.Sprintf(pageTemplate, 41, -1))
73 writeSource(t, fs, filepath.Join("content", "empty3", "b", "empty3.md"), fmt.Sprintf(pageTemplate, 3, -1))
74
75 // Section with permalink config
76 writeSource(t, fs, filepath.Join("content", "perm a", "link", "_index.md"), fmt.Sprintf(pageTemplate, 9, -1))
77 for i := 1; i < 4; i++ {
78 writeSource(t, fs, filepath.Join("content", "perm a", "link", fmt.Sprintf("page_%d.md", i)),
79 fmt.Sprintf(pageTemplate, 1, i))
80 }
81 writeSource(t, fs, filepath.Join("content", "perm a", "link", "regular", fmt.Sprintf("page_%d.md", 5)),
82 fmt.Sprintf(pageTemplate, 1, 5))
83
84 writeSource(t, fs, filepath.Join("content", "l1", "l2", "_index.md"), fmt.Sprintf(pageTemplate, 2, -1))
85 writeSource(t, fs, filepath.Join("content", "l1", "l2_2", "_index.md"), fmt.Sprintf(pageTemplate, 22, -1))
86 writeSource(t, fs, filepath.Join("content", "l1", "l2", "l3", "_index.md"), fmt.Sprintf(pageTemplate, 3, -1))
87
88 for level2 := 1; level2 < 4; level2++ {
89 writeSource(t, fs, filepath.Join("content", "l1", "l2", fmt.Sprintf("page_2_%d.md", level2)),
90 fmt.Sprintf(pageTemplate, 2, level2))
91 }
92 for level2 := 1; level2 < 3; level2++ {
93 writeSource(t, fs, filepath.Join("content", "l1", "l2_2", fmt.Sprintf("page_2_2_%d.md", level2)),
94 fmt.Sprintf(pageTemplate, 2, level2))
95 }
96 for level3 := 1; level3 < 3; level3++ {
97 writeSource(t, fs, filepath.Join("content", "l1", "l2", "l3", fmt.Sprintf("page_3_%d.md", level3)),
98 fmt.Sprintf(pageTemplate, 3, level3))
99 }
100
101 writeSource(t, fs, filepath.Join("content", "Spaces in Section", "page100.md"), fmt.Sprintf(pageTemplate, 10, 0))
102
103 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html>Single|{{ .Title }}</html>")
104 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
105 `
106 {{ $sect := (.Site.GetPage "l1/l2") }}
107 <html>List|{{ .Title }}|L1/l2-IsActive: {{ .InSection $sect }}
108 {{ range .Paginator.Pages }}
109 PAG|{{ .Title }}|{{ $sect.InSection . }}
110 {{ end }}
111 {{/* https://github.com/gohugoio/hugo/issues/4989 */}}
112 {{ $sections := (.Site.GetPage "section" .Section).Sections.ByWeight }}
113 </html>`)
114
115 cfg.Set("paginate", 2)
116
117 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
118
119 c.Assert(len(s.RegularPages()), qt.Equals, 21)
120
121 tests := []struct {
122 sections string
123 verify func(c *qt.C, p page.Page)
124 }{
125 {"elsewhere", func(c *qt.C, p page.Page) {
126 c.Assert(len(p.Pages()), qt.Equals, 1)
127 for _, p := range p.Pages() {
128 c.Assert(p.SectionsPath(), qt.Equals, "elsewhere")
129 }
130 }},
131 {"post", func(c *qt.C, p page.Page) {
132 c.Assert(len(p.Pages()), qt.Equals, 2)
133 for _, p := range p.Pages() {
134 c.Assert(p.Section(), qt.Equals, "post")
135 }
136 }},
137 {"empty1", func(c *qt.C, p page.Page) {
138 // > b,c
139 c.Assert(getPage(p, "/empty1/b"), qt.IsNil) // No _index.md page.
140 c.Assert(getPage(p, "/empty1/b/c"), qt.Not(qt.IsNil))
141 }},
142 {"empty2", func(c *qt.C, p page.Page) {
143 // > b,c,d where b and d have _index.md files.
144 b := getPage(p, "/empty2/b")
145 c.Assert(b, qt.Not(qt.IsNil))
146 c.Assert(b.Title(), qt.Equals, "T40_-1")
147
148 cp := getPage(p, "/empty2/b/c")
149 c.Assert(cp, qt.IsNil) // No _index.md
150
151 d := getPage(p, "/empty2/b/c/d")
152 c.Assert(d, qt.Not(qt.IsNil))
153 c.Assert(d.Title(), qt.Equals, "T41_-1")
154
155 c.Assert(cp.Eq(d), qt.Equals, false)
156 c.Assert(cp.Eq(cp), qt.Equals, true)
157 c.Assert(cp.Eq("asdf"), qt.Equals, false)
158 }},
159 {"empty3", func(c *qt.C, p page.Page) {
160 // b,c,d with regular page in b
161 b := getPage(p, "/empty3/b")
162 c.Assert(b, qt.IsNil) // No _index.md
163 e3 := getPage(p, "/empty3/b/empty3")
164 c.Assert(e3, qt.Not(qt.IsNil))
165 c.Assert(e3.File().LogicalName(), qt.Equals, "empty3.md")
166 }},
167 {"empty3", func(c *qt.C, p page.Page) {
168 xxx := getPage(p, "/empty3/nil")
169 c.Assert(xxx, qt.IsNil)
170 }},
171 {"top", func(c *qt.C, p page.Page) {
172 c.Assert(p.Title(), qt.Equals, "Tops")
173 c.Assert(len(p.Pages()), qt.Equals, 2)
174 c.Assert(p.Pages()[0].File().LogicalName(), qt.Equals, "mypage2.md")
175 c.Assert(p.Pages()[1].File().LogicalName(), qt.Equals, "mypage3.md")
176 home := p.Parent()
177 c.Assert(home.IsHome(), qt.Equals, true)
178 c.Assert(len(p.Sections()), qt.Equals, 0)
179 c.Assert(home.CurrentSection(), qt.Equals, home)
180 active, err := home.InSection(home)
181 c.Assert(err, qt.IsNil)
182 c.Assert(active, qt.Equals, true)
183 c.Assert(p.FirstSection(), qt.Equals, p)
184 }},
185 {"l1", func(c *qt.C, p page.Page) {
186 c.Assert(p.Title(), qt.Equals, "L1s")
187 c.Assert(len(p.Pages()), qt.Equals, 4) // 2 pages + 2 sections
188 c.Assert(p.Parent().IsHome(), qt.Equals, true)
189 c.Assert(len(p.Sections()), qt.Equals, 2)
190 }},
191 {"l1,l2", func(c *qt.C, p page.Page) {
192 c.Assert(p.Title(), qt.Equals, "T2_-1")
193 c.Assert(len(p.Pages()), qt.Equals, 4) // 3 pages + 1 section
194 c.Assert(p.Pages()[0].Parent(), qt.Equals, p)
195 c.Assert(p.Parent().Title(), qt.Equals, "L1s")
196 c.Assert(p.RelPermalink(), qt.Equals, "/l1/l2/")
197 c.Assert(len(p.Sections()), qt.Equals, 1)
198
199 for _, child := range p.Pages() {
200 if child.IsSection() {
201 c.Assert(child.CurrentSection(), qt.Equals, child)
202 continue
203 }
204
205 c.Assert(child.CurrentSection(), qt.Equals, p)
206 active, err := child.InSection(p)
207 c.Assert(err, qt.IsNil)
208
209 c.Assert(active, qt.Equals, true)
210 active, err = p.InSection(child)
211 c.Assert(err, qt.IsNil)
212 c.Assert(active, qt.Equals, true)
213 active, err = p.InSection(getPage(p, "/"))
214 c.Assert(err, qt.IsNil)
215 c.Assert(active, qt.Equals, false)
216
217 isAncestor, err := p.IsAncestor(child)
218 c.Assert(err, qt.IsNil)
219 c.Assert(isAncestor, qt.Equals, true)
220 isAncestor, err = child.IsAncestor(p)
221 c.Assert(err, qt.IsNil)
222 c.Assert(isAncestor, qt.Equals, false)
223
224 isDescendant, err := p.IsDescendant(child)
225 c.Assert(err, qt.IsNil)
226 c.Assert(isDescendant, qt.Equals, false)
227 isDescendant, err = child.IsDescendant(p)
228 c.Assert(err, qt.IsNil)
229 c.Assert(isDescendant, qt.Equals, true)
230 }
231
232 c.Assert(p.Eq(p.CurrentSection()), qt.Equals, true)
233 }},
234 {"l1,l2_2", func(c *qt.C, p page.Page) {
235 c.Assert(p.Title(), qt.Equals, "T22_-1")
236 c.Assert(len(p.Pages()), qt.Equals, 2)
237 c.Assert(p.Pages()[0].File().Path(), qt.Equals, filepath.FromSlash("l1/l2_2/page_2_2_1.md"))
238 c.Assert(p.Parent().Title(), qt.Equals, "L1s")
239 c.Assert(len(p.Sections()), qt.Equals, 0)
240 }},
241 {"l1,l2,l3", func(c *qt.C, p page.Page) {
242 nilp, _ := p.GetPage("this/does/not/exist")
243
244 c.Assert(p.Title(), qt.Equals, "T3_-1")
245 c.Assert(len(p.Pages()), qt.Equals, 2)
246 c.Assert(p.Parent().Title(), qt.Equals, "T2_-1")
247 c.Assert(len(p.Sections()), qt.Equals, 0)
248
249 l1 := getPage(p, "/l1")
250 isDescendant, err := l1.IsDescendant(p)
251 c.Assert(err, qt.IsNil)
252 c.Assert(isDescendant, qt.Equals, false)
253 isDescendant, err = l1.IsDescendant(nil)
254 c.Assert(err, qt.IsNil)
255 c.Assert(isDescendant, qt.Equals, false)
256 isDescendant, err = nilp.IsDescendant(p)
257 c.Assert(err, qt.IsNil)
258 c.Assert(isDescendant, qt.Equals, false)
259 isDescendant, err = p.IsDescendant(l1)
260 c.Assert(err, qt.IsNil)
261 c.Assert(isDescendant, qt.Equals, true)
262
263 isAncestor, err := l1.IsAncestor(p)
264 c.Assert(err, qt.IsNil)
265 c.Assert(isAncestor, qt.Equals, true)
266 isAncestor, err = p.IsAncestor(l1)
267 c.Assert(err, qt.IsNil)
268 c.Assert(isAncestor, qt.Equals, false)
269 c.Assert(p.FirstSection(), qt.Equals, l1)
270 isAncestor, err = p.IsAncestor(nil)
271 c.Assert(err, qt.IsNil)
272 c.Assert(isAncestor, qt.Equals, false)
273 isAncestor, err = nilp.IsAncestor(l1)
274 c.Assert(err, qt.IsNil)
275 c.Assert(isAncestor, qt.Equals, false)
276 }},
277 {"perm a,link", func(c *qt.C, p page.Page) {
278 c.Assert(p.Title(), qt.Equals, "T9_-1")
279 c.Assert(p.RelPermalink(), qt.Equals, "/perm-a/link/")
280 c.Assert(len(p.Pages()), qt.Equals, 4)
281 first := p.Pages()[0]
282 c.Assert(first.RelPermalink(), qt.Equals, "/perm-a/link/t1_1/")
283 th.assertFileContent("public/perm-a/link/t1_1/index.html", "Single|T1_1")
284
285 last := p.Pages()[3]
286 c.Assert(last.RelPermalink(), qt.Equals, "/perm-a/link/t1_5/")
287 }},
288 }
289
290 home := s.getPage(page.KindHome)
291
292 for _, test := range tests {
293 test := test
294 t.Run(fmt.Sprintf("sections %s", test.sections), func(t *testing.T) {
295 t.Parallel()
296 c := qt.New(t)
297 sections := strings.Split(test.sections, ",")
298 p := s.getPage(page.KindSection, sections...)
299 c.Assert(p, qt.Not(qt.IsNil), qt.Commentf(fmt.Sprint(sections)))
300
301 if p.Pages() != nil {
302 c.Assert(p.Data().(page.Data).Pages(), deepEqualsPages, p.Pages())
303 }
304 c.Assert(p.Parent(), qt.Not(qt.IsNil))
305 test.verify(c, p)
306 })
307 }
308
309 c.Assert(home, qt.Not(qt.IsNil))
310
311 c.Assert(len(home.Sections()), qt.Equals, 9)
312 c.Assert(s.Info.Sections(), deepEqualsPages, home.Sections())
313
314 rootPage := s.getPage(page.KindPage, "mypage.md")
315 c.Assert(rootPage, qt.Not(qt.IsNil))
316 c.Assert(rootPage.Parent().IsHome(), qt.Equals, true)
317 // https://github.com/gohugoio/hugo/issues/6365
318 c.Assert(rootPage.Sections(), qt.HasLen, 0)
319
320 // Add a odd test for this as this looks a little bit off, but I'm not in the mood
321 // to think too hard a out this right now. It works, but people will have to spell
322 // out the directory name as is.
323 // If we later decide to do something about this, we will have to do some normalization in
324 // getPage.
325 // TODO(bep)
326 sectionWithSpace := s.getPage(page.KindSection, "Spaces in Section")
327 c.Assert(sectionWithSpace, qt.Not(qt.IsNil))
328 c.Assert(sectionWithSpace.RelPermalink(), qt.Equals, "/spaces-in-section/")
329
330 th.assertFileContent("public/l1/l2/page/2/index.html", "L1/l2-IsActive: true", "PAG|T2_3|true")
331 }
332
333 func TestNextInSectionNested(t *testing.T) {
334 t.Parallel()
335
336 pageContent := `---
337 title: "The Page"
338 weight: %d
339 ---
340 Some content.
341 `
342 createPageContent := func(weight int) string {
343 return fmt.Sprintf(pageContent, weight)
344 }
345
346 b := newTestSitesBuilder(t)
347 b.WithSimpleConfigFile()
348 b.WithTemplates("_default/single.html", `
349 Prev: {{ with .PrevInSection }}{{ .RelPermalink }}{{ end }}|
350 Next: {{ with .NextInSection }}{{ .RelPermalink }}{{ end }}|
351 `)
352
353 b.WithContent("blog/page1.md", createPageContent(1))
354 b.WithContent("blog/page2.md", createPageContent(2))
355 b.WithContent("blog/cool/_index.md", createPageContent(1))
356 b.WithContent("blog/cool/cool1.md", createPageContent(1))
357 b.WithContent("blog/cool/cool2.md", createPageContent(2))
358 b.WithContent("root1.md", createPageContent(1))
359 b.WithContent("root2.md", createPageContent(2))
360
361 b.Build(BuildCfg{})
362
363 b.AssertFileContent("public/root1/index.html",
364 "Prev: /root2/|", "Next: |")
365 b.AssertFileContent("public/root2/index.html",
366 "Prev: |", "Next: /root1/|")
367 b.AssertFileContent("public/blog/page1/index.html",
368 "Prev: /blog/page2/|", "Next: |")
369 b.AssertFileContent("public/blog/page2/index.html",
370 "Prev: |", "Next: /blog/page1/|")
371 b.AssertFileContent("public/blog/cool/cool1/index.html",
372 "Prev: /blog/cool/cool2/|", "Next: |")
373 b.AssertFileContent("public/blog/cool/cool2/index.html",
374 "Prev: |", "Next: /blog/cool/cool1/|")
375 }