github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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/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 c.Assert(len(p.Ancestors()), qt.Equals, 1) 185 }}, 186 {"l1", func(c *qt.C, p page.Page) { 187 c.Assert(p.Title(), qt.Equals, "L1s") 188 c.Assert(len(p.Pages()), qt.Equals, 4) // 2 pages + 2 sections 189 c.Assert(p.Parent().IsHome(), qt.Equals, true) 190 c.Assert(len(p.Sections()), qt.Equals, 2) 191 c.Assert(len(p.Ancestors()), qt.Equals, 1) 192 }}, 193 {"l1,l2", func(c *qt.C, p page.Page) { 194 c.Assert(p.Title(), qt.Equals, "T2_-1") 195 c.Assert(len(p.Pages()), qt.Equals, 4) // 3 pages + 1 section 196 c.Assert(p.Pages()[0].Parent(), qt.Equals, p) 197 c.Assert(p.Parent().Title(), qt.Equals, "L1s") 198 c.Assert(p.RelPermalink(), qt.Equals, "/l1/l2/") 199 c.Assert(len(p.Sections()), qt.Equals, 1) 200 c.Assert(len(p.Ancestors()), qt.Equals, 2) 201 202 for _, child := range p.Pages() { 203 if child.IsSection() { 204 c.Assert(child.CurrentSection(), qt.Equals, child) 205 continue 206 } 207 208 c.Assert(child.CurrentSection(), qt.Equals, p) 209 active, err := child.InSection(p) 210 c.Assert(err, qt.IsNil) 211 212 c.Assert(active, qt.Equals, true) 213 active, err = p.InSection(child) 214 c.Assert(err, qt.IsNil) 215 c.Assert(active, qt.Equals, true) 216 active, err = p.InSection(getPage(p, "/")) 217 c.Assert(err, qt.IsNil) 218 c.Assert(active, qt.Equals, false) 219 220 isAncestor, err := p.IsAncestor(child) 221 c.Assert(err, qt.IsNil) 222 c.Assert(isAncestor, qt.Equals, true) 223 isAncestor, err = child.IsAncestor(p) 224 c.Assert(err, qt.IsNil) 225 c.Assert(isAncestor, qt.Equals, false) 226 227 isDescendant, err := p.IsDescendant(child) 228 c.Assert(err, qt.IsNil) 229 c.Assert(isDescendant, qt.Equals, false) 230 isDescendant, err = child.IsDescendant(p) 231 c.Assert(err, qt.IsNil) 232 c.Assert(isDescendant, qt.Equals, true) 233 } 234 235 c.Assert(p.Eq(p.CurrentSection()), qt.Equals, true) 236 }}, 237 {"l1,l2_2", func(c *qt.C, p page.Page) { 238 c.Assert(p.Title(), qt.Equals, "T22_-1") 239 c.Assert(len(p.Pages()), qt.Equals, 2) 240 c.Assert(p.Pages()[0].File().Path(), qt.Equals, filepath.FromSlash("l1/l2_2/page_2_2_1.md")) 241 c.Assert(p.Parent().Title(), qt.Equals, "L1s") 242 c.Assert(len(p.Sections()), qt.Equals, 0) 243 c.Assert(len(p.Ancestors()), qt.Equals, 2) 244 }}, 245 {"l1,l2,l3", func(c *qt.C, p page.Page) { 246 nilp, _ := p.GetPage("this/does/not/exist") 247 248 c.Assert(p.Title(), qt.Equals, "T3_-1") 249 c.Assert(len(p.Pages()), qt.Equals, 2) 250 c.Assert(p.Parent().Title(), qt.Equals, "T2_-1") 251 c.Assert(len(p.Sections()), qt.Equals, 0) 252 c.Assert(len(p.Ancestors()), qt.Equals, 3) 253 254 l1 := getPage(p, "/l1") 255 isDescendant, err := l1.IsDescendant(p) 256 c.Assert(err, qt.IsNil) 257 c.Assert(isDescendant, qt.Equals, false) 258 isDescendant, err = l1.IsDescendant(nil) 259 c.Assert(err, qt.IsNil) 260 c.Assert(isDescendant, qt.Equals, false) 261 isDescendant, err = nilp.IsDescendant(p) 262 c.Assert(err, qt.IsNil) 263 c.Assert(isDescendant, qt.Equals, false) 264 isDescendant, err = p.IsDescendant(l1) 265 c.Assert(err, qt.IsNil) 266 c.Assert(isDescendant, qt.Equals, true) 267 268 isAncestor, err := l1.IsAncestor(p) 269 c.Assert(err, qt.IsNil) 270 c.Assert(isAncestor, qt.Equals, true) 271 isAncestor, err = p.IsAncestor(l1) 272 c.Assert(err, qt.IsNil) 273 c.Assert(isAncestor, qt.Equals, false) 274 c.Assert(p.FirstSection(), qt.Equals, l1) 275 isAncestor, err = p.IsAncestor(nil) 276 c.Assert(err, qt.IsNil) 277 c.Assert(isAncestor, qt.Equals, false) 278 isAncestor, err = nilp.IsAncestor(l1) 279 c.Assert(err, qt.IsNil) 280 c.Assert(isAncestor, qt.Equals, false) 281 }}, 282 {"perm a,link", func(c *qt.C, p page.Page) { 283 c.Assert(p.Title(), qt.Equals, "T9_-1") 284 c.Assert(p.RelPermalink(), qt.Equals, "/perm-a/link/") 285 c.Assert(len(p.Pages()), qt.Equals, 4) 286 first := p.Pages()[0] 287 c.Assert(first.RelPermalink(), qt.Equals, "/perm-a/link/t1_1/") 288 th.assertFileContent("public/perm-a/link/t1_1/index.html", "Single|T1_1") 289 290 last := p.Pages()[3] 291 c.Assert(last.RelPermalink(), qt.Equals, "/perm-a/link/t1_5/") 292 }}, 293 } 294 295 home := s.getPage(page.KindHome) 296 297 for _, test := range tests { 298 test := test 299 t.Run(fmt.Sprintf("sections %s", test.sections), func(t *testing.T) { 300 t.Parallel() 301 c := qt.New(t) 302 sections := strings.Split(test.sections, ",") 303 p := s.getPage(page.KindSection, sections...) 304 c.Assert(p, qt.Not(qt.IsNil), qt.Commentf(fmt.Sprint(sections))) 305 306 if p.Pages() != nil { 307 c.Assert(p.Data().(page.Data).Pages(), deepEqualsPages, p.Pages()) 308 } 309 c.Assert(p.Parent(), qt.Not(qt.IsNil)) 310 test.verify(c, p) 311 }) 312 } 313 314 c.Assert(home, qt.Not(qt.IsNil)) 315 c.Assert(len(home.Ancestors()), qt.Equals, 0) 316 317 c.Assert(len(home.Sections()), qt.Equals, 9) 318 c.Assert(s.Info.Sections(), deepEqualsPages, home.Sections()) 319 320 rootPage := s.getPage(page.KindPage, "mypage.md") 321 c.Assert(rootPage, qt.Not(qt.IsNil)) 322 c.Assert(rootPage.Parent().IsHome(), qt.Equals, true) 323 // https://github.com/gohugoio/hugo/issues/6365 324 c.Assert(rootPage.Sections(), qt.HasLen, 0) 325 326 // Add a odd test for this as this looks a little bit off, but I'm not in the mood 327 // to think too hard a out this right now. It works, but people will have to spell 328 // out the directory name as is. 329 // If we later decide to do something about this, we will have to do some normalization in 330 // getPage. 331 // TODO(bep) 332 sectionWithSpace := s.getPage(page.KindSection, "Spaces in Section") 333 c.Assert(sectionWithSpace, qt.Not(qt.IsNil)) 334 c.Assert(sectionWithSpace.RelPermalink(), qt.Equals, "/spaces-in-section/") 335 336 th.assertFileContent("public/l1/l2/page/2/index.html", "L1/l2-IsActive: true", "PAG|T2_3|true") 337 } 338 339 func TestNextInSectionNested(t *testing.T) { 340 t.Parallel() 341 342 pageContent := `--- 343 title: "The Page" 344 weight: %d 345 --- 346 Some content. 347 ` 348 createPageContent := func(weight int) string { 349 return fmt.Sprintf(pageContent, weight) 350 } 351 352 b := newTestSitesBuilder(t) 353 b.WithSimpleConfigFile() 354 b.WithTemplates("_default/single.html", ` 355 Prev: {{ with .PrevInSection }}{{ .RelPermalink }}{{ end }}| 356 Next: {{ with .NextInSection }}{{ .RelPermalink }}{{ end }}| 357 `) 358 359 b.WithContent("blog/page1.md", createPageContent(1)) 360 b.WithContent("blog/page2.md", createPageContent(2)) 361 b.WithContent("blog/cool/_index.md", createPageContent(1)) 362 b.WithContent("blog/cool/cool1.md", createPageContent(1)) 363 b.WithContent("blog/cool/cool2.md", createPageContent(2)) 364 b.WithContent("root1.md", createPageContent(1)) 365 b.WithContent("root2.md", createPageContent(2)) 366 367 b.Build(BuildCfg{}) 368 369 b.AssertFileContent("public/root1/index.html", 370 "Prev: /root2/|", "Next: |") 371 b.AssertFileContent("public/root2/index.html", 372 "Prev: |", "Next: /root1/|") 373 b.AssertFileContent("public/blog/page1/index.html", 374 "Prev: /blog/page2/|", "Next: |") 375 b.AssertFileContent("public/blog/page2/index.html", 376 "Prev: |", "Next: /blog/page1/|") 377 b.AssertFileContent("public/blog/cool/cool1/index.html", 378 "Prev: /blog/cool/cool2/|", "Next: |") 379 b.AssertFileContent("public/blog/cool/cool2/index.html", 380 "Prev: |", "Next: /blog/cool/cool1/|") 381 }