From a3af4fe46e1e1d184538a83bc8375154a9669316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 20 Feb 2017 09:33:35 +0100 Subject: [PATCH] hugolib: Finish menu vs section content pages This commit also fixes the default menu sort when the weight is 0. Closes #2974 --- hugolib/hugo_sites_build.go | 1 + hugolib/hugo_sites_build_test.go | 4 +- hugolib/menu.go | 9 + hugolib/menu_old_test.go | 693 ++++++++++++++++++++++++++++++ hugolib/menu_test.go | 700 +++---------------------------- hugolib/page.go | 17 +- hugolib/site.go | 34 +- hugolib/taxonomy_test.go | 24 +- hugolib/testhelpers_test.go | 40 +- 9 files changed, 817 insertions(+), 705 deletions(-) create mode 100644 hugolib/menu_old_test.go diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 2715e8977..ce2c3b941 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -167,6 +167,7 @@ func (h *HugoSites) assemble(config *BuildCfg) error { } for _, s := range h.Sites { + s.assembleMenus() s.refreshPageCaches() s.setupSitePages() } diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 9f8b6084a..fce6abdd8 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -1235,11 +1235,11 @@ lag: return sites } -func writeSource(t *testing.T, fs *hugofs.Fs, filename, content string) { +func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) { writeToFs(t, fs.Source, filename, content) } -func writeToFs(t *testing.T, fs afero.Fs, filename, content string) { +func writeToFs(t testing.TB, fs afero.Fs, filename, content string) { if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil { t.Fatalf("Failed to write file: %s", err) } diff --git a/hugolib/menu.go b/hugolib/menu.go index 116545a9a..4f6bd2b4e 100644 --- a/hugolib/menu.go +++ b/hugolib/menu.go @@ -157,6 +157,15 @@ var defaultMenuEntrySort = func(m1, m2 *MenuEntry) bool { } return m1.Name < m2.Name } + + if m2.Weight == 0 { + return true + } + + if m1.Weight == 0 { + return false + } + return m1.Weight < m2.Weight } diff --git a/hugolib/menu_old_test.go b/hugolib/menu_old_test.go new file mode 100644 index 000000000..7a4de902a --- /dev/null +++ b/hugolib/menu_old_test.go @@ -0,0 +1,693 @@ +// Copyright 2016 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hugolib + +// TODO(bep) remove this file when the reworked tests in menu_test.go is done. +// NOTE: Do not add more tests to this file! + +import ( + "fmt" + "strings" + "testing" + + "github.com/spf13/hugo/deps" + + "path/filepath" + + toml "github.com/pelletier/go-toml" + "github.com/spf13/hugo/source" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + confMenu1 = ` +[[menu.main]] + name = "Go Home" + url = "/" + weight = 1 + pre = "
" + post = "
" +[[menu.main]] + name = "Blog" + url = "/posts" +[[menu.main]] + name = "ext" + url = "http://gohugo.io" + identifier = "ext" +[[menu.main]] + name = "ext2" + url = "http://foo.local/Zoo/foo" + identifier = "ext2" +[[menu.grandparent]] + name = "grandparent" + url = "/grandparent" + identifier = "grandparentId" +[[menu.grandparent]] + name = "parent" + url = "/parent" + identifier = "parentId" + parent = "grandparentId" +[[menu.grandparent]] + name = "Go Home3" + url = "/" + identifier = "grandchildId" + parent = "parentId" +[[menu.tax]] + name = "Tax1" + url = "/two/key/" + identifier="1" +[[menu.tax]] + name = "Tax2" + url = "/two/key/" + identifier="2" +[[menu.tax]] + name = "Tax RSS" + url = "/two/key.xml" + identifier="xml" +[[menu.hash]] + name = "Tax With #" + url = "/resource#anchor" + identifier="hash" +[[menu.unicode]] + name = "Unicode Russian" + identifier = "unicode-russian" + url = "/новости-проекта"` // Russian => "news-project" +) + +var menuPage1 = []byte(`+++ +title = "One" +weight = 1 +[menu] + [menu.p_one] ++++ +Front Matter with Menu Pages`) + +var menuPage2 = []byte(`+++ +title = "Two" +weight = 2 +[menu] + [menu.p_one] + [menu.p_two] + identifier = "Two" + ++++ +Front Matter with Menu Pages`) + +var menuPage3 = []byte(`+++ +title = "Three" +weight = 3 +[menu] + [menu.p_two] + Name = "Three" + Parent = "Two" ++++ +Front Matter with Menu Pages`) + +var menuPage4 = []byte(`+++ +title = "Four" +weight = 4 +[menu] + [menu.p_two] + Name = "Four" + Parent = "Three" ++++ +Front Matter with Menu Pages`) + +var menuPageSources = []source.ByteSource{ + {Name: filepath.FromSlash("sect/doc1.md"), Content: menuPage1}, + {Name: filepath.FromSlash("sect/doc2.md"), Content: menuPage2}, + {Name: filepath.FromSlash("sect/doc3.md"), Content: menuPage3}, +} + +var menuPageSectionsSources = []source.ByteSource{ + {Name: filepath.FromSlash("first/doc1.md"), Content: menuPage1}, + {Name: filepath.FromSlash("first/doc2.md"), Content: menuPage2}, + {Name: filepath.FromSlash("second-section/doc3.md"), Content: menuPage3}, + {Name: filepath.FromSlash("Fish and Chips/doc4.md"), Content: menuPage4}, +} + +func tstCreateMenuPageWithNameTOML(title, menu, name string) []byte { + return []byte(fmt.Sprintf(`+++ +title = "%s" +weight = 1 +[menu] + [menu.%s] + name = "%s" ++++ +Front Matter with Menu with Name`, title, menu, name)) +} + +func tstCreateMenuPageWithIdentifierTOML(title, menu, identifier string) []byte { + return []byte(fmt.Sprintf(`+++ +title = "%s" +weight = 1 +[menu] + [menu.%s] + identifier = "%s" + name = "somename" ++++ +Front Matter with Menu with Identifier`, title, menu, identifier)) +} + +func tstCreateMenuPageWithNameYAML(title, menu, name string) []byte { + return []byte(fmt.Sprintf(`--- +title: "%s" +weight: 1 +menu: + %s: + name: "%s" +--- +Front Matter with Menu with Name`, title, menu, name)) +} + +func tstCreateMenuPageWithIdentifierYAML(title, menu, identifier string) []byte { + return []byte(fmt.Sprintf(`--- +title: "%s" +weight: 1 +menu: + %s: + identifier: "%s" + name: "somename" +--- +Front Matter with Menu with Identifier`, title, menu, identifier)) +} + +// Issue 817 - identifier should trump everything +func TestPageMenuWithIdentifier(t *testing.T) { + t.Parallel() + toml := []source.ByteSource{ + {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i1")}, + {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, + {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, // duplicate + } + + yaml := []source.ByteSource{ + {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i1")}, + {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, + {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, // duplicate + } + + doTestPageMenuWithIdentifier(t, toml) + doTestPageMenuWithIdentifier(t, yaml) + +} + +func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSource) { + + s := setupMenuTests(t, menuPageSources) + + assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") + + me1 := findTestMenuEntryByID(s, "m1", "i1") + me2 := findTestMenuEntryByID(s, "m1", "i2") + + require.NotNil(t, me1) + require.NotNil(t, me2) + + assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) + assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) + +} + +// Issue 817 contd - name should be second identifier in +func TestPageMenuWithDuplicateName(t *testing.T) { + t.Parallel() + toml := []source.ByteSource{ + {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n1")}, + {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, + {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, // duplicate + } + + yaml := []source.ByteSource{ + {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n1")}, + {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, + {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, // duplicate + } + + doTestPageMenuWithDuplicateName(t, toml) + doTestPageMenuWithDuplicateName(t, yaml) + +} + +func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.ByteSource) { + + s := setupMenuTests(t, menuPageSources) + + assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") + + me1 := findTestMenuEntryByName(s, "m1", "n1") + me2 := findTestMenuEntryByName(s, "m1", "n2") + + require.NotNil(t, me1) + require.NotNil(t, me2) + + assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) + assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) + +} + +func TestPageMenu(t *testing.T) { + t.Parallel() + s := setupMenuTests(t, menuPageSources) + + if len(s.RegularPages) != 3 { + t.Fatalf("Posts not created, expected 3 got %d", len(s.RegularPages)) + } + + first := s.RegularPages[0] + second := s.RegularPages[1] + third := s.RegularPages[2] + + pOne := findTestMenuEntryByName(s, "p_one", "One") + pTwo := findTestMenuEntryByID(s, "p_two", "Two") + + for i, this := range []struct { + menu string + page *Page + menuItem *MenuEntry + isMenuCurrent bool + hasMenuCurrent bool + }{ + {"p_one", first, pOne, true, false}, + {"p_one", first, pTwo, false, false}, + {"p_one", second, pTwo, false, false}, + {"p_two", second, pTwo, true, false}, + {"p_two", third, pTwo, false, true}, + {"p_one", third, pTwo, false, false}, + } { + + if i != 4 { + continue + } + + isMenuCurrent := this.page.IsMenuCurrent(this.menu, this.menuItem) + hasMenuCurrent := this.page.HasMenuCurrent(this.menu, this.menuItem) + + if isMenuCurrent != this.isMenuCurrent { + t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent) + } + + if hasMenuCurrent != this.hasMenuCurrent { + t.Errorf("[%d] Wrong result for menuItem %v for HasMenuCurrent: %v", i, this.menuItem, hasMenuCurrent) + } + + } + +} + +func TestMenuURL(t *testing.T) { + t.Parallel() + s := setupMenuTests(t, menuPageSources) + + for i, this := range []struct { + me *MenuEntry + expectedURL string + }{ + // issue #888 + {findTestMenuEntryByID(s, "hash", "hash"), "/Zoo/resource#anchor"}, + // issue #1774 + {findTestMenuEntryByID(s, "main", "ext"), "http://gohugo.io"}, + {findTestMenuEntryByID(s, "main", "ext2"), "http://foo.local/Zoo/foo"}, + } { + + if this.me == nil { + t.Errorf("[%d] MenuEntry not found", i) + continue + } + + if this.me.URL != this.expectedURL { + t.Errorf("[%d] Got URL %s expected %s", i, this.me.URL, this.expectedURL) + } + + } + +} + +// Issue #1934 +func TestYAMLMenuWithMultipleEntries(t *testing.T) { + t.Parallel() + ps1 := []byte(`--- +title: "Yaml 1" +weight: 5 +menu: ["p_one", "p_two"] +--- +Yaml Front Matter with Menu Pages`) + + ps2 := []byte(`--- +title: "Yaml 2" +weight: 5 +menu: + p_three: + p_four: +--- +Yaml Front Matter with Menu Pages`) + + s := setupMenuTests(t, []source.ByteSource{ + {Name: filepath.FromSlash("sect/yaml1.md"), Content: ps1}, + {Name: filepath.FromSlash("sect/yaml2.md"), Content: ps2}}) + + p1 := s.RegularPages[0] + assert.Len(t, p1.Menus(), 2, "List YAML") + p2 := s.RegularPages[1] + assert.Len(t, p2.Menus(), 2, "Map YAML") + +} + +// issue #719 +func TestMenuWithUnicodeURLs(t *testing.T) { + t.Parallel() + for _, canonifyURLs := range []bool{true, false} { + doTestMenuWithUnicodeURLs(t, canonifyURLs) + } +} + +func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) { + + s := setupMenuTests(t, menuPageSources, "canonifyURLs", canonifyURLs) + + unicodeRussian := findTestMenuEntryByID(s, "unicode", "unicode-russian") + + expected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0" + + if !canonifyURLs { + expected = "/Zoo" + expected + } + + assert.Equal(t, expected, unicodeRussian.URL) +} + +// Issue #1114 +func TestSectionPagesMenu2(t *testing.T) { + t.Parallel() + doTestSectionPagesMenu(true, t) + doTestSectionPagesMenu(false, t) +} + +func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) { + + s := setupMenuTests(t, menuPageSectionsSources, + "sectionPagesMenu", "spm", + "canonifyURLs", canonifyURLs, + ) + + require.Equal(t, 3, len(s.Sections)) + + firstSectionPages := s.Sections["first"] + require.Equal(t, 2, len(firstSectionPages)) + secondSectionPages := s.Sections["second-section"] + require.Equal(t, 1, len(secondSectionPages)) + fishySectionPages := s.Sections["fish-and-chips"] + require.Equal(t, 1, len(fishySectionPages)) + + nodeFirst := s.getPage(KindSection, "first") + require.NotNil(t, nodeFirst) + nodeSecond := s.getPage(KindSection, "second-section") + require.NotNil(t, nodeSecond) + nodeFishy := s.getPage(KindSection, "fish-and-chips") + require.Equal(t, "fish-and-chips", nodeFishy.sections[0]) + + firstSectionMenuEntry := findTestMenuEntryByID(s, "spm", "first") + secondSectionMenuEntry := findTestMenuEntryByID(s, "spm", "second-section") + fishySectionMenuEntry := findTestMenuEntryByID(s, "spm", "fish-and-chips") + + require.NotNil(t, firstSectionMenuEntry) + require.NotNil(t, secondSectionMenuEntry) + require.NotNil(t, nodeFirst) + require.NotNil(t, nodeSecond) + require.NotNil(t, fishySectionMenuEntry) + require.NotNil(t, nodeFishy) + + require.True(t, nodeFirst.IsMenuCurrent("spm", firstSectionMenuEntry)) + require.False(t, nodeFirst.IsMenuCurrent("spm", secondSectionMenuEntry)) + require.False(t, nodeFirst.IsMenuCurrent("spm", fishySectionMenuEntry)) + require.True(t, nodeFishy.IsMenuCurrent("spm", fishySectionMenuEntry)) + require.Equal(t, "Fish and Chips", fishySectionMenuEntry.Name) + + for _, p := range firstSectionPages { + require.True(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) + require.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) + } + + for _, p := range secondSectionPages { + require.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) + require.True(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) + } + + for _, p := range fishySectionPages { + require.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) + require.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) + require.True(t, p.Page.HasMenuCurrent("spm", fishySectionMenuEntry)) + } +} + +func TestTaxonomyNodeMenu(t *testing.T) { + t.Parallel() + + type taxRenderInfo struct { + key string + singular string + plural string + } + + s := setupMenuTests(t, menuPageSources, "canonifyURLs", true) + + for i, this := range []struct { + menu string + taxInfo taxRenderInfo + menuItem *MenuEntry + isMenuCurrent bool + hasMenuCurrent bool + }{ + {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, + findTestMenuEntryByID(s, "tax", "1"), true, false}, + {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, + findTestMenuEntryByID(s, "tax", "2"), true, false}, + {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, + &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false}, + } { + + p := s.newTaxonomyPage(this.taxInfo.plural, this.taxInfo.key) + + isMenuCurrent := p.IsMenuCurrent(this.menu, this.menuItem) + hasMenuCurrent := p.HasMenuCurrent(this.menu, this.menuItem) + + if isMenuCurrent != this.isMenuCurrent { + t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent) + } + + if hasMenuCurrent != this.hasMenuCurrent { + t.Errorf("[%d] Wrong result for menuItem %v for HasMenuCurrent: %v", i, this.menuItem, hasMenuCurrent) + } + + } + + menuEntryXML := findTestMenuEntryByID(s, "tax", "xml") + + if strings.HasSuffix(menuEntryXML.URL, "/") { + t.Error("RSS menu item should not be padded with trailing slash") + } +} + +func TestMenuLimit(t *testing.T) { + t.Parallel() + s := setupMenuTests(t, menuPageSources) + m := *s.Menus["main"] + + // main menu has 4 entries + firstTwo := m.Limit(2) + assert.Equal(t, 2, len(firstTwo)) + for i := 0; i < 2; i++ { + assert.Equal(t, m[i], firstTwo[i]) + } + assert.Equal(t, m, m.Limit(4)) + assert.Equal(t, m, m.Limit(5)) +} + +func TestMenuSortByN(t *testing.T) { + t.Parallel() + for i, this := range []struct { + sortFunc func(p Menu) Menu + assertFunc func(p Menu) bool + }{ + {(Menu).Sort, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, + {(Menu).ByWeight, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, + {(Menu).ByName, func(p Menu) bool { return p[0].Name == "na" }}, + {(Menu).Reverse, func(p Menu) bool { return p[0].Identifier == "ib" && p[len(p)-1].Identifier == "ia" }}, + } { + menu := Menu{&MenuEntry{Weight: 3, Name: "nb", Identifier: "ia"}, + &MenuEntry{Weight: 1, Name: "na", Identifier: "ic"}, + &MenuEntry{Weight: 1, Name: "nx", Identifier: "ic"}, + &MenuEntry{Weight: 2, Name: "nb", Identifier: "ix"}, + &MenuEntry{Weight: 2, Name: "nb", Identifier: "ib"}} + + sorted := this.sortFunc(menu) + + if !this.assertFunc(sorted) { + t.Errorf("[%d] sort error", i) + } + } + +} + +func TestHomeNodeMenu(t *testing.T) { + t.Parallel() + s := setupMenuTests(t, menuPageSources, + "canonifyURLs", true, + "uglyURLs", false, + ) + + home := s.getPage(KindHome) + homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()} + + for i, this := range []struct { + menu string + menuItem *MenuEntry + isMenuCurrent bool + hasMenuCurrent bool + }{ + {"main", homeMenuEntry, true, false}, + {"doesnotexist", homeMenuEntry, false, false}, + {"main", &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false}, + {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandparentId"), false, true}, + {"grandparent", findTestMenuEntryByID(s, "grandparent", "parentId"), false, true}, + {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandchildId"), true, false}, + } { + + isMenuCurrent := home.IsMenuCurrent(this.menu, this.menuItem) + hasMenuCurrent := home.HasMenuCurrent(this.menu, this.menuItem) + + if isMenuCurrent != this.isMenuCurrent { + fmt.Println("isMenuCurrent", isMenuCurrent) + fmt.Printf("this: %#v\n", this) + t.Errorf("[%d] Wrong result from IsMenuCurrent: %v for %q", i, isMenuCurrent, this.menuItem) + } + + if hasMenuCurrent != this.hasMenuCurrent { + fmt.Println("hasMenuCurrent", hasMenuCurrent) + fmt.Printf("this: %#v\n", this) + t.Errorf("[%d] Wrong result for menu %q menuItem %v for HasMenuCurrent: %v", i, this.menu, this.menuItem, hasMenuCurrent) + } + } +} + +func TestHopefullyUniqueID(t *testing.T) { + t.Parallel() + assert.Equal(t, "i", (&MenuEntry{Identifier: "i", URL: "u", Name: "n"}).hopefullyUniqueID()) + assert.Equal(t, "u", (&MenuEntry{Identifier: "", URL: "u", Name: "n"}).hopefullyUniqueID()) + assert.Equal(t, "n", (&MenuEntry{Identifier: "", URL: "", Name: "n"}).hopefullyUniqueID()) +} + +func TestAddMenuEntryChild(t *testing.T) { + t.Parallel() + root := &MenuEntry{Weight: 1} + root.addChild(&MenuEntry{Weight: 2}) + root.addChild(&MenuEntry{Weight: 1}) + assert.Equal(t, 2, len(root.Children)) + assert.Equal(t, 1, root.Children[0].Weight) +} + +var testMenuIdentityMatcher = func(me *MenuEntry, id string) bool { return me.Identifier == id } +var testMenuNameMatcher = func(me *MenuEntry, id string) bool { return me.Name == id } + +func findTestMenuEntryByID(s *Site, mn string, id string) *MenuEntry { + return findTestMenuEntry(s, mn, id, testMenuIdentityMatcher) +} +func findTestMenuEntryByName(s *Site, mn string, id string) *MenuEntry { + return findTestMenuEntry(s, mn, id, testMenuNameMatcher) +} + +func findTestMenuEntry(s *Site, mn string, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { + var found *MenuEntry + if menu, ok := s.Menus[mn]; ok { + for _, me := range *menu { + + if matcher(me, id) { + if found != nil { + panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) + } + found = me + } + + descendant := findDescendantTestMenuEntry(me, id, matcher) + if descendant != nil { + if found != nil { + panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) + } + found = descendant + } + } + } + return found +} + +func findDescendantTestMenuEntry(parent *MenuEntry, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { + var found *MenuEntry + if parent.HasChildren() { + for _, child := range parent.Children { + + if matcher(child, id) { + if found != nil { + panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) + } + found = child + } + + descendant := findDescendantTestMenuEntry(child, id, matcher) + if descendant != nil { + if found != nil { + panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) + } + found = descendant + } + } + } + return found +} + +func setupMenuTests(t *testing.T, pageSources []source.ByteSource, configKeyValues ...interface{}) *Site { + + var ( + cfg, fs = newTestCfg() + ) + + menus, err := tomlToMap(confMenu1) + require.NoError(t, err) + + cfg.Set("menu", menus["menu"]) + cfg.Set("baseURL", "http://foo.local/Zoo/") + + for i := 0; i < len(configKeyValues); i += 2 { + cfg.Set(configKeyValues[i].(string), configKeyValues[i+1]) + } + + for _, src := range pageSources { + writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content)) + + } + + return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) + +} + +func tomlToMap(s string) (map[string]interface{}, error) { + tree, err := toml.Load(s) + + if err != nil { + return nil, err + } + + return tree.ToMap(), nil + +} diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go index bb0846b21..0c3badc7b 100644 --- a/hugolib/menu_test.go +++ b/hugolib/menu_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. +// Copyright 2017 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,677 +14,81 @@ package hugolib import ( - "fmt" - "strings" "testing" - "github.com/spf13/hugo/deps" + "fmt" - "path/filepath" - - toml "github.com/pelletier/go-toml" - "github.com/spf13/hugo/source" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( - confMenu1 = ` -[[menu.main]] - name = "Go Home" - url = "/" - weight = 1 - pre = "
" - post = "
" -[[menu.main]] - name = "Blog" - url = "/posts" -[[menu.main]] - name = "ext" - url = "http://gohugo.io" - identifier = "ext" -[[menu.main]] - name = "ext2" - url = "http://foo.local/Zoo/foo" - identifier = "ext2" -[[menu.grandparent]] - name = "grandparent" - url = "/grandparent" - identifier = "grandparentId" -[[menu.grandparent]] - name = "parent" - url = "/parent" - identifier = "parentId" - parent = "grandparentId" -[[menu.grandparent]] - name = "Go Home3" - url = "/" - identifier = "grandchildId" - parent = "parentId" -[[menu.tax]] - name = "Tax1" - url = "/two/key/" - identifier="1" -[[menu.tax]] - name = "Tax2" - url = "/two/key/" - identifier="2" -[[menu.tax]] - name = "Tax RSS" - url = "/two/key.xml" - identifier="xml" -[[menu.hash]] - name = "Tax With #" - url = "/resource#anchor" - identifier="hash" -[[menu.unicode]] - name = "Unicode Russian" - identifier = "unicode-russian" - url = "/новости-проекта"` // Russian => "news-project" + menuPageTemplate = `--- +title: %q +weight: %d +menu: + %s: + weight: %d +--- +# Doc Menu +` ) -var menuPage1 = []byte(`+++ -title = "One" -weight = 1 -[menu] - [menu.p_one] -+++ -Front Matter with Menu Pages`) - -var menuPage2 = []byte(`+++ -title = "Two" -weight = 2 -[menu] - [menu.p_one] - [menu.p_two] - identifier = "Two" - -+++ -Front Matter with Menu Pages`) - -var menuPage3 = []byte(`+++ -title = "Three" -weight = 3 -[menu] - [menu.p_two] - Name = "Three" - Parent = "Two" -+++ -Front Matter with Menu Pages`) - -var menuPage4 = []byte(`+++ -title = "Four" -weight = 4 -[menu] - [menu.p_two] - Name = "Four" - Parent = "Three" -+++ -Front Matter with Menu Pages`) - -var menuPageSources = []source.ByteSource{ - {Name: filepath.FromSlash("sect/doc1.md"), Content: menuPage1}, - {Name: filepath.FromSlash("sect/doc2.md"), Content: menuPage2}, - {Name: filepath.FromSlash("sect/doc3.md"), Content: menuPage3}, -} - -var menuPageSectionsSources = []source.ByteSource{ - {Name: filepath.FromSlash("first/doc1.md"), Content: menuPage1}, - {Name: filepath.FromSlash("first/doc2.md"), Content: menuPage2}, - {Name: filepath.FromSlash("second-section/doc3.md"), Content: menuPage3}, - {Name: filepath.FromSlash("Fish and Chips/doc4.md"), Content: menuPage4}, -} - -func tstCreateMenuPageWithNameTOML(title, menu, name string) []byte { - return []byte(fmt.Sprintf(`+++ -title = "%s" -weight = 1 -[menu] - [menu.%s] - name = "%s" -+++ -Front Matter with Menu with Name`, title, menu, name)) -} - -func tstCreateMenuPageWithIdentifierTOML(title, menu, identifier string) []byte { - return []byte(fmt.Sprintf(`+++ -title = "%s" -weight = 1 -[menu] - [menu.%s] - identifier = "%s" - name = "somename" -+++ -Front Matter with Menu with Identifier`, title, menu, identifier)) -} - -func tstCreateMenuPageWithNameYAML(title, menu, name string) []byte { - return []byte(fmt.Sprintf(`--- -title: "%s" -weight: 1 -menu: - %s: - name: "%s" ---- -Front Matter with Menu with Name`, title, menu, name)) -} - -func tstCreateMenuPageWithIdentifierYAML(title, menu, identifier string) []byte { - return []byte(fmt.Sprintf(`--- -title: "%s" -weight: 1 -menu: - %s: - identifier: "%s" - name: "somename" ---- -Front Matter with Menu with Identifier`, title, menu, identifier)) -} - -// Issue 817 - identifier should trump everything -func TestPageMenuWithIdentifier(t *testing.T) { - t.Parallel() - toml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, // duplicate - } - - yaml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, // duplicate - } - - doTestPageMenuWithIdentifier(t, toml) - doTestPageMenuWithIdentifier(t, yaml) - -} - -func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSource) { - - s := setupMenuTests(t, menuPageSources) - - assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") - - me1 := findTestMenuEntryByID(s, "m1", "i1") - me2 := findTestMenuEntryByID(s, "m1", "i2") - - require.NotNil(t, me1) - require.NotNil(t, me2) - - assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) - assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) - -} - -// Issue 817 contd - name should be second identifier in -func TestPageMenuWithDuplicateName(t *testing.T) { - t.Parallel() - toml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, // duplicate - } - - yaml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, // duplicate - } - - doTestPageMenuWithDuplicateName(t, toml) - doTestPageMenuWithDuplicateName(t, yaml) - -} - -func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.ByteSource) { - - s := setupMenuTests(t, menuPageSources) - - assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") - - me1 := findTestMenuEntryByName(s, "m1", "n1") - me2 := findTestMenuEntryByName(s, "m1", "n2") - - require.NotNil(t, me1) - require.NotNil(t, me2) - - assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) - assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) - -} - -func TestPageMenu(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - - if len(s.RegularPages) != 3 { - t.Fatalf("Posts not created, expected 3 got %d", len(s.RegularPages)) - } - - first := s.RegularPages[0] - second := s.RegularPages[1] - third := s.RegularPages[2] - - pOne := findTestMenuEntryByName(s, "p_one", "One") - pTwo := findTestMenuEntryByID(s, "p_two", "Two") - - for i, this := range []struct { - menu string - page *Page - menuItem *MenuEntry - isMenuCurrent bool - hasMenuCurrent bool - }{ - {"p_one", first, pOne, true, false}, - {"p_one", first, pTwo, false, false}, - {"p_one", second, pTwo, false, false}, - {"p_two", second, pTwo, true, false}, - {"p_two", third, pTwo, false, true}, - {"p_one", third, pTwo, false, false}, - } { - - if i != 4 { - continue - } - - isMenuCurrent := this.page.IsMenuCurrent(this.menu, this.menuItem) - hasMenuCurrent := this.page.HasMenuCurrent(this.menu, this.menuItem) - - if isMenuCurrent != this.isMenuCurrent { - t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent) - } - - if hasMenuCurrent != this.hasMenuCurrent { - t.Errorf("[%d] Wrong result for menuItem %v for HasMenuCurrent: %v", i, this.menuItem, hasMenuCurrent) - } - - } - -} - -func TestMenuURL(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - - for i, this := range []struct { - me *MenuEntry - expectedURL string - }{ - // issue #888 - {findTestMenuEntryByID(s, "hash", "hash"), "/Zoo/resource#anchor"}, - // issue #1774 - {findTestMenuEntryByID(s, "main", "ext"), "http://gohugo.io"}, - {findTestMenuEntryByID(s, "main", "ext2"), "http://foo.local/Zoo/foo"}, - } { - - if this.me == nil { - t.Errorf("[%d] MenuEntry not found", i) - continue - } - - if this.me.URL != this.expectedURL { - t.Errorf("[%d] Got URL %s expected %s", i, this.me.URL, this.expectedURL) - } - - } - -} - -// Issue #1934 -func TestYAMLMenuWithMultipleEntries(t *testing.T) { - t.Parallel() - ps1 := []byte(`--- -title: "Yaml 1" -weight: 5 -menu: ["p_one", "p_two"] ---- -Yaml Front Matter with Menu Pages`) - - ps2 := []byte(`--- -title: "Yaml 2" -weight: 5 -menu: - p_three: - p_four: ---- -Yaml Front Matter with Menu Pages`) - - s := setupMenuTests(t, []source.ByteSource{ - {Name: filepath.FromSlash("sect/yaml1.md"), Content: ps1}, - {Name: filepath.FromSlash("sect/yaml2.md"), Content: ps2}}) - - p1 := s.RegularPages[0] - assert.Len(t, p1.Menus(), 2, "List YAML") - p2 := s.RegularPages[1] - assert.Len(t, p2.Menus(), 2, "Map YAML") - -} - -// issue #719 -func TestMenuWithUnicodeURLs(t *testing.T) { - t.Parallel() - for _, canonifyURLs := range []bool{true, false} { - doTestMenuWithUnicodeURLs(t, canonifyURLs) - } -} - -func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) { - - s := setupMenuTests(t, menuPageSources, "canonifyURLs", canonifyURLs) - - unicodeRussian := findTestMenuEntryByID(s, "unicode", "unicode-russian") - - expected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0" - - if !canonifyURLs { - expected = "/Zoo" + expected - } - - assert.Equal(t, expected, unicodeRussian.URL) -} - -// Issue #1114 func TestSectionPagesMenu(t *testing.T) { t.Parallel() - doTestSectionPagesMenu(true, t) - doTestSectionPagesMenu(false, t) -} -func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) { + siteConfig := ` +baseurl = "http://example.com/" +title = "Section Menu" +sectionPagesMenu = "sect" +` - s := setupMenuTests(t, menuPageSectionsSources, - "sectionPagesMenu", "spm", - "canonifyURLs", canonifyURLs, + th, h := newTestSitesFromConfig(t, siteConfig, + "layouts/partials/menu.html", `{{- $p := .page -}} +{{- $m := .menu -}} +{{ range (index $p.Site.Menus $m) -}} +{{- .URL }}|{{ .Name }}|{{ .Weight -}}| +{{- if $p.IsMenuCurrent $m . }}IsMenuCurrent{{ else }}-{{ end -}}| +{{- if $p.HasMenuCurrent $m . }}HasMenuCurrent{{ else }}-{{ end -}}| +{{- end -}} +`, + "layouts/_default/single.html", + `Single|{{ .Title }} +Menu Sect: {{ partial "menu.html" (dict "page" . "menu" "sect") }} +Menu Main: {{ partial "menu.html" (dict "page" . "menu" "main") }}`, + "layouts/_default/list.html", "List|{{ .Title }}|{{ .Content }}", ) + require.Len(t, h.Sites, 1) - require.Equal(t, 3, len(s.Sections)) + fs := th.Fs - firstSectionPages := s.Sections["first"] - require.Equal(t, 2, len(firstSectionPages)) - secondSectionPages := s.Sections["second-section"] - require.Equal(t, 1, len(secondSectionPages)) - fishySectionPages := s.Sections["fish-and-chips"] - require.Equal(t, 1, len(fishySectionPages)) + writeSource(t, fs, "content/sect1/p1.md", fmt.Sprintf(menuPageTemplate, "p1", 1, "main", 40)) + writeSource(t, fs, "content/sect1/p2.md", fmt.Sprintf(menuPageTemplate, "p2", 2, "main", 30)) + writeSource(t, fs, "content/sect2/p3.md", fmt.Sprintf(menuPageTemplate, "p3", 3, "main", 20)) + writeSource(t, fs, "content/sect2/p4.md", fmt.Sprintf(menuPageTemplate, "p4", 4, "main", 10)) + writeSource(t, fs, "content/sect3/p5.md", fmt.Sprintf(menuPageTemplate, "p5", 5, "main", 5)) - nodeFirst := s.getPage(KindSection, "first") - require.NotNil(t, nodeFirst) - nodeSecond := s.getPage(KindSection, "second-section") - require.NotNil(t, nodeSecond) - nodeFishy := s.getPage(KindSection, "fish-and-chips") - require.Equal(t, "fish-and-chips", nodeFishy.sections[0]) + writeNewContentFile(t, fs, "Section One", "2017-01-01", "content/sect1/_index.md", 100) + writeNewContentFile(t, fs, "Section Five", "2017-01-01", "content/sect5/_index.md", 10) - firstSectionMenuEntry := findTestMenuEntryByID(s, "spm", "first") - secondSectionMenuEntry := findTestMenuEntryByID(s, "spm", "second-section") - fishySectionMenuEntry := findTestMenuEntryByID(s, "spm", "Fish and Chips") + err := h.Build(BuildCfg{}) - require.NotNil(t, firstSectionMenuEntry) - require.NotNil(t, secondSectionMenuEntry) - require.NotNil(t, nodeFirst) - require.NotNil(t, nodeSecond) - require.NotNil(t, fishySectionMenuEntry) - require.NotNil(t, nodeFishy) - - require.True(t, nodeFirst.IsMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, nodeFirst.IsMenuCurrent("spm", secondSectionMenuEntry)) - require.False(t, nodeFirst.IsMenuCurrent("spm", fishySectionMenuEntry)) - require.True(t, nodeFishy.IsMenuCurrent("spm", fishySectionMenuEntry)) - require.Equal(t, "Fish and Chips", fishySectionMenuEntry.Name) - - for _, p := range firstSectionPages { - require.True(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) - } - - for _, p := range secondSectionPages { - require.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.True(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) - } - - for _, p := range fishySectionPages { - require.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry)) - require.True(t, p.Page.HasMenuCurrent("spm", fishySectionMenuEntry)) - } -} - -func TestTaxonomyNodeMenu(t *testing.T) { - t.Parallel() - - type taxRenderInfo struct { - key string - singular string - plural string - } - - s := setupMenuTests(t, menuPageSources, "canonifyURLs", true) - - for i, this := range []struct { - menu string - taxInfo taxRenderInfo - menuItem *MenuEntry - isMenuCurrent bool - hasMenuCurrent bool - }{ - {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, - findTestMenuEntryByID(s, "tax", "1"), true, false}, - {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, - findTestMenuEntryByID(s, "tax", "2"), true, false}, - {"tax", taxRenderInfo{key: "key", singular: "one", plural: "two"}, - &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false}, - } { - - p := s.newTaxonomyPage(this.taxInfo.plural, this.taxInfo.key) - - isMenuCurrent := p.IsMenuCurrent(this.menu, this.menuItem) - hasMenuCurrent := p.HasMenuCurrent(this.menu, this.menuItem) - - if isMenuCurrent != this.isMenuCurrent { - t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent) - } - - if hasMenuCurrent != this.hasMenuCurrent { - t.Errorf("[%d] Wrong result for menuItem %v for HasMenuCurrent: %v", i, this.menuItem, hasMenuCurrent) - } - - } - - menuEntryXML := findTestMenuEntryByID(s, "tax", "xml") - - if strings.HasSuffix(menuEntryXML.URL, "/") { - t.Error("RSS menu item should not be padded with trailing slash") - } -} - -func TestMenuLimit(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - m := *s.Menus["main"] - - // main menu has 4 entries - firstTwo := m.Limit(2) - assert.Equal(t, 2, len(firstTwo)) - for i := 0; i < 2; i++ { - assert.Equal(t, m[i], firstTwo[i]) - } - assert.Equal(t, m, m.Limit(4)) - assert.Equal(t, m, m.Limit(5)) -} - -func TestMenuSortByN(t *testing.T) { - t.Parallel() - for i, this := range []struct { - sortFunc func(p Menu) Menu - assertFunc func(p Menu) bool - }{ - {(Menu).Sort, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, - {(Menu).ByWeight, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, - {(Menu).ByName, func(p Menu) bool { return p[0].Name == "na" }}, - {(Menu).Reverse, func(p Menu) bool { return p[0].Identifier == "ib" && p[len(p)-1].Identifier == "ia" }}, - } { - menu := Menu{&MenuEntry{Weight: 3, Name: "nb", Identifier: "ia"}, - &MenuEntry{Weight: 1, Name: "na", Identifier: "ic"}, - &MenuEntry{Weight: 1, Name: "nx", Identifier: "ic"}, - &MenuEntry{Weight: 2, Name: "nb", Identifier: "ix"}, - &MenuEntry{Weight: 2, Name: "nb", Identifier: "ib"}} - - sorted := this.sortFunc(menu) - - if !this.assertFunc(sorted) { - t.Errorf("[%d] sort error", i) - } - } - -} - -func TestHomeNodeMenu(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources, - "canonifyURLs", true, - "uglyURLs", false, - ) - - home := s.getPage(KindHome) - homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()} - - for i, this := range []struct { - menu string - menuItem *MenuEntry - isMenuCurrent bool - hasMenuCurrent bool - }{ - {"main", homeMenuEntry, true, false}, - {"doesnotexist", homeMenuEntry, false, false}, - {"main", &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandparentId"), false, true}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "parentId"), false, true}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandchildId"), true, false}, - } { - - isMenuCurrent := home.IsMenuCurrent(this.menu, this.menuItem) - hasMenuCurrent := home.HasMenuCurrent(this.menu, this.menuItem) - - if isMenuCurrent != this.isMenuCurrent { - fmt.Println("isMenuCurrent", isMenuCurrent) - fmt.Printf("this: %#v\n", this) - t.Errorf("[%d] Wrong result from IsMenuCurrent: %v for %q", i, isMenuCurrent, this.menuItem) - } - - if hasMenuCurrent != this.hasMenuCurrent { - fmt.Println("hasMenuCurrent", hasMenuCurrent) - fmt.Printf("this: %#v\n", this) - t.Errorf("[%d] Wrong result for menu %q menuItem %v for HasMenuCurrent: %v", i, this.menu, this.menuItem, hasMenuCurrent) - } - } -} - -func TestHopefullyUniqueID(t *testing.T) { - t.Parallel() - assert.Equal(t, "i", (&MenuEntry{Identifier: "i", URL: "u", Name: "n"}).hopefullyUniqueID()) - assert.Equal(t, "u", (&MenuEntry{Identifier: "", URL: "u", Name: "n"}).hopefullyUniqueID()) - assert.Equal(t, "n", (&MenuEntry{Identifier: "", URL: "", Name: "n"}).hopefullyUniqueID()) -} - -func TestAddMenuEntryChild(t *testing.T) { - t.Parallel() - root := &MenuEntry{Weight: 1} - root.addChild(&MenuEntry{Weight: 2}) - root.addChild(&MenuEntry{Weight: 1}) - assert.Equal(t, 2, len(root.Children)) - assert.Equal(t, 1, root.Children[0].Weight) -} - -var testMenuIdentityMatcher = func(me *MenuEntry, id string) bool { return me.Identifier == id } -var testMenuNameMatcher = func(me *MenuEntry, id string) bool { return me.Name == id } - -func findTestMenuEntryByID(s *Site, mn string, id string) *MenuEntry { - return findTestMenuEntry(s, mn, id, testMenuIdentityMatcher) -} -func findTestMenuEntryByName(s *Site, mn string, id string) *MenuEntry { - return findTestMenuEntry(s, mn, id, testMenuNameMatcher) -} - -func findTestMenuEntry(s *Site, mn string, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { - var found *MenuEntry - if menu, ok := s.Menus[mn]; ok { - for _, me := range *menu { - - if matcher(me, id) { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) - } - found = me - } - - descendant := findDescendantTestMenuEntry(me, id, matcher) - if descendant != nil { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) - } - found = descendant - } - } - } - return found -} - -func findDescendantTestMenuEntry(parent *MenuEntry, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { - var found *MenuEntry - if parent.HasChildren() { - for _, child := range parent.Children { - - if matcher(child, id) { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) - } - found = child - } - - descendant := findDescendantTestMenuEntry(child, id, matcher) - if descendant != nil { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) - } - found = descendant - } - } - } - return found -} - -func setupMenuTests(t *testing.T, pageSources []source.ByteSource, configKeyValues ...interface{}) *Site { - - var ( - cfg, fs = newTestCfg() - ) - - menus, err := tomlToMap(confMenu1) require.NoError(t, err) - cfg.Set("menu", menus["menu"]) - cfg.Set("baseURL", "http://foo.local/Zoo/") + s := h.Sites[0] - for i := 0; i < len(configKeyValues); i += 2 { - cfg.Set(configKeyValues[i].(string), configKeyValues[i+1]) - } + require.Len(t, s.Menus, 2) - for _, src := range pageSources { - writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content)) + p1 := s.RegularPages[0].Menus() - } + // There is only one menu in the page, but it is "member of" 2 + require.Len(t, p1, 1) - return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - -} - -func tomlToMap(s string) (map[string]interface{}, error) { - tree, err := toml.Load(s) - - if err != nil { - return nil, err - } - - return tree.ToMap(), nil + th.assertFileContent("public/sect1/p1/index.html", "Single", + "Menu Sect: /sect5/|Section Five|10|-|-|/sect1/|Section One|100|-|HasMenuCurrent|/sect2/|Sect2s|0|-|-|/sect3/|Sect3s|0|-|-|", + "Menu Main: /sect3/p5/|p5|5|-|-|/sect2/p4/|p4|10|-|-|/sect2/p3/|p3|20|-|-|/sect1/p2/|p2|30|-|-|/sect1/p1/|p1|40|IsMenuCurrent|-|", + ) + + th.assertFileContent("public/sect2/p3/index.html", "Single", + "Menu Sect: /sect5/|Section Five|10|-|-|/sect1/|Section One|100|-|-|/sect2/|Sect2s|0|-|HasMenuCurrent|/sect3/|Sect3s|0|-|-|") } diff --git a/hugolib/page.go b/hugolib/page.go index 7ca166290..23e11e5b4 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -621,6 +621,9 @@ func (p *Page) Type() string { } func (p *Page) Section() string { + if p.Kind == KindSection { + return p.sections[0] + } return p.Source.Section() } @@ -1167,8 +1170,16 @@ func (p *Page) HasMenuCurrent(menuID string, me *MenuEntry) bool { sectionPagesMenu := p.Site.sectionPagesMenu // page is labeled as "shadow-member" of the menu with the same identifier as the section - if sectionPagesMenu != "" && p.Section() != "" && sectionPagesMenu == menuID && p.Section() == me.Identifier { - return true + if sectionPagesMenu != "" { + section := p.Section() + + if !p.s.Info.preserveTaxonomyNames { + section = p.s.PathSpec.MakePathSanitized(section) + } + + if section != "" && sectionPagesMenu == menuID && section == me.Identifier { + return true + } } if !me.HasChildren() { @@ -1537,7 +1548,7 @@ func (p *Page) prepareData(s *Site) error { case KindHome: pages = s.RegularPages case KindSection: - sectionData, ok := s.Sections[p.sections[0]] + sectionData, ok := s.Sections[p.Section()] if !ok { return fmt.Errorf("Data for section %s not found", p.Section()) } diff --git a/hugolib/site.go b/hugolib/site.go index dd8169a96..cbb031680 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1366,8 +1366,6 @@ func (s *Site) buildSiteMeta() (err error) { s.assembleSections() - s.assembleMenus() - return } @@ -1442,42 +1440,20 @@ func (s *Site) assembleMenus() { pages := s.Pages if sectionPagesMenu != "" { - // Create menu entries for section pages with content for _, p := range pages { if p.Kind == KindSection { - // menu with same id defined in config, let that one win - if _, ok := flat[twoD{sectionPagesMenu, p.Section()}]; ok { + id := p.Section() + if _, ok := flat[twoD{sectionPagesMenu, id}]; ok { continue } - me := MenuEntry{Identifier: p.Section(), + me := MenuEntry{Identifier: id, Name: p.LinkTitle(), Weight: p.Weight, URL: p.RelPermalink()} - flat[twoD{sectionPagesMenu, me.KeyName()}] = &me } } - - // Create entries for remaining content-less section pages - sectionPagesMenus := make(map[string]interface{}) - for _, p := range pages { - if _, ok := sectionPagesMenus[p.Section()]; !ok { - if p.Section() != "" { - // menu with same id defined in config, let that one win - if _, ok := flat[twoD{sectionPagesMenu, p.Section()}]; ok { - continue - } - - me := MenuEntry{Identifier: p.Section(), - Name: helpers.MakeTitle(helpers.FirstUpper(p.Section())), - URL: s.Info.createNodeMenuEntryURL(p.addLangPathPrefix("/"+p.Section()) + "/")} - - flat[twoD{sectionPagesMenu, me.KeyName()}] = &me - sectionPagesMenus[p.Section()] = true - } - } - } } // Add menu entries provided by pages @@ -1610,7 +1586,8 @@ func (s *Site) assembleSections() { sectionPages := s.findPagesByKind(KindSection) for i, p := range regularPages { - s.Sections.add(s.getTaxonomyKey(p.Section()), WeightedPage{regularPages[i].Weight, regularPages[i]}) + section := s.getTaxonomyKey(p.Section()) + s.Sections.add(section, WeightedPage{regularPages[i].Weight, regularPages[i]}) } // Add sections without regular pages, but with a content page @@ -2135,7 +2112,6 @@ func (s *Site) newTaxonomyPage(plural, key string) *Page { } func (s *Site) newSectionPage(name string, section WeightedPages) *Page { - p := s.newNodePage(KindSection) p.sections = []string{name} diff --git a/hugolib/taxonomy_test.go b/hugolib/taxonomy_test.go index 9ef23279d..209d8d2fe 100644 --- a/hugolib/taxonomy_test.go +++ b/hugolib/taxonomy_test.go @@ -19,11 +19,9 @@ import ( "reflect" "testing" - "github.com/spf13/afero" "github.com/stretchr/testify/require" "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/hugofs" ) func TestByCountOrderOfTaxonomies(t *testing.T) { @@ -79,19 +77,10 @@ others: # Doc ` - mf := afero.NewMemMapFs() + th, h := newTestSitesFromConfigWithDefaultTemplates(t, siteConfig) + require.Len(t, h.Sites, 1) - writeToFs(t, mf, "config.toml", siteConfig) - - cfg, err := LoadConfig(mf, "", "config.toml") - require.NoError(t, err) - - fs := hugofs.NewFrom(mf, cfg) - th := testHelper{cfg, fs, t} - - writeSource(t, fs, "layouts/_default/single.html", "Single|{{ .Title }}|{{ .Content }}") - writeSource(t, fs, "layouts/_default/list.html", "List|{{ .Title }}|{{ .Content }}") - writeSource(t, fs, "layouts/_default/terms.html", "Terms List|{{ .Title }}|{{ .Content }}") + fs := th.Fs writeSource(t, fs, "content/p1.md", fmt.Sprintf(pageTemplate, "t1/c1", "- tag1", "- cat1", "- o1")) writeSource(t, fs, "content/p2.md", fmt.Sprintf(pageTemplate, "t2/c1", "- tag2", "- cat1", "- o1")) @@ -99,12 +88,7 @@ others: writeNewContentFile(t, fs, "Category Terms", "2017-01-01", "content/categories/_index.md", 10) writeNewContentFile(t, fs, "Tag1 List", "2017-01-01", "content/tags/tag1/_index.md", 10) - h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) - - require.NoError(t, err) - require.Len(t, h.Sites, 1) - - err = h.Build(BuildCfg{}) + err := h.Build(BuildCfg{}) require.NoError(t, err) diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 668b46b1d..92af07a46 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -6,12 +6,13 @@ import ( "regexp" - "github.com/spf13/hugo/config" - "github.com/spf13/hugo/deps" - "fmt" "strings" + "github.com/spf13/afero" + "github.com/spf13/hugo/config" + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/source" "github.com/spf13/hugo/tpl" @@ -113,6 +114,39 @@ func newTestSite(t testing.TB, configKeyValues ...interface{}) *Site { return s } +func newTestSitesFromConfig(t testing.TB, tomlConfig string, layoutPathContentPairs ...string) (testHelper, *HugoSites) { + if len(layoutPathContentPairs)%2 != 0 { + t.Fatalf("Layouts must be provided in pairs") + } + mf := afero.NewMemMapFs() + + writeToFs(t, mf, "config.toml", tomlConfig) + + cfg, err := LoadConfig(mf, "", "config.toml") + require.NoError(t, err) + + fs := hugofs.NewFrom(mf, cfg) + th := testHelper{cfg, fs, t} + + for i := 0; i < len(layoutPathContentPairs); i += 2 { + writeSource(t, fs, layoutPathContentPairs[i], layoutPathContentPairs[i+1]) + } + + h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) + + require.NoError(t, err) + + return th, h +} + +func newTestSitesFromConfigWithDefaultTemplates(t testing.TB, tomlConfig string) (testHelper, *HugoSites) { + return newTestSitesFromConfig(t, tomlConfig, + "layouts/_default/single.html", "Single|{{ .Title }}|{{ .Content }}", + "layouts/_default/list.html", "List|{{ .Title }}|{{ .Content }}", + "layouts/_default/terms.html", "Terms List|{{ .Title }}|{{ .Content }}", + ) +} + func newDebugLogger() *jww.Notepad { return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) }