github.com/neohugo/neohugo@v0.123.8/hugolib/site_sections_test.go (about) 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/neohugo/neohugo/deps" 24 "github.com/neohugo/neohugo/htesting" 25 "github.com/neohugo/neohugo/resources/kinds" 26 "github.com/neohugo/neohugo/resources/page" 27 ) 28 29 func TestNestedSections(t *testing.T) { 30 var ( 31 c = qt.New(t) 32 cfg, fs = newTestCfg() 33 ) 34 35 tt := htesting.NewPinnedRunner(c, "") 36 37 cfg.Set("permalinks", map[string]string{ 38 "perm-a": ":sections/:title", 39 }) 40 41 pageTemplate := `--- 42 title: T%d_%d 43 --- 44 Content 45 ` 46 47 // Home page 48 writeSource(t, fs, filepath.Join("content", "_index.md"), fmt.Sprintf(pageTemplate, -1, -1)) 49 50 // Top level content page 51 writeSource(t, fs, filepath.Join("content", "mypage.md"), fmt.Sprintf(pageTemplate, 1234, 5)) 52 53 // Top level section without index content page 54 writeSource(t, fs, filepath.Join("content", "top", "mypage2.md"), fmt.Sprintf(pageTemplate, 12345, 6)) 55 // Just a page in a subfolder, i.e. not a section. 56 writeSource(t, fs, filepath.Join("content", "top", "folder", "mypage3.md"), fmt.Sprintf(pageTemplate, 12345, 67)) 57 58 for level1 := 1; level1 < 3; level1++ { 59 writeSource(t, fs, filepath.Join("content", "l1", fmt.Sprintf("page_1_%d.md", level1)), 60 fmt.Sprintf(pageTemplate, 1, level1)) 61 } 62 63 // Issue #3586 64 writeSource(t, fs, filepath.Join("content", "post", "0000.md"), fmt.Sprintf(pageTemplate, 1, 2)) 65 writeSource(t, fs, filepath.Join("content", "post", "0000", "0001.md"), fmt.Sprintf(pageTemplate, 1, 3)) 66 writeSource(t, fs, filepath.Join("content", "elsewhere", "0003.md"), fmt.Sprintf(pageTemplate, 1, 4)) 67 68 // Empty nested section, i.e. no regular content pages. 69 writeSource(t, fs, filepath.Join("content", "empty1", "b", "c", "_index.md"), fmt.Sprintf(pageTemplate, 33, -1)) 70 // Index content file a the end and in the middle. 71 writeSource(t, fs, filepath.Join("content", "empty2", "b", "_index.md"), fmt.Sprintf(pageTemplate, 40, -1)) 72 writeSource(t, fs, filepath.Join("content", "empty2", "b", "c", "d", "_index.md"), fmt.Sprintf(pageTemplate, 41, -1)) 73 74 // Empty with content file in the middle. 75 writeSource(t, fs, filepath.Join("content", "empty3", "b", "c", "d", "_index.md"), fmt.Sprintf(pageTemplate, 41, -1)) 76 writeSource(t, fs, filepath.Join("content", "empty3", "b", "empty3.md"), fmt.Sprintf(pageTemplate, 3, -1)) 77 78 // Section with permalink config 79 writeSource(t, fs, filepath.Join("content", "perm a", "link", "_index.md"), fmt.Sprintf(pageTemplate, 9, -1)) 80 for i := 1; i < 4; i++ { 81 writeSource(t, fs, filepath.Join("content", "perm a", "link", fmt.Sprintf("page_%d.md", i)), 82 fmt.Sprintf(pageTemplate, 1, i)) 83 } 84 writeSource(t, fs, filepath.Join("content", "perm a", "link", "regular", fmt.Sprintf("page_%d.md", 5)), 85 fmt.Sprintf(pageTemplate, 1, 5)) 86 87 writeSource(t, fs, filepath.Join("content", "l1", "l2", "_index.md"), fmt.Sprintf(pageTemplate, 2, -1)) 88 writeSource(t, fs, filepath.Join("content", "l1", "l2_2", "_index.md"), fmt.Sprintf(pageTemplate, 22, -1)) 89 writeSource(t, fs, filepath.Join("content", "l1", "l2", "l3", "_index.md"), fmt.Sprintf(pageTemplate, 3, -1)) 90 91 for level2 := 1; level2 < 4; level2++ { 92 writeSource(t, fs, filepath.Join("content", "l1", "l2", fmt.Sprintf("page_2_%d.md", level2)), 93 fmt.Sprintf(pageTemplate, 2, level2)) 94 } 95 for level2 := 1; level2 < 3; level2++ { 96 writeSource(t, fs, filepath.Join("content", "l1", "l2_2", fmt.Sprintf("page_2_2_%d.md", level2)), 97 fmt.Sprintf(pageTemplate, 2, level2)) 98 } 99 for level3 := 1; level3 < 3; level3++ { 100 writeSource(t, fs, filepath.Join("content", "l1", "l2", "l3", fmt.Sprintf("page_3_%d.md", level3)), 101 fmt.Sprintf(pageTemplate, 3, level3)) 102 } 103 104 writeSource(t, fs, filepath.Join("content", "Spaces in Section", "page100.md"), fmt.Sprintf(pageTemplate, 10, 0)) 105 106 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html>Single|{{ .Title }}</html>") 107 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), 108 ` 109 {{ $sect := (.Site.GetPage "l1/l2") }} 110 <html>List|{{ .Title }}|L1/l2-IsActive: {{ .InSection $sect }} 111 {{ range .Paginator.Pages }} 112 PAG|{{ .Title }}|{{ $sect.InSection . }} 113 {{ end }} 114 {{/* https://github.com/gohugoio/hugo/issues/4989 */}} 115 {{ $sections := (.Site.GetPage "section" .Section).Sections.ByWeight }} 116 </html>`) 117 118 cfg.Set("paginate", 2) 119 120 th, configs := newTestHelperFromProvider(cfg, fs, t) 121 122 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Configs: configs}, BuildCfg{}) 123 124 c.Assert(len(s.RegularPages()), qt.Equals, 21) 125 126 tests := []struct { 127 sections string 128 verify func(c *qt.C, p page.Page) 129 }{ 130 {"elsewhere", func(c *qt.C, p page.Page) { 131 c.Assert(len(p.Pages()), qt.Equals, 1) 132 for _, p := range p.Pages() { 133 c.Assert(p.SectionsPath(), qt.Equals, "/elsewhere") 134 } 135 }}, 136 {"post", func(c *qt.C, p page.Page) { 137 c.Assert(len(p.Pages()), qt.Equals, 2) 138 for _, p := range p.Pages() { 139 c.Assert(p.Section(), qt.Equals, "post") 140 } 141 }}, 142 {"empty1", func(c *qt.C, p page.Page) { 143 // > b,c 144 c.Assert(getPage(p, "/empty1/b"), qt.IsNil) // No _index.md page. 145 c.Assert(getPage(p, "/empty1/b/c"), qt.Not(qt.IsNil)) 146 }}, 147 {"empty2", func(c *qt.C, p page.Page) { 148 // > b,c,d where b and d have _index.md files. 149 b := getPage(p, "/empty2/b") 150 c.Assert(b, qt.Not(qt.IsNil)) 151 c.Assert(b.Title(), qt.Equals, "T40_-1") 152 153 cp := getPage(p, "/empty2/b/c") 154 c.Assert(cp, qt.IsNil) // No _index.md 155 156 d := getPage(p, "/empty2/b/c/d") 157 c.Assert(d, qt.Not(qt.IsNil)) 158 c.Assert(d.Title(), qt.Equals, "T41_-1") 159 160 c.Assert(cp.Eq(d), qt.Equals, false) 161 c.Assert(cp.Eq(cp), qt.Equals, true) 162 c.Assert(cp.Eq("asdf"), qt.Equals, false) 163 }}, 164 {"empty3", func(c *qt.C, p page.Page) { 165 // b,c,d with regular page in b 166 b := getPage(p, "/empty3/b") 167 c.Assert(b, qt.IsNil) // No _index.md 168 e3 := getPage(p, "/empty3/b/empty3") 169 c.Assert(e3, qt.Not(qt.IsNil)) 170 c.Assert(e3.File().LogicalName(), qt.Equals, "empty3.md") 171 }}, 172 {"empty3", func(c *qt.C, p page.Page) { 173 xxx := getPage(p, "/empty3/nil") 174 c.Assert(xxx, qt.IsNil) 175 }}, 176 {"top", func(c *qt.C, p page.Page) { 177 c.Assert(p.Title(), qt.Equals, "Tops") 178 c.Assert(len(p.Pages()), qt.Equals, 2) 179 c.Assert(p.Pages()[0].File().LogicalName(), qt.Equals, "mypage2.md") 180 c.Assert(p.Pages()[1].File().LogicalName(), qt.Equals, "mypage3.md") 181 home := p.Parent() 182 c.Assert(home.IsHome(), qt.Equals, true) 183 c.Assert(len(p.Sections()), qt.Equals, 0) 184 c.Assert(home.CurrentSection(), qt.Equals, home) 185 active := home.InSection(home) 186 c.Assert(active, qt.Equals, true) 187 c.Assert(p.FirstSection(), qt.Equals, p) 188 c.Assert(len(p.Ancestors()), qt.Equals, 1) 189 }}, 190 {"l1", func(c *qt.C, p page.Page) { 191 c.Assert(p.Title(), qt.Equals, "L1s") 192 c.Assert(len(p.Pages()), qt.Equals, 4) // 2 pages + 2 sections 193 c.Assert(p.Parent().IsHome(), qt.Equals, true) 194 c.Assert(len(p.Sections()), qt.Equals, 2) 195 c.Assert(len(p.Ancestors()), qt.Equals, 1) 196 }}, 197 {"l1,l2", func(c *qt.C, p page.Page) { 198 c.Assert(p.Title(), qt.Equals, "T2_-1") 199 c.Assert(len(p.Pages()), qt.Equals, 4) // 3 pages + 1 section 200 c.Assert(p.Pages()[0].Parent(), qt.Equals, p) 201 c.Assert(p.Parent().Title(), qt.Equals, "L1s") 202 c.Assert(p.RelPermalink(), qt.Equals, "/l1/l2/") 203 c.Assert(len(p.Sections()), qt.Equals, 1) 204 c.Assert(len(p.Ancestors()), qt.Equals, 2) 205 206 for _, child := range p.Pages() { 207 if child.IsSection() { 208 c.Assert(child.CurrentSection(), qt.Equals, child) 209 continue 210 } 211 212 c.Assert(child.CurrentSection(), qt.Equals, p) 213 active := child.InSection(p) 214 215 c.Assert(active, qt.Equals, true) 216 active = p.InSection(child) 217 c.Assert(active, qt.Equals, true) 218 active = p.InSection(getPage(p, "/")) 219 c.Assert(active, qt.Equals, false) 220 221 isAncestor := p.IsAncestor(child) 222 c.Assert(isAncestor, qt.Equals, true) 223 isAncestor = child.IsAncestor(p) 224 c.Assert(isAncestor, qt.Equals, false) 225 226 isDescendant := p.IsDescendant(child) 227 c.Assert(isDescendant, qt.Equals, false) 228 isDescendant = child.IsDescendant(p) 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 c.Assert(len(p.Ancestors()), qt.Equals, 2) 241 }}, 242 {"l1,l2,l3", func(c *qt.C, p page.Page) { 243 nilp, _ := p.GetPage("this/does/not/exist") 244 245 c.Assert(p.Title(), qt.Equals, "T3_-1") 246 c.Assert(len(p.Pages()), qt.Equals, 2) 247 c.Assert(p.Parent().Title(), qt.Equals, "T2_-1") 248 c.Assert(len(p.Sections()), qt.Equals, 0) 249 c.Assert(len(p.Ancestors()), qt.Equals, 3) 250 251 l1 := getPage(p, "/l1") 252 isDescendant := l1.IsDescendant(p) 253 c.Assert(isDescendant, qt.Equals, false) 254 isDescendant = l1.IsDescendant(nil) 255 c.Assert(isDescendant, qt.Equals, false) 256 isDescendant = nilp.IsDescendant(p) 257 c.Assert(isDescendant, qt.Equals, false) 258 isDescendant = p.IsDescendant(l1) 259 c.Assert(isDescendant, qt.Equals, true) 260 261 isAncestor := l1.IsAncestor(p) 262 c.Assert(isAncestor, qt.Equals, true) 263 isAncestor = p.IsAncestor(l1) 264 c.Assert(isAncestor, qt.Equals, false) 265 c.Assert(p.FirstSection(), qt.Equals, l1) 266 isAncestor = p.IsAncestor(nil) 267 c.Assert(isAncestor, qt.Equals, false) 268 c.Assert(isAncestor, qt.Equals, false) 269 270 l3 := getPage(p, "/l1/l2/l3") 271 c.Assert(l3.FirstSection(), qt.Equals, l1) 272 }}, 273 {"perm a,link", func(c *qt.C, p page.Page) { 274 c.Assert(p.Title(), qt.Equals, "T9_-1") 275 c.Assert(p.RelPermalink(), qt.Equals, "/perm-a/link/") 276 c.Assert(len(p.Pages()), qt.Equals, 4) 277 first := p.Pages()[0] 278 c.Assert(first.RelPermalink(), qt.Equals, "/perm-a/link/t1_1/") 279 th.assertFileContent("public/perm-a/link/t1_1/index.html", "Single|T1_1") 280 281 last := p.Pages()[3] 282 c.Assert(last.RelPermalink(), qt.Equals, "/perm-a/link/t1_5/") 283 }}, 284 } 285 286 home := s.getPageOldVersion(kinds.KindHome) 287 288 for _, test := range tests { 289 test := test 290 tt.Run(fmt.Sprintf("sections %s", test.sections), func(c *qt.C) { 291 c.Parallel() 292 sections := strings.Split(test.sections, ",") 293 p := s.getPageOldVersion(kinds.KindSection, sections...) 294 c.Assert(p, qt.Not(qt.IsNil), qt.Commentf(fmt.Sprint(sections))) 295 296 if p.Pages() != nil { 297 c.Assert(p.Data().(page.Data).Pages(), deepEqualsPages, p.Pages()) 298 } 299 c.Assert(p.Parent(), qt.Not(qt.IsNil)) 300 test.verify(c, p) 301 }) 302 } 303 304 c.Assert(home, qt.Not(qt.IsNil)) 305 c.Assert(len(home.Ancestors()), qt.Equals, 0) 306 307 c.Assert(len(home.Sections()), qt.Equals, 9) 308 c.Assert(s.Sections(), deepEqualsPages, home.Sections()) 309 310 rootPage := s.getPageOldVersion(kinds.KindPage, "mypage.md") 311 c.Assert(rootPage, qt.Not(qt.IsNil)) 312 c.Assert(rootPage.Parent().IsHome(), qt.Equals, true) 313 // https://github.com/gohugoio/hugo/issues/6365 314 c.Assert(rootPage.Sections(), qt.HasLen, 0) 315 316 sectionWithSpace := s.getPageOldVersion(kinds.KindSection, "Spaces in Section") 317 // s.h.pageTrees.debugPrint() 318 c.Assert(sectionWithSpace, qt.Not(qt.IsNil)) 319 c.Assert(sectionWithSpace.RelPermalink(), qt.Equals, "/spaces-in-section/") 320 321 th.assertFileContent("public/l1/l2/page/2/index.html", "L1/l2-IsActive: true", "PAG|T2_3|true") 322 } 323 324 func TestNextInSectionNested(t *testing.T) { 325 t.Parallel() 326 327 pageContent := `--- 328 title: "The Page" 329 weight: %d 330 --- 331 Some content. 332 ` 333 createPageContent := func(weight int) string { 334 return fmt.Sprintf(pageContent, weight) 335 } 336 337 b := newTestSitesBuilder(t) 338 b.WithSimpleConfigFile() 339 b.WithTemplates("_default/single.html", ` 340 Prev: {{ with .PrevInSection }}{{ .RelPermalink }}{{ end }}| 341 Next: {{ with .NextInSection }}{{ .RelPermalink }}{{ end }}| 342 `) 343 344 b.WithContent("blog/page1.md", createPageContent(1)) 345 b.WithContent("blog/page2.md", createPageContent(2)) 346 b.WithContent("blog/cool/_index.md", createPageContent(1)) 347 b.WithContent("blog/cool/cool1.md", createPageContent(1)) 348 b.WithContent("blog/cool/cool2.md", createPageContent(2)) 349 b.WithContent("root1.md", createPageContent(1)) 350 b.WithContent("root2.md", createPageContent(2)) 351 352 b.Build(BuildCfg{}) 353 354 b.AssertFileContent("public/root1/index.html", 355 "Prev: /root2/|", "Next: |") 356 b.AssertFileContent("public/root2/index.html", 357 "Prev: |", "Next: /root1/|") 358 b.AssertFileContent("public/blog/page1/index.html", 359 "Prev: /blog/page2/|", "Next: |") 360 b.AssertFileContent("public/blog/page2/index.html", 361 "Prev: |", "Next: /blog/page1/|") 362 b.AssertFileContent("public/blog/cool/cool1/index.html", 363 "Prev: /blog/cool/cool2/|", "Next: |") 364 b.AssertFileContent("public/blog/cool/cool2/index.html", 365 "Prev: |", "Next: /blog/cool/cool1/|") 366 } 367 368 func TestSectionEntries(t *testing.T) { 369 t.Parallel() 370 371 files := ` 372 -- hugo.toml -- 373 baseURL = "https://example.com/" 374 -- content/myfirstsection/p1.md -- 375 --- 376 title: "P1" 377 --- 378 P1 379 -- content/a/b/c/_index.md -- 380 --- 381 title: "C" 382 --- 383 C 384 -- content/a/b/c/mybundle/index.md -- 385 --- 386 title: "My Bundle" 387 --- 388 -- layouts/_default/list.html -- 389 Kind: {{ .Kind }}|RelPermalink: {{ .RelPermalink }}|SectionsPath: {{ .SectionsPath }}|SectionsEntries: {{ .SectionsEntries }}|Len: {{ len .SectionsEntries }}| 390 -- layouts/_default/single.html -- 391 Kind: {{ .Kind }}|RelPermalink: {{ .RelPermalink }}|SectionsPath: {{ .SectionsPath }}|SectionsEntries: {{ .SectionsEntries }}|Len: {{ len .SectionsEntries }}| 392 ` 393 394 b := Test(t, files) 395 396 b.AssertFileContent("public/myfirstsection/p1/index.html", "RelPermalink: /myfirstsection/p1/|SectionsPath: /myfirstsection|SectionsEntries: [myfirstsection]|Len: 1") 397 b.AssertFileContent("public/a/b/c/index.html", "RelPermalink: /a/b/c/|SectionsPath: /a/b/c|SectionsEntries: [a b c]|Len: 3") 398 b.AssertFileContent("public/a/b/c/mybundle/index.html", "Kind: page|RelPermalink: /a/b/c/mybundle/|SectionsPath: /a/b/c|SectionsEntries: [a b c]|Len: 3") 399 b.AssertFileContent("public/index.html", "Kind: home|RelPermalink: /|SectionsPath: /|SectionsEntries: []|Len: 0") 400 }