URI: 
       hugolib: Finish menu vs section content pages - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit a3af4fe46e1e1d184538a83bc8375154a9669316
   DIR parent 2a6b26a7a520bf88af1413f3f00c7e2782abe8cd
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Mon, 20 Feb 2017 09:33:35 +0100
       
       hugolib: Finish menu vs section content pages
       
       This commit also fixes the default menu sort when the weight is 0.
       
       Closes #2974
       
       Diffstat:
         M hugolib/hugo_sites_build.go         |       1 +
         M hugolib/hugo_sites_build_test.go    |       4 ++--
         M hugolib/menu.go                     |       9 +++++++++
         A hugolib/menu_old_test.go            |     693 +++++++++++++++++++++++++++++++
         M hugolib/menu_test.go                |     698 +++----------------------------
         M hugolib/page.go                     |      17 ++++++++++++++---
         M hugolib/site.go                     |      34 +++++--------------------------
         M hugolib/taxonomy_test.go            |      24 ++++--------------------
         M hugolib/testhelpers_test.go         |      40 ++++++++++++++++++++++++++++---
       
       9 files changed, 816 insertions(+), 704 deletions(-)
       ---
   DIR diff --git 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()
                }
   DIR diff --git 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)
                }
   DIR diff --git 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
        }
        
   DIR diff --git a/hugolib/menu_old_test.go 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 = "<div>"
       +        post = "</div>"
       +[[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
       +
       +}
   DIR diff --git 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"
       -
       -        "path/filepath"
       +        "fmt"
        
       -        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 = "<div>"
       -        post = "</div>"
       -[[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
       +        menuPageTemplate = `---
       +title: %q
       +weight: %d
        menu:
       -    p_three:
       -    p_four:
       +  %s:
       +    weight: %d
        ---
       -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)
       -}
       +# Doc Menu
       +`
       +)
        
       -// Issue #1114
        func TestSectionPagesMenu(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,
       +        siteConfig := `
       +baseurl = "http://example.com/"
       +title = "Section Menu"
       +sectionPagesMenu = "sect"
       +`
       +
       +        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))
       -
       -        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)
       +        fs := th.Fs
        
       -        for _, p := range firstSectionPages {
       -                require.True(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry))
       -                require.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry))
       -        }
       +        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))
        
       -        for _, p := range secondSectionPages {
       -                require.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry))
       -                require.True(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry))
       -        }
       +        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)
        
       -        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"}}
       +        err := h.Build(BuildCfg{})
        
       -                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])
       -        }
       +        s := h.Sites[0]
        
       -        for _, src := range pageSources {
       -                writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
       +        require.Len(t, s.Menus, 2)
        
       -        }
       +        p1 := s.RegularPages[0].Menus()
        
       -        return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
       +        // There is only one menu in the page, but it is "member of" 2
       +        require.Len(t, p1, 1)
        
       -}
       -
       -func tomlToMap(s string) (map[string]interface{}, error) {
       -        tree, err := toml.Load(s)
       -
       -        if err != nil {
       -                return nil, err
       -        }
       +        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|-|",
       +        )
        
       -        return tree.ToMap(), nil
       +        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|-|-|")
        
        }
   DIR diff --git 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())
                        }
   DIR diff --git 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}
        
   DIR diff --git 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()
       -
       -        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}
       +        th, h := newTestSitesFromConfigWithDefaultTemplates(t, siteConfig)
       +        require.Len(t, h.Sites, 1)
        
       -        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)
        
   DIR diff --git 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)
        }