github.com/neohugo/neohugo@v0.123.8/hugolib/cascade_test.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"path"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/neohugo/neohugo/common/maps"
    24  
    25  	qt "github.com/frankban/quicktest"
    26  	"github.com/neohugo/neohugo/parser"
    27  	"github.com/neohugo/neohugo/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].getPageOldVersion("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.H.Sites[0].pageMap.debugPrint("", 999, os.Stdout)
   162  
   163  		// 12|term|/categories/cool|Cascade Category|cat.png|page|html-|\
   164  
   165  		b.AssertFileContent("public/index.html", `
   166  12|term|/categories/cool|Cascade Category|cat.png|categories|html-|
   167  12|term|/categories/catsect1|Cascade Category|cat.png|categories|html-|
   168  12|term|/categories/funny|Cascade Category|cat.png|categories|html-|
   169  12|taxonomy|/categories|My Categories|cat.png|categories|html-|
   170  32|term|/categories/sad|Cascade Category|sad.png|categories|html-|
   171  42|term|/tags/blue|Cascade Home|home.png|tags|html-|
   172  42|taxonomy|/tags|Cascade Home|home.png|tags|html-|
   173  42|section|/sectnocontent|Cascade Home|home.png|sectnocontent|html-|
   174  42|section|/sect3|Cascade Home|home.png|sect3|html-|
   175  42|page|/bundle1|Cascade Home|home.png|page|html-|
   176  42|page|/p2|Cascade Home|home.png|page|html-|
   177  42|page|/sect2/p2|Cascade Home|home.png|sect2|html-|
   178  42|page|/sect3/nofrontmatter|Cascade Home|home.png|sect3|html-|
   179  42|page|/sect3/p1|Cascade Home|home.png|sect3|html-|
   180  42|page|/sectnocontent/p1|Cascade Home|home.png|sectnocontent|html-|
   181  42|section|/sectnofrontmatter|Cascade Home|home.png|sectnofrontmatter|html-|
   182  42|term|/tags/green|Cascade Home|home.png|tags|html-|
   183  42|home|/|Home|home.png|page|html-|
   184  42|page|/p1|p1|home.png|page|html-|
   185  42|section|/sect1|Sect1|sect1.png|stype|html-|
   186  42|section|/sect1/s1_2|Sect1_2|sect1.png|stype|html-|
   187  42|page|/sect1/s1_2/p1|Sect1_2_p1|sect1.png|stype|html-|
   188  42|page|/sect1/s1_2/p2|Sect1_2_p2|sect1.png|stype|html-|
   189  42|section|/sect2|Sect2|home.png|sect2|html-|
   190  42|page|/sect2/p1|Sect2_p1|home.png|sect2|html-|
   191  52|page|/sect4/p1|Cascade Home|home.png|sect4|rss-|
   192  52|section|/sect4|Sect4|home.png|sect4|rss-|
   193  `)
   194  
   195  		// Check that type set in cascade gets the correct layout.
   196  		b.AssertFileContent("public/sect1/index.html", `stype list: Sect1`)
   197  		b.AssertFileContent("public/sect1/s1_2/p2/index.html", `stype single: Sect1_2_p2`)
   198  
   199  		// Check output formats set in cascade
   200  		b.AssertFileContent("public/sect4/index.xml", `<link>https://example.org/sect4/index.xml</link>`)
   201  		b.AssertFileContent("public/sect4/p1/index.xml", `<link>https://example.org/sect4/p1/index.xml</link>`)
   202  		b.C.Assert(b.CheckExists("public/sect2/index.xml"), qt.Equals, false)
   203  
   204  		// Check cascade into bundled page
   205  		b.AssertFileContent("public/bundle1/index.html", `Resources: bp1.md|home.png|`)
   206  	})
   207  }
   208  
   209  func TestCascadeEdit(t *testing.T) {
   210  	p1Content := `---
   211  title: P1
   212  ---
   213  `
   214  
   215  	indexContentNoCascade := `
   216  ---
   217  title: Home
   218  ---
   219  `
   220  
   221  	indexContentCascade := `
   222  ---
   223  title: Section
   224  cascade:
   225    banner: post.jpg
   226    layout: postlayout
   227    type: posttype
   228  ---
   229  `
   230  
   231  	layout := `Banner: {{ .Params.banner }}|Layout: {{ .Layout }}|Type: {{ .Type }}|Content: {{ .Content }}`
   232  
   233  	newSite := func(t *testing.T, cascade bool) *sitesBuilder {
   234  		b := newTestSitesBuilder(t).Running()
   235  		b.WithTemplates("_default/single.html", layout)
   236  		b.WithTemplates("_default/list.html", layout)
   237  		if cascade {
   238  			b.WithContent("post/_index.md", indexContentCascade)
   239  		} else {
   240  			b.WithContent("post/_index.md", indexContentNoCascade)
   241  		}
   242  		b.WithContent("post/dir/p1.md", p1Content)
   243  
   244  		return b
   245  	}
   246  
   247  	t.Run("Edit descendant", func(t *testing.T) {
   248  		t.Parallel()
   249  
   250  		b := newSite(t, true)
   251  		b.Build(BuildCfg{})
   252  
   253  		assert := func() {
   254  			b.Helper()
   255  			b.AssertFileContent("public/post/dir/p1/index.html",
   256  				`Banner: post.jpg|`,
   257  				`Layout: postlayout`,
   258  				`Type: posttype`,
   259  			)
   260  		}
   261  
   262  		assert()
   263  
   264  		b.EditFiles("content/post/dir/p1.md", p1Content+"\ncontent edit")
   265  		b.Build(BuildCfg{})
   266  
   267  		assert()
   268  		b.AssertFileContent("public/post/dir/p1/index.html",
   269  			`content edit
   270  	Banner: post.jpg`,
   271  		)
   272  	})
   273  
   274  	t.Run("Edit ancestor", func(t *testing.T) {
   275  		t.Parallel()
   276  
   277  		b := newSite(t, true)
   278  		b.Build(BuildCfg{})
   279  
   280  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content:`)
   281  
   282  		b.EditFiles("content/post/_index.md", strings.Replace(indexContentCascade, "post.jpg", "edit.jpg", 1))
   283  
   284  		b.Build(BuildCfg{})
   285  
   286  		b.AssertFileContent("public/post/index.html", `Banner: edit.jpg|Layout: postlayout|Type: posttype|`)
   287  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: edit.jpg|Layout: postlayout|Type: posttype|`)
   288  	})
   289  
   290  	t.Run("Edit ancestor, add cascade", func(t *testing.T) {
   291  		t.Parallel()
   292  
   293  		b := newSite(t, true)
   294  		b.Build(BuildCfg{})
   295  
   296  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg`)
   297  
   298  		b.EditFiles("content/post/_index.md", indexContentCascade)
   299  
   300  		b.Build(BuildCfg{})
   301  
   302  		b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|`)
   303  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
   304  	})
   305  
   306  	t.Run("Edit ancestor, remove cascade", func(t *testing.T) {
   307  		t.Parallel()
   308  
   309  		b := newSite(t, false)
   310  		b.Build(BuildCfg{})
   311  
   312  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: |Layout: |`)
   313  
   314  		b.EditFiles("content/post/_index.md", indexContentNoCascade)
   315  
   316  		b.Build(BuildCfg{})
   317  
   318  		b.AssertFileContent("public/post/index.html", `Banner: |Layout: |Type: post|`)
   319  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: |Layout: |`)
   320  	})
   321  
   322  	t.Run("Edit ancestor, content only", func(t *testing.T) {
   323  		t.Parallel()
   324  
   325  		b := newSite(t, true)
   326  		b.Build(BuildCfg{})
   327  
   328  		b.EditFiles("content/post/_index.md", indexContentCascade+"\ncontent edit")
   329  
   330  		counters := &buildCounters{}
   331  		b.Build(BuildCfg{testCounters: counters})
   332  		b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 2)
   333  
   334  		b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content: <p>content edit</p>`)
   335  		b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
   336  	})
   337  }
   338  
   339  func TestCascadeBuildOptionsTaxonomies(t *testing.T) {
   340  	t.Parallel()
   341  
   342  	files := `
   343  -- hugo.toml --
   344  baseURL="https://example.org"
   345  [taxonomies]
   346  tag = "tags"
   347  
   348  [[cascade]]
   349  
   350  [cascade._build]
   351  render = "never"
   352  list = "never"
   353  publishResources = false
   354  
   355  [cascade._target]
   356  path = '/hidden/**'
   357  -- content/p1.md --
   358  ---
   359  title: P1
   360  ---
   361  -- content/hidden/p2.md --
   362  ---
   363  title: P2
   364  tags: [t1, t2]
   365  ---
   366  -- layouts/_default/list.html --
   367  List: {{ len .Pages }}|
   368  -- layouts/_default/single.html --
   369  Single: Tags: {{ site.Taxonomies.tags }}|
   370  `
   371  
   372  	b := Test(t, files)
   373  
   374  	b.AssertFileContent("public/p1/index.html", "Single: Tags: map[]|")
   375  	b.AssertFileContent("public/tags/index.html", "List: 0|")
   376  	b.AssertFileExists("public/hidden/p2/index.html", false)
   377  	b.AssertFileExists("public/tags/t2/index.html", false)
   378  }
   379  
   380  func newCascadeTestBuilder(t testing.TB, langs []string) *sitesBuilder {
   381  	c := qt.New(t)
   382  	p := func(m map[string]any) string {
   383  		var yamlStr string
   384  
   385  		if len(m) > 0 {
   386  			var b bytes.Buffer
   387  
   388  			c.Assert(parser.InterfaceToConfig(m, metadecoders.YAML, &b), qt.IsNil)
   389  			yamlStr = b.String()
   390  		}
   391  
   392  		metaStr := "---\n" + yamlStr + "\n---"
   393  
   394  		return metaStr
   395  	}
   396  
   397  	createLangConfig := func(lang string) string {
   398  		const langEntry = `
   399  [languages.%s]
   400  `
   401  		return fmt.Sprintf(langEntry, lang)
   402  	}
   403  
   404  	createMount := func(lang string) string {
   405  		const mountsTempl = `
   406  [[module.mounts]]
   407  source="content/%s"
   408  target="content"
   409  lang="%s"
   410  `
   411  		return fmt.Sprintf(mountsTempl, lang, lang)
   412  	}
   413  
   414  	config := `
   415  baseURL = "https://example.org"
   416  defaultContentLanguage = "en"
   417  defaultContentLanguageInSubDir = false
   418  
   419  [languages]`
   420  	for _, lang := range langs {
   421  		config += createLangConfig(lang)
   422  	}
   423  
   424  	config += "\n\n[module]\n"
   425  	for _, lang := range langs {
   426  		config += createMount(lang)
   427  	}
   428  
   429  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
   430  
   431  	createContentFiles := func(lang string) {
   432  		withContent := func(filenameContent ...string) {
   433  			for i := 0; i < len(filenameContent); i += 2 {
   434  				b.WithContent(path.Join(lang, filenameContent[i]), filenameContent[i+1])
   435  			}
   436  		}
   437  
   438  		withContent(
   439  			"_index.md", p(map[string]any{
   440  				"title": "Home",
   441  				"cascade": map[string]any{
   442  					"title":   "Cascade Home",
   443  					"ICoN":    "home.png",
   444  					"outputs": []string{"HTML"},
   445  					"weight":  42,
   446  				},
   447  			}),
   448  			"p1.md", p(map[string]any{
   449  				"title": "p1",
   450  			}),
   451  			"p2.md", p(map[string]any{}),
   452  			"sect1/_index.md", p(map[string]any{
   453  				"title": "Sect1",
   454  				"type":  "stype",
   455  				"cascade": map[string]any{
   456  					"title":      "Cascade Sect1",
   457  					"icon":       "sect1.png",
   458  					"type":       "stype",
   459  					"categories": []string{"catsect1"},
   460  				},
   461  			}),
   462  			"sect1/s1_2/_index.md", p(map[string]any{
   463  				"title": "Sect1_2",
   464  			}),
   465  			"sect1/s1_2/p1.md", p(map[string]any{
   466  				"title": "Sect1_2_p1",
   467  			}),
   468  			"sect1/s1_2/p2.md", p(map[string]any{
   469  				"title": "Sect1_2_p2",
   470  			}),
   471  			"sect2/_index.md", p(map[string]any{
   472  				"title": "Sect2",
   473  			}),
   474  			"sect2/p1.md", p(map[string]any{
   475  				"title":      "Sect2_p1",
   476  				"categories": []string{"cool", "funny", "sad"},
   477  				"tags":       []string{"blue", "green"},
   478  			}),
   479  			"sect2/p2.md", p(map[string]any{}),
   480  			"sect3/p1.md", p(map[string]any{}),
   481  
   482  			// No front matter, see #6855
   483  			"sect3/nofrontmatter.md", `**Hello**`,
   484  			"sectnocontent/p1.md", `**Hello**`,
   485  			"sectnofrontmatter/_index.md", `**Hello**`,
   486  
   487  			"sect4/_index.md", p(map[string]any{
   488  				"title": "Sect4",
   489  				"cascade": map[string]any{
   490  					"weight":  52,
   491  					"outputs": []string{"RSS"},
   492  				},
   493  			}),
   494  			"sect4/p1.md", p(map[string]any{}),
   495  			"p2.md", p(map[string]any{}),
   496  			"bundle1/index.md", p(map[string]any{}),
   497  			"bundle1/bp1.md", p(map[string]any{}),
   498  			"categories/_index.md", p(map[string]any{
   499  				"title": "My Categories",
   500  				"cascade": map[string]any{
   501  					"title":  "Cascade Category",
   502  					"icoN":   "cat.png",
   503  					"weight": 12,
   504  				},
   505  			}),
   506  			"categories/cool/_index.md", p(map[string]any{}),
   507  			"categories/sad/_index.md", p(map[string]any{
   508  				"cascade": map[string]any{
   509  					"icon":   "sad.png",
   510  					"weight": 32,
   511  				},
   512  			}),
   513  		)
   514  	}
   515  
   516  	createContentFiles("en")
   517  
   518  	b.WithTemplates("index.html", `
   519  
   520  {{ range .Site.Pages }}
   521  {{- .Weight }}|{{ .Kind }}|{{ .Path }}|{{ .Title }}|{{ .Params.icon }}|{{ .Type }}|{{ range .OutputFormats }}{{ .Name }}-{{ end }}|
   522  {{ end }}
   523  `,
   524  
   525  		"_default/single.html", "default single: {{ .Title }}|{{ .RelPermalink }}|{{ .Content }}|Resources: {{ range .Resources }}{{ .Name }}|{{ .Params.icon }}|{{ .Content }}{{ end }}",
   526  		"_default/list.html", "default list: {{ .Title }}",
   527  		"stype/single.html", "stype single: {{ .Title }}|{{ .RelPermalink }}|{{ .Content }}",
   528  		"stype/list.html", "stype list: {{ .Title }}",
   529  	)
   530  
   531  	return b
   532  }
   533  
   534  func TestCascadeTarget(t *testing.T) {
   535  	t.Parallel()
   536  
   537  	c := qt.New(t)
   538  
   539  	newBuilder := func(c *qt.C) *sitesBuilder {
   540  		b := newTestSitesBuilder(c)
   541  
   542  		b.WithTemplates("index.html", `
   543  {{ $p1 := site.GetPage "s1/p1" }}
   544  {{ $s1 := site.GetPage "s1" }}
   545  
   546  P1|p1:{{ $p1.Params.p1 }}|p2:{{ $p1.Params.p2 }}|
   547  S1|p1:{{ $s1.Params.p1 }}|p2:{{ $s1.Params.p2 }}|
   548  `)
   549  		b.WithContent("s1/_index.md", "---\ntitle: s1 section\n---")
   550  		b.WithContent("s1/p1/index.md", "---\ntitle: p1\n---")
   551  		b.WithContent("s1/p2/index.md", "---\ntitle: p2\n---")
   552  		b.WithContent("s2/p1/index.md", "---\ntitle: p1_2\n---")
   553  
   554  		return b
   555  	}
   556  
   557  	c.Run("slice", func(c *qt.C) {
   558  		b := newBuilder(c)
   559  		b.WithContent("_index.md", `+++
   560  title = "Home"
   561  [[cascade]]
   562  p1 = "p1"
   563  [[cascade]]
   564  p2 = "p2"
   565  +++
   566  `)
   567  
   568  		b.Build(BuildCfg{})
   569  
   570  		b.AssertFileContent("public/index.html", "P1|p1:p1|p2:p2")
   571  	})
   572  
   573  	c.Run("slice with _target", func(c *qt.C) {
   574  		b := newBuilder(c)
   575  
   576  		b.WithContent("_index.md", `+++
   577  title = "Home"
   578  [[cascade]]
   579  p1 = "p1"
   580  [cascade._target]
   581  path="**p1**"
   582  [[cascade]]
   583  p2 = "p2"
   584  [cascade._target]
   585  kind="section"
   586  +++
   587  `)
   588  
   589  		b.Build(BuildCfg{})
   590  
   591  		b.AssertFileContent("public/index.html", `
   592  P1|p1:p1|p2:|
   593  S1|p1:|p2:p2|
   594  `)
   595  	})
   596  
   597  	c.Run("slice with environment _target", func(c *qt.C) {
   598  		b := newBuilder(c)
   599  
   600  		b.WithContent("_index.md", `+++
   601  title = "Home"
   602  [[cascade]]
   603  p1 = "p1"
   604  [cascade._target]
   605  path="**p1**"
   606  environment="testing"
   607  [[cascade]]
   608  p2 = "p2"
   609  [cascade._target]
   610  kind="section"
   611  environment="production"
   612  +++
   613  `)
   614  
   615  		b.Build(BuildCfg{})
   616  
   617  		b.AssertFileContent("public/index.html", `
   618  P1|p1:|p2:|
   619  S1|p1:|p2:p2|
   620  `)
   621  	})
   622  
   623  	c.Run("slice with yaml _target", func(c *qt.C) {
   624  		b := newBuilder(c)
   625  
   626  		b.WithContent("_index.md", `---
   627  title: "Home"
   628  cascade:
   629  - p1: p1
   630    _target:
   631      path: "**p1**"
   632  - p2: p2
   633    _target:
   634      kind: "section"
   635  ---
   636  `)
   637  
   638  		b.Build(BuildCfg{})
   639  
   640  		b.AssertFileContent("public/index.html", `
   641  P1|p1:p1|p2:|
   642  S1|p1:|p2:p2|
   643  `)
   644  	})
   645  
   646  	c.Run("slice with json _target", func(c *qt.C) {
   647  		b := newBuilder(c)
   648  
   649  		b.WithContent("_index.md", `{
   650  "title": "Home",
   651  "cascade": [
   652    {
   653      "p1": "p1",
   654  	"_target": {
   655  	  "path": "**p1**"
   656      }
   657    },{
   658      "p2": "p2",
   659  	"_target": {
   660        "kind": "section"
   661      }
   662    }
   663  ]
   664  }
   665  `)
   666  
   667  		b.Build(BuildCfg{})
   668  
   669  		b.AssertFileContent("public/index.html", `
   670  		P1|p1:p1|p2:|
   671  		S1|p1:|p2:p2|
   672  		`)
   673  	})
   674  }
   675  
   676  // Issue 11977.
   677  func TestCascadeExtensionInPath(t *testing.T) {
   678  	t.Parallel()
   679  
   680  	files := `
   681  -- hugo.toml --
   682  baseURL = "https://example.org"
   683  [languages]
   684  [languages.en]
   685  weight = 1
   686  [languages.de]
   687  -- content/_index.de.md --
   688  +++
   689  [[cascade]]
   690  [cascade.params]
   691  foo = 'bar'
   692  [cascade._target]
   693  path = '/posts/post-1.de.md'
   694  +++
   695  -- content/posts/post-1.de.md --
   696  ---
   697  title: "Post 1"
   698  ---
   699  -- layouts/_default/single.html --
   700  {{ .Title }}|{{ .Params.foo }}$
   701  `
   702  	b, err := TestE(t, files)
   703  	b.Assert(err, qt.IsNotNil)
   704  	b.AssertLogContains(`cascade target path "/posts/post-1.de.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see  https://gohugo.io/methods/page/path/`)
   705  }
   706  
   707  func TestCascadeExtensionInPathIgnore(t *testing.T) {
   708  	t.Parallel()
   709  
   710  	files := `
   711  -- hugo.toml --
   712  baseURL = "https://example.org"
   713  ignoreLogs   = ['cascade-pattern-with-extension']
   714  [languages]
   715  [languages.en]
   716  weight = 1
   717  [languages.de]
   718  -- content/_index.de.md --
   719  +++
   720  [[cascade]]
   721  [cascade.params]
   722  foo = 'bar'
   723  [cascade._target]
   724  path = '/posts/post-1.de.md'
   725  +++
   726  -- content/posts/post-1.de.md --
   727  ---
   728  title: "Post 1"
   729  ---
   730  -- layouts/_default/single.html --
   731  {{ .Title }}|{{ .Params.foo }}$
   732  `
   733  	b := Test(t, files)
   734  	b.AssertLogNotContains(`looks like a path with an extension`)
   735  }
   736  
   737  func TestCascadConfigExtensionInPath(t *testing.T) {
   738  	t.Parallel()
   739  
   740  	files := `
   741  -- hugo.toml --
   742  baseURL = "https://example.org"
   743  [[cascade]]
   744  [cascade.params]
   745  foo = 'bar'
   746  [cascade._target]
   747  path = '/p1.md'
   748  `
   749  	b, err := TestE(t, files)
   750  	b.Assert(err, qt.IsNotNil)
   751  	b.AssertLogContains(`looks like a path with an extension`)
   752  }
   753  
   754  func TestCascadConfigExtensionInPathIgnore(t *testing.T) {
   755  	t.Parallel()
   756  
   757  	files := `
   758  -- hugo.toml --
   759  baseURL = "https://example.org"
   760  ignoreLogs   = ['cascade-pattern-with-extension']
   761  [[cascade]]
   762  [cascade.params]
   763  foo = 'bar'
   764  [cascade._target]
   765  path = '/p1.md'
   766  `
   767  	b := Test(t, files)
   768  	b.AssertLogNotContains(`looks like a path with an extension`)
   769  }
   770  
   771  func TestCascadeIssue12172(t *testing.T) {
   772  	t.Parallel()
   773  
   774  	files := `
   775  -- hugo.toml --
   776  disableKinds = ['rss','sitemap','taxonomy','term']
   777  [[cascade]]
   778  headless = true
   779  [cascade._target]
   780  path = '/s1**'
   781  -- content/s1/p1.md --
   782  ---
   783  title: p1
   784  ---
   785  -- layouts/_default/single.html --
   786  {{ .Title }}|
   787  -- layouts/_default/list.html --
   788  {{ .Title }}|
   789    `
   790  	b := Test(t, files)
   791  
   792  	b.AssertFileExists("public/index.html", true)
   793  	b.AssertFileExists("public/s1/index.html", false)
   794  	b.AssertFileExists("public/s1/p1/index.html", false)
   795  }