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 }