hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

cascade_test.go (15597B)

    1 // Copyright 2019 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package hugolib
   15 
   16 import (
   17 	"bytes"
   18 	"fmt"
   19 	"path"
   20 	"strings"
   21 	"testing"
   22 
   23 	"github.com/gohugoio/hugo/common/maps"
   24 
   25 	qt "github.com/frankban/quicktest"
   26 	"github.com/gohugoio/hugo/parser"
   27 	"github.com/gohugoio/hugo/parser/metadecoders"
   28 )
   29 
   30 func BenchmarkCascade(b *testing.B) {
   31 	allLangs := []string{"en", "nn", "nb", "sv", "ab", "aa", "af", "sq", "kw", "da"}
   32 
   33 	for i := 1; i <= len(allLangs); i += 2 {
   34 		langs := allLangs[0:i]
   35 		b.Run(fmt.Sprintf("langs-%d", len(langs)), func(b *testing.B) {
   36 			c := qt.New(b)
   37 			b.StopTimer()
   38 			builders := make([]*sitesBuilder, b.N)
   39 			for i := 0; i < b.N; i++ {
   40 				builders[i] = newCascadeTestBuilder(b, langs)
   41 			}
   42 			b.StartTimer()
   43 
   44 			for i := 0; i < b.N; i++ {
   45 				builder := builders[i]
   46 				err := builder.BuildE(BuildCfg{})
   47 				c.Assert(err, qt.IsNil)
   48 				first := builder.H.Sites[0]
   49 				c.Assert(first, qt.Not(qt.IsNil))
   50 			}
   51 		})
   52 	}
   53 }
   54 
   55 func BenchmarkCascadeTarget(b *testing.B) {
   56 	files := `
   57 -- content/_index.md --
   58 background = 'yosemite.jpg'
   59 [cascade._target]
   60 kind = '{section,term}'
   61 -- content/posts/_index.md --
   62 -- content/posts/funny/_index.md --
   63 `
   64 
   65 	for i := 1; i < 100; i++ {
   66 		files += fmt.Sprintf("\n-- content/posts/p%d.md --\n", i+1)
   67 	}
   68 
   69 	for i := 1; i < 100; i++ {
   70 		files += fmt.Sprintf("\n-- content/posts/funny/pf%d.md --\n", i+1)
   71 	}
   72 
   73 	b.Run("Kind", func(b *testing.B) {
   74 		cfg := IntegrationTestConfig{
   75 			T:           b,
   76 			TxtarString: files,
   77 		}
   78 		builders := make([]*IntegrationTestBuilder, b.N)
   79 
   80 		for i := range builders {
   81 			builders[i] = NewIntegrationTestBuilder(cfg)
   82 		}
   83 
   84 		b.ResetTimer()
   85 
   86 		for i := 0; i < b.N; i++ {
   87 			builders[i].Build()
   88 		}
   89 	})
   90 }
   91 
   92 func TestCascadeConfig(t *testing.T) {
   93 	c := qt.New(t)
   94 
   95 	// Make sure the cascade from config gets applied even if we're not
   96 	// having a content file for the home page.
   97 	for _, withHomeContent := range []bool{true, false} {
   98 		testName := "Home content file"
   99 		if !withHomeContent {
  100 			testName = "No home content file"
  101 		}
  102 		c.Run(testName, func(c *qt.C) {
  103 			b := newTestSitesBuilder(c)
  104 
  105 			b.WithConfigFile("toml", `
  106 baseURL="https://example.org"
  107 
  108 [cascade]
  109 img1 = "img1-config.jpg"
  110 imgconfig = "img-config.jpg"
  111 
  112 `)
  113 
  114 			if withHomeContent {
  115 				b.WithContent("_index.md", `
  116 ---
  117 title: "Home"
  118 cascade:
  119   img1: "img1-home.jpg"
  120   img2: "img2-home.jpg"
  121 ---
  122 `)
  123 			}
  124 
  125 			b.WithContent("p1.md", ``)
  126 
  127 			b.Build(BuildCfg{})
  128 
  129 			p1 := b.H.Sites[0].getPage("p1")
  130 
  131 			if withHomeContent {
  132 				b.Assert(p1.Params(), qt.DeepEquals, maps.Params{
  133 					"imgconfig":     "img-config.jpg",
  134 					"draft":         bool(false),
  135 					"iscjklanguage": bool(false),
  136 					"img1":          "img1-home.jpg",
  137 					"img2":          "img2-home.jpg",
  138 				})
  139 			} else {
  140 				b.Assert(p1.Params(), qt.DeepEquals, maps.Params{
  141 					"img1":          "img1-config.jpg",
  142 					"imgconfig":     "img-config.jpg",
  143 					"draft":         bool(false),
  144 					"iscjklanguage": bool(false),
  145 				})
  146 			}
  147 		})
  148 
  149 	}
  150 }
  151 
  152 func TestCascade(t *testing.T) {
  153 	allLangs := []string{"en", "nn", "nb", "sv"}
  154 
  155 	langs := allLangs[:3]
  156 
  157 	t.Run(fmt.Sprintf("langs-%d", len(langs)), func(t *testing.T) {
  158 		b := newCascadeTestBuilder(t, langs)
  159 		b.Build(BuildCfg{})
  160 
  161 		b.AssertFileContent("public/index.html", `
  162 12|term|categories/cool/_index.md|Cascade Category|cat.png|categories|HTML-|
  163 12|term|categories/catsect1|catsect1|cat.png|categories|HTML-|
  164 12|term|categories/funny|funny|cat.png|categories|HTML-|
  165 12|taxonomy|categories/_index.md|My Categories|cat.png|categories|HTML-|
  166 32|term|categories/sad/_index.md|Cascade Category|sad.png|categories|HTML-|
  167 42|term|tags/blue|blue|home.png|tags|HTML-|
  168 42|taxonomy|tags|Cascade Home|home.png|tags|HTML-|
  169 42|section|sectnocontent|Cascade Home|home.png|sectnocontent|HTML-|
  170 42|section|sect3|Cascade Home|home.png|sect3|HTML-|
  171 42|page|bundle1/index.md|Cascade Home|home.png|page|HTML-|
  172 42|page|p2.md|Cascade Home|home.png|page|HTML-|
  173 42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-|
  174 42|page|sect3/nofrontmatter.md|Cascade Home|home.png|sect3|HTML-|
  175 42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-|
  176 42|page|sectnocontent/p1.md|Cascade Home|home.png|sectnocontent|HTML-|
  177 42|section|sectnofrontmatter/_index.md|Cascade Home|home.png|sectnofrontmatter|HTML-|
  178 42|term|tags/green|green|home.png|tags|HTML-|
  179 42|home|_index.md|Home|home.png|page|HTML-|
  180 42|page|p1.md|p1|home.png|page|HTML-|
  181 42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-|
  182 42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-|
  183 42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-|
  184 42|page|sect1/s1_2/p2.md|Sect1_2_p2|sect1.png|stype|HTML-|
  185 42|section|sect2/_index.md|Sect2|home.png|sect2|HTML-|
  186 42|page|sect2/p1.md|Sect2_p1|home.png|sect2|HTML-|
  187 52|page|sect4/p1.md|Cascade Home|home.png|sect4|RSS-|
  188 52|section|sect4/_index.md|Sect4|home.png|sect4|RSS-|
  189 `)
  190 
  191 		// Check that type set in cascade gets the correct layout.
  192 		b.AssertFileContent("public/sect1/index.html", `stype list: Sect1`)
  193 		b.AssertFileContent("public/sect1/s1_2/p2/index.html", `stype single: Sect1_2_p2`)
  194 
  195 		// Check output formats set in cascade
  196 		b.AssertFileContent("public/sect4/index.xml", `<link>https://example.org/sect4/index.xml</link>`)
  197 		b.AssertFileContent("public/sect4/p1/index.xml", `<link>https://example.org/sect4/p1/index.xml</link>`)
  198 		b.C.Assert(b.CheckExists("public/sect2/index.xml"), qt.Equals, false)
  199 
  200 		// Check cascade into bundled page
  201 		b.AssertFileContent("public/bundle1/index.html", `Resources: bp1.md|home.png|`)
  202 	})
  203 }
  204 
  205 func TestCascadeEdit(t *testing.T) {
  206 	p1Content := `---
  207 title: P1
  208 ---
  209 `
  210 
  211 	indexContentNoCascade := `
  212 ---
  213 title: Home
  214 ---
  215 `
  216 
  217 	indexContentCascade := `
  218 ---
  219 title: Section
  220 cascade:
  221   banner: post.jpg
  222   layout: postlayout
  223   type: posttype
  224 ---
  225 `
  226 
  227 	layout := `Banner: {{ .Params.banner }}|Layout: {{ .Layout }}|Type: {{ .Type }}|Content: {{ .Content }}`
  228 
  229 	newSite := func(t *testing.T, cascade bool) *sitesBuilder {
  230 		b := newTestSitesBuilder(t).Running()
  231 		b.WithTemplates("_default/single.html", layout)
  232 		b.WithTemplates("_default/list.html", layout)
  233 		if cascade {
  234 			b.WithContent("post/_index.md", indexContentCascade)
  235 		} else {
  236 			b.WithContent("post/_index.md", indexContentNoCascade)
  237 		}
  238 		b.WithContent("post/dir/p1.md", p1Content)
  239 
  240 		return b
  241 	}
  242 
  243 	t.Run("Edit descendant", func(t *testing.T) {
  244 		t.Parallel()
  245 
  246 		b := newSite(t, true)
  247 		b.Build(BuildCfg{})
  248 
  249 		assert := func() {
  250 			b.Helper()
  251 			b.AssertFileContent("public/post/dir/p1/index.html",
  252 				`Banner: post.jpg|`,
  253 				`Layout: postlayout`,
  254 				`Type: posttype`,
  255 			)
  256 		}
  257 
  258 		assert()
  259 
  260 		b.EditFiles("content/post/dir/p1.md", p1Content+"\ncontent edit")
  261 		b.Build(BuildCfg{})
  262 
  263 		assert()
  264 		b.AssertFileContent("public/post/dir/p1/index.html",
  265 			`content edit
  266 Banner: post.jpg`,
  267 		)
  268 	})
  269 
  270 	t.Run("Edit ancestor", func(t *testing.T) {
  271 		t.Parallel()
  272 
  273 		b := newSite(t, true)
  274 		b.Build(BuildCfg{})
  275 
  276 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content:`)
  277 
  278 		b.EditFiles("content/post/_index.md", strings.Replace(indexContentCascade, "post.jpg", "edit.jpg", 1))
  279 
  280 		b.Build(BuildCfg{})
  281 
  282 		b.AssertFileContent("public/post/index.html", `Banner: edit.jpg|Layout: postlayout|Type: posttype|`)
  283 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: edit.jpg|Layout: postlayout|Type: posttype|`)
  284 	})
  285 
  286 	t.Run("Edit ancestor, add cascade", func(t *testing.T) {
  287 		t.Parallel()
  288 
  289 		b := newSite(t, true)
  290 		b.Build(BuildCfg{})
  291 
  292 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg`)
  293 
  294 		b.EditFiles("content/post/_index.md", indexContentCascade)
  295 
  296 		b.Build(BuildCfg{})
  297 
  298 		b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|`)
  299 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
  300 	})
  301 
  302 	t.Run("Edit ancestor, remove cascade", func(t *testing.T) {
  303 		t.Parallel()
  304 
  305 		b := newSite(t, false)
  306 		b.Build(BuildCfg{})
  307 
  308 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: |Layout: |`)
  309 
  310 		b.EditFiles("content/post/_index.md", indexContentNoCascade)
  311 
  312 		b.Build(BuildCfg{})
  313 
  314 		b.AssertFileContent("public/post/index.html", `Banner: |Layout: |Type: post|`)
  315 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: |Layout: |`)
  316 	})
  317 
  318 	t.Run("Edit ancestor, content only", func(t *testing.T) {
  319 		t.Parallel()
  320 
  321 		b := newSite(t, true)
  322 		b.Build(BuildCfg{})
  323 
  324 		b.EditFiles("content/post/_index.md", indexContentCascade+"\ncontent edit")
  325 
  326 		counters := &testCounters{}
  327 		b.Build(BuildCfg{testCounters: counters})
  328 		// As we only changed the content, not the cascade front matter,
  329 		// only the home page is re-rendered.
  330 		b.Assert(int(counters.contentRenderCounter), qt.Equals, 1)
  331 
  332 		b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content: <p>content edit</p>`)
  333 		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
  334 	})
  335 }
  336 
  337 func newCascadeTestBuilder(t testing.TB, langs []string) *sitesBuilder {
  338 	p := func(m map[string]any) string {
  339 		var yamlStr string
  340 
  341 		if len(m) > 0 {
  342 			var b bytes.Buffer
  343 
  344 			parser.InterfaceToConfig(m, metadecoders.YAML, &b)
  345 			yamlStr = b.String()
  346 		}
  347 
  348 		metaStr := "---\n" + yamlStr + "\n---"
  349 
  350 		return metaStr
  351 	}
  352 
  353 	createLangConfig := func(lang string) string {
  354 		const langEntry = `
  355 [languages.%s]
  356 `
  357 		return fmt.Sprintf(langEntry, lang)
  358 	}
  359 
  360 	createMount := func(lang string) string {
  361 		const mountsTempl = `
  362 [[module.mounts]]
  363 source="content/%s"
  364 target="content"
  365 lang="%s"
  366 `
  367 		return fmt.Sprintf(mountsTempl, lang, lang)
  368 	}
  369 
  370 	config := `
  371 baseURL = "https://example.org"
  372 defaultContentLanguage = "en"
  373 defaultContentLanguageInSubDir = false
  374 
  375 [languages]`
  376 	for _, lang := range langs {
  377 		config += createLangConfig(lang)
  378 	}
  379 
  380 	config += "\n\n[module]\n"
  381 	for _, lang := range langs {
  382 		config += createMount(lang)
  383 	}
  384 
  385 	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  386 
  387 	createContentFiles := func(lang string) {
  388 		withContent := func(filenameContent ...string) {
  389 			for i := 0; i < len(filenameContent); i += 2 {
  390 				b.WithContent(path.Join(lang, filenameContent[i]), filenameContent[i+1])
  391 			}
  392 		}
  393 
  394 		withContent(
  395 			"_index.md", p(map[string]any{
  396 				"title": "Home",
  397 				"cascade": map[string]any{
  398 					"title":   "Cascade Home",
  399 					"ICoN":    "home.png",
  400 					"outputs": []string{"HTML"},
  401 					"weight":  42,
  402 				},
  403 			}),
  404 			"p1.md", p(map[string]any{
  405 				"title": "p1",
  406 			}),
  407 			"p2.md", p(map[string]any{}),
  408 			"sect1/_index.md", p(map[string]any{
  409 				"title": "Sect1",
  410 				"type":  "stype",
  411 				"cascade": map[string]any{
  412 					"title":      "Cascade Sect1",
  413 					"icon":       "sect1.png",
  414 					"type":       "stype",
  415 					"categories": []string{"catsect1"},
  416 				},
  417 			}),
  418 			"sect1/s1_2/_index.md", p(map[string]any{
  419 				"title": "Sect1_2",
  420 			}),
  421 			"sect1/s1_2/p1.md", p(map[string]any{
  422 				"title": "Sect1_2_p1",
  423 			}),
  424 			"sect1/s1_2/p2.md", p(map[string]any{
  425 				"title": "Sect1_2_p2",
  426 			}),
  427 			"sect2/_index.md", p(map[string]any{
  428 				"title": "Sect2",
  429 			}),
  430 			"sect2/p1.md", p(map[string]any{
  431 				"title":      "Sect2_p1",
  432 				"categories": []string{"cool", "funny", "sad"},
  433 				"tags":       []string{"blue", "green"},
  434 			}),
  435 			"sect2/p2.md", p(map[string]any{}),
  436 			"sect3/p1.md", p(map[string]any{}),
  437 
  438 			// No front matter, see #6855
  439 			"sect3/nofrontmatter.md", `**Hello**`,
  440 			"sectnocontent/p1.md", `**Hello**`,
  441 			"sectnofrontmatter/_index.md", `**Hello**`,
  442 
  443 			"sect4/_index.md", p(map[string]any{
  444 				"title": "Sect4",
  445 				"cascade": map[string]any{
  446 					"weight":  52,
  447 					"outputs": []string{"RSS"},
  448 				},
  449 			}),
  450 			"sect4/p1.md", p(map[string]any{}),
  451 			"p2.md", p(map[string]any{}),
  452 			"bundle1/index.md", p(map[string]any{}),
  453 			"bundle1/bp1.md", p(map[string]any{}),
  454 			"categories/_index.md", p(map[string]any{
  455 				"title": "My Categories",
  456 				"cascade": map[string]any{
  457 					"title":  "Cascade Category",
  458 					"icoN":   "cat.png",
  459 					"weight": 12,
  460 				},
  461 			}),
  462 			"categories/cool/_index.md", p(map[string]any{}),
  463 			"categories/sad/_index.md", p(map[string]any{
  464 				"cascade": map[string]any{
  465 					"icon":   "sad.png",
  466 					"weight": 32,
  467 				},
  468 			}),
  469 		)
  470 	}
  471 
  472 	createContentFiles("en")
  473 
  474 	b.WithTemplates("index.html", `
  475 	
  476 {{ range .Site.Pages }}
  477 {{- .Weight }}|{{ .Kind }}|{{ path.Join .Path }}|{{ .Title }}|{{ .Params.icon }}|{{ .Type }}|{{ range .OutputFormats }}{{ .Name }}-{{ end }}|
  478 {{ end }}
  479 `,
  480 
  481 		"_default/single.html", "default single: {{ .Title }}|{{ .RelPermalink }}|{{ .Content }}|Resources: {{ range .Resources }}{{ .Name }}|{{ .Params.icon }}|{{ .Content }}{{ end }}",
  482 		"_default/list.html", "default list: {{ .Title }}",
  483 		"stype/single.html", "stype single: {{ .Title }}|{{ .RelPermalink }}|{{ .Content }}",
  484 		"stype/list.html", "stype list: {{ .Title }}",
  485 	)
  486 
  487 	return b
  488 }
  489 
  490 func TestCascadeTarget(t *testing.T) {
  491 	t.Parallel()
  492 
  493 	c := qt.New(t)
  494 
  495 	newBuilder := func(c *qt.C) *sitesBuilder {
  496 		b := newTestSitesBuilder(c)
  497 
  498 		b.WithTemplates("index.html", `
  499 {{ $p1 := site.GetPage "s1/p1" }}
  500 {{ $s1 := site.GetPage "s1" }}
  501 
  502 P1|p1:{{ $p1.Params.p1 }}|p2:{{ $p1.Params.p2 }}|
  503 S1|p1:{{ $s1.Params.p1 }}|p2:{{ $s1.Params.p2 }}|
  504 `)
  505 		b.WithContent("s1/_index.md", "---\ntitle: s1 section\n---")
  506 		b.WithContent("s1/p1/index.md", "---\ntitle: p1\n---")
  507 		b.WithContent("s1/p2/index.md", "---\ntitle: p2\n---")
  508 		b.WithContent("s2/p1/index.md", "---\ntitle: p1_2\n---")
  509 
  510 		return b
  511 	}
  512 
  513 	c.Run("slice", func(c *qt.C) {
  514 		b := newBuilder(c)
  515 		b.WithContent("_index.md", `+++
  516 title = "Home"
  517 [[cascade]]
  518 p1 = "p1"
  519 [[cascade]]
  520 p2 = "p2"
  521 +++
  522 `)
  523 
  524 		b.Build(BuildCfg{})
  525 
  526 		b.AssertFileContent("public/index.html", "P1|p1:p1|p2:p2")
  527 	})
  528 
  529 	c.Run("slice with _target", func(c *qt.C) {
  530 		b := newBuilder(c)
  531 
  532 		b.WithContent("_index.md", `+++
  533 title = "Home"
  534 [[cascade]]
  535 p1 = "p1"
  536 [cascade._target]
  537 path="**p1**"
  538 [[cascade]]
  539 p2 = "p2"
  540 [cascade._target]
  541 kind="section"
  542 +++
  543 `)
  544 
  545 		b.Build(BuildCfg{})
  546 
  547 		b.AssertFileContent("public/index.html", `
  548 P1|p1:p1|p2:|
  549 S1|p1:|p2:p2|
  550 `)
  551 	})
  552 
  553 	c.Run("slice with environment _target", func(c *qt.C) {
  554 		b := newBuilder(c)
  555 
  556 		b.WithContent("_index.md", `+++
  557 title = "Home"
  558 [[cascade]]
  559 p1 = "p1"
  560 [cascade._target]
  561 path="**p1**"
  562 environment="testing"
  563 [[cascade]]
  564 p2 = "p2"
  565 [cascade._target]
  566 kind="section"
  567 environment="production"
  568 +++
  569 `)
  570 
  571 		b.Build(BuildCfg{})
  572 
  573 		b.AssertFileContent("public/index.html", `
  574 P1|p1:|p2:|
  575 S1|p1:|p2:p2|
  576 `)
  577 	})
  578 
  579 	c.Run("slice with yaml _target", func(c *qt.C) {
  580 		b := newBuilder(c)
  581 
  582 		b.WithContent("_index.md", `---
  583 title: "Home"
  584 cascade:
  585 - p1: p1
  586   _target:
  587     path: "**p1**"
  588 - p2: p2
  589   _target:
  590     kind: "section"
  591 ---
  592 `)
  593 
  594 		b.Build(BuildCfg{})
  595 
  596 		b.AssertFileContent("public/index.html", `
  597 P1|p1:p1|p2:|
  598 S1|p1:|p2:p2|
  599 `)
  600 	})
  601 
  602 	c.Run("slice with json _target", func(c *qt.C) {
  603 		b := newBuilder(c)
  604 
  605 		b.WithContent("_index.md", `{
  606 "title": "Home",
  607 "cascade": [
  608   {
  609     "p1": "p1",
  610 	"_target": {
  611 	  "path": "**p1**"
  612     }
  613   },{
  614     "p2": "p2",
  615 	"_target": {
  616       "kind": "section"
  617     }
  618   }
  619 ]
  620 }
  621 `)
  622 
  623 		b.Build(BuildCfg{})
  624 
  625 		b.AssertFileContent("public/index.html", `
  626 		P1|p1:p1|p2:|
  627 		S1|p1:|p2:p2|
  628 		`)
  629 	})
  630 }