config_test.go (17460B)
1 // Copyright 2016-present 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/filepath" 20 "strings" 21 "testing" 22 23 "github.com/gohugoio/hugo/config" 24 25 "github.com/gohugoio/hugo/media" 26 "github.com/google/go-cmp/cmp" 27 28 qt "github.com/frankban/quicktest" 29 "github.com/gohugoio/hugo/common/maps" 30 "github.com/spf13/afero" 31 ) 32 33 func TestLoadConfig(t *testing.T) { 34 35 c := qt.New(t) 36 37 loadConfig := func(c *qt.C, configContent string, fromDir bool) config.Provider { 38 mm := afero.NewMemMapFs() 39 filename := "config.toml" 40 descriptor := ConfigSourceDescriptor{Fs: mm} 41 if fromDir { 42 filename = filepath.Join("config", "_default", filename) 43 descriptor.AbsConfigDir = "config" 44 } 45 writeToFs(t, mm, filename, configContent) 46 cfg, _, err := LoadConfig(descriptor) 47 c.Assert(err, qt.IsNil) 48 return cfg 49 } 50 51 c.Run("Basic", func(c *qt.C) { 52 c.Parallel() 53 // Add a random config variable for testing. 54 // side = page in Norwegian. 55 cfg := loadConfig(c, `PaginatePath = "side"`, false) 56 c.Assert(cfg.GetString("paginatePath"), qt.Equals, "side") 57 }) 58 59 // Issue #8763 60 for _, fromDir := range []bool{false, true} { 61 testName := "Taxonomy overrides" 62 if fromDir { 63 testName += " from dir" 64 } 65 c.Run(testName, func(c *qt.C) { 66 c.Parallel() 67 cfg := loadConfig(c, `[taxonomies] 68 appellation = "appellations" 69 vigneron = "vignerons"`, fromDir) 70 71 c.Assert(cfg.Get("taxonomies"), qt.DeepEquals, maps.Params{ 72 "appellation": "appellations", 73 "vigneron": "vignerons", 74 }) 75 }) 76 } 77 } 78 79 func TestLoadMultiConfig(t *testing.T) { 80 t.Parallel() 81 82 c := qt.New(t) 83 84 // Add a random config variable for testing. 85 // side = page in Norwegian. 86 configContentBase := ` 87 DontChange = "same" 88 PaginatePath = "side" 89 ` 90 configContentSub := ` 91 PaginatePath = "top" 92 ` 93 mm := afero.NewMemMapFs() 94 95 writeToFs(t, mm, "base.toml", configContentBase) 96 97 writeToFs(t, mm, "override.toml", configContentSub) 98 99 cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Filename: "base.toml,override.toml"}) 100 c.Assert(err, qt.IsNil) 101 102 c.Assert(cfg.GetString("paginatePath"), qt.Equals, "top") 103 c.Assert(cfg.GetString("DontChange"), qt.Equals, "same") 104 } 105 106 func TestLoadConfigFromThemes(t *testing.T) { 107 t.Parallel() 108 109 c := qt.New(t) 110 111 mainConfigTemplate := ` 112 theme = "test-theme" 113 baseURL = "https://example.com/" 114 115 [frontmatter] 116 date = ["date","publishDate"] 117 118 [params] 119 MERGE_PARAMS 120 p1 = "p1 main" 121 [params.b] 122 b1 = "b1 main" 123 [params.b.c] 124 bc1 = "bc1 main" 125 126 [mediaTypes] 127 [mediaTypes."text/m1"] 128 suffixes = ["m1main"] 129 130 [outputFormats.o1] 131 mediaType = "text/m1" 132 baseName = "o1main" 133 134 [languages] 135 [languages.en] 136 languageName = "English" 137 [languages.en.params] 138 pl1 = "p1-en-main" 139 [languages.nb] 140 languageName = "Norsk" 141 [languages.nb.params] 142 pl1 = "p1-nb-main" 143 144 [[menu.main]] 145 name = "menu-main-main" 146 147 [[menu.top]] 148 name = "menu-top-main" 149 150 ` 151 152 themeConfig := ` 153 baseURL = "http://bep.is/" 154 155 # Can not be set in theme. 156 disableKinds = ["taxonomy", "term"] 157 158 # Can not be set in theme. 159 [frontmatter] 160 expiryDate = ["date"] 161 162 [params] 163 p1 = "p1 theme" 164 p2 = "p2 theme" 165 [params.b] 166 b1 = "b1 theme" 167 b2 = "b2 theme" 168 [params.b.c] 169 bc1 = "bc1 theme" 170 bc2 = "bc2 theme" 171 [params.b.c.d] 172 bcd1 = "bcd1 theme" 173 174 [mediaTypes] 175 [mediaTypes."text/m1"] 176 suffixes = ["m1theme"] 177 [mediaTypes."text/m2"] 178 suffixes = ["m2theme"] 179 180 [outputFormats.o1] 181 mediaType = "text/m1" 182 baseName = "o1theme" 183 [outputFormats.o2] 184 mediaType = "text/m2" 185 baseName = "o2theme" 186 187 [languages] 188 [languages.en] 189 languageName = "English2" 190 [languages.en.params] 191 pl1 = "p1-en-theme" 192 pl2 = "p2-en-theme" 193 [[languages.en.menu.main]] 194 name = "menu-lang-en-main" 195 [[languages.en.menu.theme]] 196 name = "menu-lang-en-theme" 197 [languages.nb] 198 languageName = "Norsk2" 199 [languages.nb.params] 200 pl1 = "p1-nb-theme" 201 pl2 = "p2-nb-theme" 202 top = "top-nb-theme" 203 [[languages.nb.menu.main]] 204 name = "menu-lang-nb-main" 205 [[languages.nb.menu.theme]] 206 name = "menu-lang-nb-theme" 207 [[languages.nb.menu.top]] 208 name = "menu-lang-nb-top" 209 210 [[menu.main]] 211 name = "menu-main-theme" 212 213 [[menu.thememenu]] 214 name = "menu-theme" 215 216 ` 217 218 buildForConfig := func(t testing.TB, mainConfig, themeConfig string) *sitesBuilder { 219 b := newTestSitesBuilder(t) 220 b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig) 221 return b.Build(BuildCfg{}) 222 } 223 224 buildForStrategy := func(t testing.TB, s string) *sitesBuilder { 225 mainConfig := strings.ReplaceAll(mainConfigTemplate, "MERGE_PARAMS", s) 226 return buildForConfig(t, mainConfig, themeConfig) 227 } 228 229 c.Run("Merge default", func(c *qt.C) { 230 b := buildForStrategy(c, "") 231 232 got := b.Cfg.Get("").(maps.Params) 233 234 // Issue #8866 235 b.Assert(b.Cfg.Get("disableKinds"), qt.IsNil) 236 237 b.Assert(got["params"], qt.DeepEquals, maps.Params{ 238 "b": maps.Params{ 239 "b1": "b1 main", 240 "c": maps.Params{ 241 "bc1": "bc1 main", 242 "bc2": "bc2 theme", 243 "d": maps.Params{"bcd1": string("bcd1 theme")}, 244 }, 245 "b2": "b2 theme", 246 }, 247 "p2": "p2 theme", 248 "p1": "p1 main", 249 }) 250 251 b.Assert(got["mediatypes"], qt.DeepEquals, maps.Params{ 252 "text/m2": maps.Params{ 253 "suffixes": []any{ 254 "m2theme", 255 }, 256 }, 257 "text/m1": maps.Params{ 258 "suffixes": []any{ 259 "m1main", 260 }, 261 }, 262 }) 263 264 var eq = qt.CmpEquals( 265 cmp.Comparer(func(m1, m2 media.Type) bool { 266 if m1.SubType != m2.SubType { 267 return false 268 } 269 return m1.FirstSuffix == m2.FirstSuffix 270 }), 271 ) 272 273 mediaTypes := b.H.Sites[0].mediaTypesConfig 274 m1, _ := mediaTypes.GetByType("text/m1") 275 m2, _ := mediaTypes.GetByType("text/m2") 276 277 b.Assert(got["outputformats"], eq, maps.Params{ 278 "o1": maps.Params{ 279 "mediatype": m1, 280 "basename": "o1main", 281 }, 282 "o2": maps.Params{ 283 "basename": "o2theme", 284 "mediatype": m2, 285 }, 286 }) 287 288 b.Assert(got["languages"], qt.DeepEquals, maps.Params{ 289 "en": maps.Params{ 290 "languagename": "English", 291 "params": maps.Params{ 292 "pl2": "p2-en-theme", 293 "pl1": "p1-en-main", 294 }, 295 "menus": maps.Params{ 296 "main": []any{ 297 map[string]any{ 298 "name": "menu-lang-en-main", 299 }, 300 }, 301 "theme": []any{ 302 map[string]any{ 303 "name": "menu-lang-en-theme", 304 }, 305 }, 306 }, 307 }, 308 "nb": maps.Params{ 309 "languagename": "Norsk", 310 "params": maps.Params{ 311 "top": "top-nb-theme", 312 "pl1": "p1-nb-main", 313 "pl2": "p2-nb-theme", 314 }, 315 "menus": maps.Params{ 316 "main": []any{ 317 map[string]any{ 318 "name": "menu-lang-nb-main", 319 }, 320 }, 321 "theme": []any{ 322 map[string]any{ 323 "name": "menu-lang-nb-theme", 324 }, 325 }, 326 "top": []any{ 327 map[string]any{ 328 "name": "menu-lang-nb-top", 329 }, 330 }, 331 }, 332 }, 333 }) 334 335 c.Assert(got["baseurl"], qt.Equals, "https://example.com/") 336 }) 337 338 c.Run("Merge shallow", func(c *qt.C) { 339 b := buildForStrategy(c, fmt.Sprintf("_merge=%q", "shallow")) 340 341 got := b.Cfg.Get("").(maps.Params) 342 343 // Shallow merge, only add new keys to params. 344 b.Assert(got["params"], qt.DeepEquals, maps.Params{ 345 "p1": "p1 main", 346 "b": maps.Params{ 347 "b1": "b1 main", 348 "c": maps.Params{ 349 "bc1": "bc1 main", 350 }, 351 }, 352 "p2": "p2 theme", 353 }) 354 }) 355 356 c.Run("Merge no params in project", func(c *qt.C) { 357 b := buildForConfig( 358 c, 359 "baseURL=\"https://example.org\"\ntheme = \"test-theme\"\n", 360 "[params]\np1 = \"p1 theme\"\n", 361 ) 362 363 got := b.Cfg.Get("").(maps.Params) 364 365 b.Assert(got["params"], qt.DeepEquals, maps.Params{ 366 "p1": "p1 theme", 367 }) 368 }) 369 370 c.Run("Merge language no menus or params in project", func(c *qt.C) { 371 b := buildForConfig( 372 c, 373 ` 374 theme = "test-theme" 375 baseURL = "https://example.com/" 376 377 [languages] 378 [languages.en] 379 languageName = "English" 380 381 `, 382 ` 383 [languages] 384 [languages.en] 385 languageName = "EnglishTheme" 386 387 [languages.en.params] 388 p1="themep1" 389 390 [[languages.en.menus.main]] 391 name = "menu-theme" 392 `, 393 ) 394 395 got := b.Cfg.Get("").(maps.Params) 396 397 b.Assert(got["languages"], qt.DeepEquals, 398 maps.Params{ 399 "en": maps.Params{ 400 "languagename": "English", 401 "menus": maps.Params{ 402 "main": []any{ 403 map[string]any{ 404 "name": "menu-theme", 405 }, 406 }, 407 }, 408 "params": maps.Params{ 409 "p1": "themep1", 410 }, 411 }, 412 }, 413 ) 414 }) 415 416 // Issue #8724 417 for _, mergeStrategy := range []string{"none", "shallow"} { 418 c.Run(fmt.Sprintf("Merge with sitemap config in theme, mergestrategy %s", mergeStrategy), func(c *qt.C) { 419 420 smapConfigTempl := `[sitemap] 421 changefreq = %q 422 filename = "sitemap.xml" 423 priority = 0.5` 424 425 b := buildForConfig( 426 c, 427 fmt.Sprintf("_merge=%q\nbaseURL=\"https://example.org\"\ntheme = \"test-theme\"\n", mergeStrategy), 428 "baseURL=\"http://example.com\"\n"+fmt.Sprintf(smapConfigTempl, "monthly"), 429 ) 430 431 got := b.Cfg.Get("").(maps.Params) 432 433 if mergeStrategy == "none" { 434 b.Assert(got["sitemap"], qt.DeepEquals, maps.Params{ 435 "priority": int(-1), 436 "filename": "sitemap.xml", 437 }) 438 439 b.AssertFileContent("public/sitemap.xml", "schemas/sitemap") 440 } else { 441 b.Assert(got["sitemap"], qt.DeepEquals, maps.Params{ 442 "priority": int(-1), 443 "filename": "sitemap.xml", 444 "changefreq": "monthly", 445 }) 446 447 b.AssertFileContent("public/sitemap.xml", "<changefreq>monthly</changefreq>") 448 } 449 450 }) 451 } 452 453 } 454 455 func TestLoadConfigFromThemeDir(t *testing.T) { 456 t.Parallel() 457 458 mainConfig := ` 459 theme = "test-theme" 460 461 [params] 462 m1 = "mv1" 463 ` 464 465 themeConfig := ` 466 [params] 467 t1 = "tv1" 468 t2 = "tv2" 469 ` 470 471 themeConfigDir := filepath.Join("themes", "test-theme", "config") 472 themeConfigDirDefault := filepath.Join(themeConfigDir, "_default") 473 themeConfigDirProduction := filepath.Join(themeConfigDir, "production") 474 475 projectConfigDir := "config" 476 477 b := newTestSitesBuilder(t) 478 b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig) 479 b.Assert(b.Fs.Source.MkdirAll(themeConfigDirDefault, 0777), qt.IsNil) 480 b.Assert(b.Fs.Source.MkdirAll(themeConfigDirProduction, 0777), qt.IsNil) 481 b.Assert(b.Fs.Source.MkdirAll(projectConfigDir, 0777), qt.IsNil) 482 483 b.WithSourceFile(filepath.Join(projectConfigDir, "config.toml"), `[params] 484 m2 = "mv2" 485 `) 486 b.WithSourceFile(filepath.Join(themeConfigDirDefault, "config.toml"), `[params] 487 t2 = "tv2d" 488 t3 = "tv3d" 489 `) 490 491 b.WithSourceFile(filepath.Join(themeConfigDirProduction, "config.toml"), `[params] 492 t3 = "tv3p" 493 `) 494 495 b.Build(BuildCfg{}) 496 497 got := b.Cfg.Get("params").(maps.Params) 498 499 b.Assert(got, qt.DeepEquals, maps.Params{ 500 "t3": "tv3p", 501 "m1": "mv1", 502 "t1": "tv1", 503 "t2": "tv2d", 504 }) 505 506 } 507 508 func TestPrivacyConfig(t *testing.T) { 509 t.Parallel() 510 511 c := qt.New(t) 512 513 tomlConfig := ` 514 515 someOtherValue = "foo" 516 517 [privacy] 518 [privacy.youtube] 519 privacyEnhanced = true 520 ` 521 522 b := newTestSitesBuilder(t) 523 b.WithConfigFile("toml", tomlConfig) 524 b.Build(BuildCfg{SkipRender: true}) 525 526 c.Assert(b.H.Sites[0].Info.Config().Privacy.YouTube.PrivacyEnhanced, qt.Equals, true) 527 } 528 529 func TestLoadConfigModules(t *testing.T) { 530 t.Parallel() 531 532 c := qt.New(t) 533 534 // https://github.com/gohugoio/hugoThemes#themetoml 535 536 const ( 537 // Before Hugo 0.56 each theme/component could have its own theme.toml 538 // with some settings, mostly used on the Hugo themes site. 539 // To preserve combability we read these files into the new "modules" 540 // section in config.toml. 541 o1t = ` 542 name = "Component o1" 543 license = "MIT" 544 min_version = 0.38 545 ` 546 // This is the component's config.toml, using the old theme syntax. 547 o1c = ` 548 theme = ["n2"] 549 ` 550 551 n1 = ` 552 title = "Component n1" 553 554 [module] 555 description = "Component n1 description" 556 [module.hugoVersion] 557 min = "0.40.0" 558 max = "0.50.0" 559 extended = true 560 [[module.imports]] 561 path="o1" 562 [[module.imports]] 563 path="n3" 564 565 566 ` 567 568 n2 = ` 569 title = "Component n2" 570 ` 571 572 n3 = ` 573 title = "Component n3" 574 ` 575 576 n4 = ` 577 title = "Component n4" 578 ` 579 ) 580 581 b := newTestSitesBuilder(t) 582 583 writeThemeFiles := func(name, configTOML, themeTOML string) { 584 b.WithSourceFile(filepath.Join("themes", name, "data", "module.toml"), fmt.Sprintf("name=%q", name)) 585 if configTOML != "" { 586 b.WithSourceFile(filepath.Join("themes", name, "config.toml"), configTOML) 587 } 588 if themeTOML != "" { 589 b.WithSourceFile(filepath.Join("themes", name, "theme.toml"), themeTOML) 590 } 591 } 592 593 writeThemeFiles("n1", n1, "") 594 writeThemeFiles("n2", n2, "") 595 writeThemeFiles("n3", n3, "") 596 writeThemeFiles("n4", n4, "") 597 writeThemeFiles("o1", o1c, o1t) 598 599 b.WithConfigFile("toml", ` 600 [module] 601 [[module.imports]] 602 path="n1" 603 [[module.imports]] 604 path="n4" 605 606 `) 607 608 b.Build(BuildCfg{}) 609 610 modulesClient := b.H.Paths.ModulesClient 611 var graphb bytes.Buffer 612 modulesClient.Graph(&graphb) 613 614 expected := `project n1 615 n1 o1 616 o1 n2 617 n1 n3 618 project n4 619 ` 620 621 c.Assert(graphb.String(), qt.Equals, expected) 622 } 623 624 func TestLoadConfigWithOsEnvOverrides(t *testing.T) { 625 c := qt.New(t) 626 627 baseConfig := ` 628 629 theme = "mytheme" 630 environment = "production" 631 enableGitInfo = true 632 intSlice = [5,7,9] 633 floatSlice = [3.14, 5.19] 634 stringSlice = ["a", "b"] 635 636 [outputFormats] 637 [outputFormats.ofbase] 638 mediaType = "text/plain" 639 640 [params] 641 paramWithNoEnvOverride="nooverride" 642 [params.api_config] 643 api_key="default_key" 644 another_key="default another_key" 645 646 [imaging] 647 anchor = "smart" 648 quality = 75 649 ` 650 651 newB := func(t testing.TB) *sitesBuilder { 652 b := newTestSitesBuilder(t).WithConfigFile("toml", baseConfig) 653 654 b.WithSourceFile("themes/mytheme/config.toml", ` 655 656 [outputFormats] 657 [outputFormats.oftheme] 658 mediaType = "text/plain" 659 [outputFormats.ofbase] 660 mediaType = "application/xml" 661 662 [params] 663 [params.mytheme_section] 664 theme_param="themevalue" 665 theme_param_nooverride="nooverride" 666 [params.mytheme_section2] 667 theme_param="themevalue2" 668 669 `) 670 671 return b 672 } 673 674 c.Run("Variations", func(c *qt.C) { 675 676 b := newB(c) 677 678 b.WithEnviron( 679 "HUGO_ENVIRONMENT", "test", 680 "HUGO_NEW", "new", // key not in config.toml 681 "HUGO_ENABLEGITINFO", "false", 682 "HUGO_IMAGING_ANCHOR", "top", 683 "HUGO_IMAGING_RESAMPLEFILTER", "CatmullRom", 684 "HUGO_STRINGSLICE", `["c", "d"]`, 685 "HUGO_INTSLICE", `[5, 8, 9]`, 686 "HUGO_FLOATSLICE", `[5.32]`, 687 // Issue #7829 688 "HUGOxPARAMSxAPI_CONFIGxAPI_KEY", "new_key", 689 // Delimiters are case sensitive. 690 "HUGOxPARAMSxAPI_CONFIGXANOTHER_KEY", "another_key", 691 // Issue #8346 692 "HUGOxPARAMSxMYTHEME_SECTIONxTHEME_PARAM", "themevalue_changed", 693 "HUGOxPARAMSxMYTHEME_SECTION2xTHEME_PARAM", "themevalue2_changed", 694 "HUGO_PARAMS_EMPTY", ``, 695 "HUGO_PARAMS_HTML", `<a target="_blank" />`, 696 // Issue #8618 697 "HUGO_SERVICES_GOOGLEANALYTICS_ID", `gaid`, 698 "HUGO_PARAMS_A_B_C", "abc", 699 ) 700 701 b.Build(BuildCfg{}) 702 703 cfg := b.H.Cfg 704 s := b.H.Sites[0] 705 scfg := s.siteConfigConfig.Services 706 707 c.Assert(cfg.Get("environment"), qt.Equals, "test") 708 c.Assert(cfg.GetBool("enablegitinfo"), qt.Equals, false) 709 c.Assert(cfg.Get("new"), qt.Equals, "new") 710 c.Assert(cfg.Get("imaging.anchor"), qt.Equals, "top") 711 c.Assert(cfg.Get("imaging.quality"), qt.Equals, int64(75)) 712 c.Assert(cfg.Get("imaging.resamplefilter"), qt.Equals, "CatmullRom") 713 c.Assert(cfg.Get("stringSlice"), qt.DeepEquals, []any{"c", "d"}) 714 c.Assert(cfg.Get("floatSlice"), qt.DeepEquals, []any{5.32}) 715 c.Assert(cfg.Get("intSlice"), qt.DeepEquals, []any{5, 8, 9}) 716 c.Assert(cfg.Get("params.api_config.api_key"), qt.Equals, "new_key") 717 c.Assert(cfg.Get("params.api_config.another_key"), qt.Equals, "default another_key") 718 c.Assert(cfg.Get("params.mytheme_section.theme_param"), qt.Equals, "themevalue_changed") 719 c.Assert(cfg.Get("params.mytheme_section.theme_param_nooverride"), qt.Equals, "nooverride") 720 c.Assert(cfg.Get("params.mytheme_section2.theme_param"), qt.Equals, "themevalue2_changed") 721 c.Assert(cfg.Get("params.empty"), qt.Equals, ``) 722 c.Assert(cfg.Get("params.html"), qt.Equals, `<a target="_blank" />`) 723 724 params := cfg.Get("params").(maps.Params) 725 c.Assert(params["paramwithnoenvoverride"], qt.Equals, "nooverride") 726 c.Assert(cfg.Get("params.paramwithnoenvoverride"), qt.Equals, "nooverride") 727 c.Assert(scfg.GoogleAnalytics.ID, qt.Equals, "gaid") 728 c.Assert(cfg.Get("params.a.b"), qt.DeepEquals, maps.Params{ 729 "c": "abc", 730 }) 731 732 ofBase, _ := s.outputFormatsConfig.GetByName("ofbase") 733 ofTheme, _ := s.outputFormatsConfig.GetByName("oftheme") 734 735 c.Assert(ofBase.MediaType, qt.Equals, media.TextType) 736 c.Assert(ofTheme.MediaType, qt.Equals, media.TextType) 737 738 }) 739 740 // Issue #8709 741 c.Run("Set in string", func(c *qt.C) { 742 b := newB(c) 743 744 b.WithEnviron( 745 "HUGO_ENABLEGITINFO", "false", 746 // imaging.anchor is a string, and it's not possible 747 // to set a child attribute. 748 "HUGO_IMAGING_ANCHOR_FOO", "top", 749 ) 750 751 b.Build(BuildCfg{}) 752 753 cfg := b.H.Cfg 754 c.Assert(cfg.Get("imaging.anchor"), qt.Equals, "smart") 755 756 }) 757 758 } 759 760 func TestInvalidDefaultMarkdownHandler(t *testing.T) { 761 t.Parallel() 762 763 files := ` 764 -- config.toml -- 765 [markup] 766 defaultMarkdownHandler = 'blackfriday' 767 -- content/_index.md -- 768 ## Foo 769 -- layouts/index.html -- 770 {{ .Content }} 771 772 ` 773 774 b, err := NewIntegrationTestBuilder( 775 IntegrationTestConfig{ 776 T: t, 777 TxtarString: files, 778 }, 779 ).BuildE() 780 781 b.Assert(err, qt.IsNotNil) 782 b.Assert(err.Error(), qt.Contains, "Configured defaultMarkdownHandler \"blackfriday\" not found. Did you mean to use goldmark? Blackfriday was removed in Hugo v0.100.0.") 783 784 }