From: Bjørn Erik Pedersen Date: Sun, 28 Jan 2024 10:41:59 +0000 (+0100) Subject: testing: Rename integration_test.go to PACKAGE_integration_test.go X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=2a0329423ce4b3359842affad76c40140d426943;p=brevno-suite%2Fhugo testing: Rename integration_test.go to PACKAGE_integration_test.go Primary motivation making them easier to find in the code editor. --- diff --git a/cache/filecache/filecache_integration_test.go b/cache/filecache/filecache_integration_test.go new file mode 100644 index 000000000..1e920c29f --- /dev/null +++ b/cache/filecache/filecache_integration_test.go @@ -0,0 +1,106 @@ +// Copyright 2024 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 filecache_test + +import ( + "path/filepath" + "testing" + "time" + + "github.com/bep/logg" + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/htesting" + "github.com/gohugoio/hugo/hugolib" +) + +// See issue #10781. That issue wouldn't have been triggered if we kept +// the empty root directories (e.g. _resources/gen/images). +// It's still an upstream Go issue that we also need to handle, but +// this is a test for the first part. +func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +-- content/_index.md -- +--- +title: "Home" +--- + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true}, + ).Build() + + _, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images")) + + b.Assert(err, qt.IsNil) +} + +func TestPruneImages(t *testing.T) { + if htesting.IsCI() { + // TODO(bep) + t.Skip("skip flaky test on CI server") + } + t.Skip("skip flaky test") + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +[caches] +[caches.images] +maxAge = "200ms" +dir = ":resourceDir/_gen" +-- content/_index.md -- +--- +title: "Home" +--- +-- assets/a/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ warnf "HOME!" }} +{{ $img := resources.GetMatch "**.png" }} +{{ $img = $img.Resize "3x3" }} +{{ $img.RelPermalink }} + + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: logg.LevelInfo}, + ).Build() + + b.Assert(b.GCCount, qt.Equals, 0) + b.Assert(b.H, qt.IsNotNil) + + imagesCacheDir := filepath.Join("_gen", "images") + _, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + + b.Assert(err, qt.IsNil) + + // TODO(bep) we need a way to test full rebuilds. + // For now, just sleep a little so the cache elements expires. + time.Sleep(500 * time.Millisecond) + + b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build() + + b.Assert(b.GCCount, qt.Equals, 1) + // Build it again to GC the empty a dir. + b.Build() + + _, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a")) + b.Assert(err, qt.Not(qt.IsNil)) + _, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + b.Assert(err, qt.IsNil) +} diff --git a/cache/filecache/integration_test.go b/cache/filecache/integration_test.go deleted file mode 100644 index 1e920c29f..000000000 --- a/cache/filecache/integration_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2024 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 filecache_test - -import ( - "path/filepath" - "testing" - "time" - - "github.com/bep/logg" - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/htesting" - "github.com/gohugoio/hugo/hugolib" -) - -// See issue #10781. That issue wouldn't have been triggered if we kept -// the empty root directories (e.g. _resources/gen/images). -// It's still an upstream Go issue that we also need to handle, but -// this is a test for the first part. -func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) { - files := ` --- hugo.toml -- -baseURL = "https://example.com" --- content/_index.md -- ---- -title: "Home" ---- - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true}, - ).Build() - - _, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images")) - - b.Assert(err, qt.IsNil) -} - -func TestPruneImages(t *testing.T) { - if htesting.IsCI() { - // TODO(bep) - t.Skip("skip flaky test on CI server") - } - t.Skip("skip flaky test") - files := ` --- hugo.toml -- -baseURL = "https://example.com" -[caches] -[caches.images] -maxAge = "200ms" -dir = ":resourceDir/_gen" --- content/_index.md -- ---- -title: "Home" ---- --- assets/a/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{ warnf "HOME!" }} -{{ $img := resources.GetMatch "**.png" }} -{{ $img = $img.Resize "3x3" }} -{{ $img.RelPermalink }} - - - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: logg.LevelInfo}, - ).Build() - - b.Assert(b.GCCount, qt.Equals, 0) - b.Assert(b.H, qt.IsNotNil) - - imagesCacheDir := filepath.Join("_gen", "images") - _, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) - - b.Assert(err, qt.IsNil) - - // TODO(bep) we need a way to test full rebuilds. - // For now, just sleep a little so the cache elements expires. - time.Sleep(500 * time.Millisecond) - - b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build() - - b.Assert(b.GCCount, qt.Equals, 1) - // Build it again to GC the empty a dir. - b.Build() - - _, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a")) - b.Assert(err, qt.Not(qt.IsNil)) - _, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) - b.Assert(err, qt.IsNil) -} diff --git a/common/htime/htime_integration_test.go b/common/htime/htime_integration_test.go new file mode 100644 index 000000000..983fff1f7 --- /dev/null +++ b/common/htime/htime_integration_test.go @@ -0,0 +1,83 @@ +// Copyright 2024 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 htime_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue #11267 +func TestApplyWithContext(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +defaultContentLanguage = 'it' +-- layouts/index.html -- +{{ $dates := slice + "2022-01-03" + "2022-02-01" + "2022-03-02" + "2022-04-07" + "2022-05-06" + "2022-06-04" + "2022-07-03" + "2022-08-01" + "2022-09-06" + "2022-10-05" + "2022-11-03" + "2022-12-02" +}} +{{ range $dates }} + {{ . | time.Format "month: _January_ weekday: _Monday_" }} + {{ . | time.Format "month: _Jan_ weekday: _Mon_" }} +{{ end }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +month: _gennaio_ weekday: _lunedì_ +month: _gen_ weekday: _lun_ +month: _febbraio_ weekday: _martedì_ +month: _feb_ weekday: _mar_ +month: _marzo_ weekday: _mercoledì_ +month: _mar_ weekday: _mer_ +month: _aprile_ weekday: _giovedì_ +month: _apr_ weekday: _gio_ +month: _maggio_ weekday: _venerdì_ +month: _mag_ weekday: _ven_ +month: _giugno_ weekday: _sabato_ +month: _giu_ weekday: _sab_ +month: _luglio_ weekday: _domenica_ +month: _lug_ weekday: _dom_ +month: _agosto_ weekday: _lunedì_ +month: _ago_ weekday: _lun_ +month: _settembre_ weekday: _martedì_ +month: _set_ weekday: _mar_ +month: _ottobre_ weekday: _mercoledì_ +month: _ott_ weekday: _mer_ +month: _novembre_ weekday: _giovedì_ +month: _nov_ weekday: _gio_ +month: _dicembre_ weekday: _venerdì_ +month: _dic_ weekday: _ven_ +`) +} diff --git a/common/htime/integration_test.go b/common/htime/integration_test.go deleted file mode 100644 index 983fff1f7..000000000 --- a/common/htime/integration_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2024 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 htime_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue #11267 -func TestApplyWithContext(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -defaultContentLanguage = 'it' --- layouts/index.html -- -{{ $dates := slice - "2022-01-03" - "2022-02-01" - "2022-03-02" - "2022-04-07" - "2022-05-06" - "2022-06-04" - "2022-07-03" - "2022-08-01" - "2022-09-06" - "2022-10-05" - "2022-11-03" - "2022-12-02" -}} -{{ range $dates }} - {{ . | time.Format "month: _January_ weekday: _Monday_" }} - {{ . | time.Format "month: _Jan_ weekday: _Mon_" }} -{{ end }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -month: _gennaio_ weekday: _lunedì_ -month: _gen_ weekday: _lun_ -month: _febbraio_ weekday: _martedì_ -month: _feb_ weekday: _mar_ -month: _marzo_ weekday: _mercoledì_ -month: _mar_ weekday: _mer_ -month: _aprile_ weekday: _giovedì_ -month: _apr_ weekday: _gio_ -month: _maggio_ weekday: _venerdì_ -month: _mag_ weekday: _ven_ -month: _giugno_ weekday: _sabato_ -month: _giu_ weekday: _sab_ -month: _luglio_ weekday: _domenica_ -month: _lug_ weekday: _dom_ -month: _agosto_ weekday: _lunedì_ -month: _ago_ weekday: _lun_ -month: _settembre_ weekday: _martedì_ -month: _set_ weekday: _mar_ -month: _ottobre_ weekday: _mercoledì_ -month: _ott_ weekday: _mer_ -month: _novembre_ weekday: _giovedì_ -month: _nov_ weekday: _gio_ -month: _dicembre_ weekday: _venerdì_ -month: _dic_ weekday: _ven_ -`) -} diff --git a/config/allconfig/allconfig_integration_test.go b/config/allconfig/allconfig_integration_test.go new file mode 100644 index 000000000..4f2f1a06e --- /dev/null +++ b/config/allconfig/allconfig_integration_test.go @@ -0,0 +1,86 @@ +package allconfig_test + +import ( + "path/filepath" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/config/allconfig" + "github.com/gohugoio/hugo/hugolib" +) + +func TestDirsMount(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term"] +[languages] +[languages.en] +weight = 1 +[languages.sv] +weight = 2 +[[module.mounts]] +source = 'content/en' +target = 'content' +lang = 'en' +[[module.mounts]] +source = 'content/sv' +target = 'content' +lang = 'sv' +-- content/en/p1.md -- +--- +title: "p1" +--- +-- content/sv/p1.md -- +--- +title: "p1" +--- +-- layouts/_default/single.html -- +Title: {{ .Title }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files}, + ).Build() + + // b.AssertFileContent("public/p1/index.html", "Title: p1") + + sites := b.H.Sites + b.Assert(len(sites), qt.Equals, 2) + + configs := b.H.Configs + mods := configs.Modules + b.Assert(len(mods), qt.Equals, 1) + mod := mods[0] + b.Assert(mod.Mounts(), qt.HasLen, 8) + + enConcp := sites[0].Conf + enConf := enConcp.GetConfig().(*allconfig.Config) + + b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com/") + modConf := enConf.Module + b.Assert(modConf.Mounts, qt.HasLen, 8) + b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en")) + b.Assert(modConf.Mounts[0].Target, qt.Equals, "content") + b.Assert(modConf.Mounts[0].Lang, qt.Equals, "en") + b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv")) + b.Assert(modConf.Mounts[1].Target, qt.Equals, "content") + b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv") +} + +func TestConfigAliases(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +logI18nWarnings = true +logPathWarnings = true +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files}, + ).Build() + + conf := b.H.Configs.Base + + b.Assert(conf.PrintI18nWarnings, qt.Equals, true) + b.Assert(conf.PrintPathWarnings, qt.Equals, true) +} diff --git a/config/allconfig/integration_test.go b/config/allconfig/integration_test.go deleted file mode 100644 index 4f2f1a06e..000000000 --- a/config/allconfig/integration_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package allconfig_test - -import ( - "path/filepath" - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/config/allconfig" - "github.com/gohugoio/hugo/hugolib" -) - -func TestDirsMount(t *testing.T) { - files := ` --- hugo.toml -- -baseURL = "https://example.com" -disableKinds = ["taxonomy", "term"] -[languages] -[languages.en] -weight = 1 -[languages.sv] -weight = 2 -[[module.mounts]] -source = 'content/en' -target = 'content' -lang = 'en' -[[module.mounts]] -source = 'content/sv' -target = 'content' -lang = 'sv' --- content/en/p1.md -- ---- -title: "p1" ---- --- content/sv/p1.md -- ---- -title: "p1" ---- --- layouts/_default/single.html -- -Title: {{ .Title }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{T: t, TxtarString: files}, - ).Build() - - // b.AssertFileContent("public/p1/index.html", "Title: p1") - - sites := b.H.Sites - b.Assert(len(sites), qt.Equals, 2) - - configs := b.H.Configs - mods := configs.Modules - b.Assert(len(mods), qt.Equals, 1) - mod := mods[0] - b.Assert(mod.Mounts(), qt.HasLen, 8) - - enConcp := sites[0].Conf - enConf := enConcp.GetConfig().(*allconfig.Config) - - b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com/") - modConf := enConf.Module - b.Assert(modConf.Mounts, qt.HasLen, 8) - b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en")) - b.Assert(modConf.Mounts[0].Target, qt.Equals, "content") - b.Assert(modConf.Mounts[0].Lang, qt.Equals, "en") - b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv")) - b.Assert(modConf.Mounts[1].Target, qt.Equals, "content") - b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv") -} - -func TestConfigAliases(t *testing.T) { - files := ` --- hugo.toml -- -baseURL = "https://example.com" -logI18nWarnings = true -logPathWarnings = true -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{T: t, TxtarString: files}, - ).Build() - - conf := b.H.Configs.Base - - b.Assert(conf.PrintI18nWarnings, qt.Equals, true) - b.Assert(conf.PrintPathWarnings, qt.Equals, true) -} diff --git a/hugolib/hugolib_integration_test.go b/hugolib/hugolib_integration_test.go new file mode 100644 index 000000000..250c7bcec --- /dev/null +++ b/hugolib/hugolib_integration_test.go @@ -0,0 +1,147 @@ +// Copyright 2024 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_test + +import ( + "strings" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 9073 +func TestPageTranslationsMap(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'https://example.org/' +title = 'Issue-9073' +defaultContentLanguageInSubdir = true + +[taxonomies] +tag = 'tags' + +[languages.en] +contentDir = 'content/en' +weight = 1 +disableKinds = ['RSS','sitemap'] + +[languages.de] +contentDir = 'content/de' +weight = 2 +disableKinds = ['home', 'page', 'section', 'taxonomy', 'term','RSS','sitemap'] +-- content/de/posts/p1.md -- +--- +title: P1 +tags: ['T1'] +--- +-- content/en/posts/p1.md -- +--- +title: P1 +tags: ['T1'] +--- +-- layouts/_default/single.html -- + +-- layouts/_default/list.html -- + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + b.Build() + + // Kind home + b.AssertFileContent("public/en/index.html", + "", + ) + // Kind section + b.AssertFileContent("public/en/posts/index.html", + "", + ) + // Kind page + b.AssertFileContent("public/en/posts/p1/index.html", + "", + ) + // Kind taxonomy + b.AssertFileContent("public/en/tags/index.html", + "", + ) + // Kind term + b.AssertFileContent("public/en/tags/t1/index.html", + "", + ) +} + +// Issue #11538 +func TestRenderStringBadMarkupOpt(t *testing.T) { + t.Parallel() + + files := ` +-- layouts/index.html -- +{{ $opts := dict "markup" "foo" }} +{{ "something" | .RenderString $opts }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + + _, err := b.BuildE() + + want := `no content renderer found for markup "foo"` + if !strings.Contains(err.Error(), want) { + t.Errorf("error msg must contain %q, error msg actually contains %q", want, err.Error()) + } +} + +// Issue #11547 +func TestTitleCaseStyleWithAutomaticSectionPages(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +titleCaseStyle = 'none' +-- content/books/book-1.md -- +--- +title: Book 1 +tags: [fiction] +--- +-- content/films/_index.md -- +--- +title: Films +--- +-- layouts/index.html -- +{{ (site.GetPage "/tags").Title }} +{{ (site.GetPage "/tags/fiction").Title }} +{{ (site.GetPage "/books").Title }} +{{ (site.GetPage "/films").Title }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + b.Build() + b.AssertFileContent("public/index.html", "tags\nfiction\nbooks\nFilms") +} diff --git a/hugolib/integration_test.go b/hugolib/integration_test.go deleted file mode 100644 index 250c7bcec..000000000 --- a/hugolib/integration_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2024 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_test - -import ( - "strings" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 9073 -func TestPageTranslationsMap(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'https://example.org/' -title = 'Issue-9073' -defaultContentLanguageInSubdir = true - -[taxonomies] -tag = 'tags' - -[languages.en] -contentDir = 'content/en' -weight = 1 -disableKinds = ['RSS','sitemap'] - -[languages.de] -contentDir = 'content/de' -weight = 2 -disableKinds = ['home', 'page', 'section', 'taxonomy', 'term','RSS','sitemap'] --- content/de/posts/p1.md -- ---- -title: P1 -tags: ['T1'] ---- --- content/en/posts/p1.md -- ---- -title: P1 -tags: ['T1'] ---- --- layouts/_default/single.html -- - --- layouts/_default/list.html -- - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - b.Build() - - // Kind home - b.AssertFileContent("public/en/index.html", - "", - ) - // Kind section - b.AssertFileContent("public/en/posts/index.html", - "", - ) - // Kind page - b.AssertFileContent("public/en/posts/p1/index.html", - "", - ) - // Kind taxonomy - b.AssertFileContent("public/en/tags/index.html", - "", - ) - // Kind term - b.AssertFileContent("public/en/tags/t1/index.html", - "", - ) -} - -// Issue #11538 -func TestRenderStringBadMarkupOpt(t *testing.T) { - t.Parallel() - - files := ` --- layouts/index.html -- -{{ $opts := dict "markup" "foo" }} -{{ "something" | .RenderString $opts }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - - _, err := b.BuildE() - - want := `no content renderer found for markup "foo"` - if !strings.Contains(err.Error(), want) { - t.Errorf("error msg must contain %q, error msg actually contains %q", want, err.Error()) - } -} - -// Issue #11547 -func TestTitleCaseStyleWithAutomaticSectionPages(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -titleCaseStyle = 'none' --- content/books/book-1.md -- ---- -title: Book 1 -tags: [fiction] ---- --- content/films/_index.md -- ---- -title: Films ---- --- layouts/index.html -- -{{ (site.GetPage "/tags").Title }} -{{ (site.GetPage "/tags/fiction").Title }} -{{ (site.GetPage "/books").Title }} -{{ (site.GetPage "/films").Title }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - b.Build() - b.AssertFileContent("public/index.html", "tags\nfiction\nbooks\nFilms") -} diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 34d3c5530..3fec04df0 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -90,6 +90,7 @@ func TestRunning(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBu return NewIntegrationTestBuilder(cfg).Build() } +// In most cases you should not use this function directly, but the Test or TestRunning function. func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuilder { // Code fences. conf.TxtarString = strings.ReplaceAll(conf.TxtarString, "§§§", "```") diff --git a/langs/i18n/i18n_integration_test.go b/langs/i18n/i18n_integration_test.go new file mode 100644 index 000000000..7deae6451 --- /dev/null +++ b/langs/i18n/i18n_integration_test.go @@ -0,0 +1,143 @@ +// Copyright 2024 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 i18n_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestI18nFromTheme(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[module] +[[module.imports]] +path = "mytheme" +-- i18n/en.toml -- +[l1] +other = 'l1main' +[l2] +other = 'l2main' +-- themes/mytheme/i18n/en.toml -- +[l1] +other = 'l1theme' +[l2] +other = 'l2theme' +[l3] +other = 'l3theme' +-- layouts/index.html -- +l1: {{ i18n "l1" }}|l2: {{ i18n "l2" }}|l3: {{ i18n "l3" }} + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +l1: l1main|l2: l2main|l3: l3theme + `) +} + +func TestPassPageToI18n(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/_index.md -- +--- +title: "Home" +--- +Duis quis irure id nisi sunt minim aliqua occaecat. Aliqua cillum labore consectetur quis culpa tempor quis non officia cupidatat in ad cillum. Velit irure pariatur nisi adipisicing officia reprehenderit commodo esse non. + +Ullamco cupidatat nostrud ut reprehenderit. Consequat nisi culpa magna amet tempor velit reprehenderit. Ad minim eiusmod tempor nostrud eu aliquip consectetur commodo ut in aliqua enim. Cupidatat voluptate laborum consequat qui nulla laborum laborum aute ea culpa nulla dolor cillum veniam. Commodo esse tempor qui labore aute aliqua sint nulla do. + +Ad deserunt esse nostrud labore. Amet reprehenderit fugiat nostrud eu reprehenderit sit reprehenderit minim deserunt esse id occaecat cillum. Ad qui Lorem cillum laboris ipsum anim in culpa ad dolor consectetur minim culpa. + +Lorem cupidatat officia aute in eu commodo anim nulla deserunt occaecat reprehenderit dolore. Eu cupidatat reprehenderit ipsum sit laboris proident. Duis quis nulla tempor adipisicing. Adipisicing amet ad reprehenderit non mollit. Cupidatat proident tempor laborum sit ipsum adipisicing sunt magna labore. Eu irure nostrud cillum exercitation tempor proident. Laborum magna nisi consequat do sint occaecat magna incididunt. + +Sit mollit amet esse dolore in labore aliquip eu duis officia incididunt. Esse veniam labore excepteur eiusmod occaecat ullamco magna sunt. Ipsum occaecat exercitation anim fugiat in amet excepteur excepteur aliquip laborum. Aliquip aliqua consequat officia sit sint amet aliqua ipsum eu veniam. Id enim quis ea in eu consequat exercitation occaecat veniam consequat anim nulla adipisicing minim. Ut duis cillum laboris duis non commodo eu aliquip tempor nisi aute do. + +Ipsum nulla esse excepteur ut aliqua esse incididunt deserunt veniam dolore est laborum nisi veniam. Magna eiusmod Lorem do tempor incididunt ut aute aliquip ipsum ea laboris culpa. Occaecat do officia velit fugiat culpa eu minim magna sint occaecat sunt. Duis magna proident incididunt est cupidatat proident esse proident ut ipsum non dolor Lorem eiusmod. Officia quis irure id eu aliquip. + +Duis anim elit in officia in in aliquip est. Aliquip nisi labore qui elit elit cupidatat ut labore incididunt eiusmod ipsum. Sit irure nulla non cupidatat exercitation sit culpa nisi ex dolore. Culpa nisi duis duis eiusmod commodo nulla. + +Et magna aliqua amet qui mollit. Eiusmod aute ut anim ea est fugiat non nisi in laborum ullamco. Proident mollit sunt nostrud irure esse sunt eiusmod deserunt dolor. Irure aute ad magna est consequat duis cupidatat consequat. Enim tempor aute cillum quis ea do enim proident incididunt aliquip cillum tempor minim. Nulla minim tempor proident in excepteur consectetur veniam. + +Exercitation tempor nulla incididunt deserunt laboris ad incididunt aliqua exercitation. Adipisicing laboris veniam aute eiusmod qui magna fugiat velit. Aute quis officia anim commodo id fugiat nostrud est. Quis ipsum amet velit adipisicing eu anim minim eu est in culpa aute. Esse in commodo irure enim proident reprehenderit ullamco in dolore aute cillum. + +Irure excepteur ex occaecat ipsum laboris fugiat exercitation. Exercitation adipisicing velit excepteur eu culpa consequat exercitation dolore. In laboris aute quis qui mollit minim culpa. Magna velit ea aliquip veniam fugiat mollit veniam. +-- i18n/en.toml -- +[a] +other = 'Reading time: {{ .ReadingTime }}' +-- layouts/index.html -- +i18n: {{ i18n "a" . }}| + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` + i18n: Reading time: 3| + `) +} + +// Issue 9216 +func TestI18nDefaultContentLanguage(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +disableKinds = ['RSS','sitemap','taxonomy','term','page','section'] +defaultContentLanguage = 'es' +defaultContentLanguageInSubdir = true +[languages.es] +[languages.fr] +-- i18n/es.toml -- +cat = 'gato' +-- i18n/fr.toml -- +# this file intentionally empty +-- layouts/index.html -- +{{ .Title }}_{{ T "cat" }} +-- content/_index.fr.md -- +--- +title: home_fr +--- +-- content/_index.md -- +--- +title: home_es +--- +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/es/index.html", `home_es_gato`) + b.AssertFileContent("public/fr/index.html", `home_fr_gato`) +} diff --git a/langs/i18n/integration_test.go b/langs/i18n/integration_test.go deleted file mode 100644 index 7deae6451..000000000 --- a/langs/i18n/integration_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2024 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 i18n_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestI18nFromTheme(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[module] -[[module.imports]] -path = "mytheme" --- i18n/en.toml -- -[l1] -other = 'l1main' -[l2] -other = 'l2main' --- themes/mytheme/i18n/en.toml -- -[l1] -other = 'l1theme' -[l2] -other = 'l2theme' -[l3] -other = 'l3theme' --- layouts/index.html -- -l1: {{ i18n "l1" }}|l2: {{ i18n "l2" }}|l3: {{ i18n "l3" }} - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -l1: l1main|l2: l2main|l3: l3theme - `) -} - -func TestPassPageToI18n(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/_index.md -- ---- -title: "Home" ---- -Duis quis irure id nisi sunt minim aliqua occaecat. Aliqua cillum labore consectetur quis culpa tempor quis non officia cupidatat in ad cillum. Velit irure pariatur nisi adipisicing officia reprehenderit commodo esse non. - -Ullamco cupidatat nostrud ut reprehenderit. Consequat nisi culpa magna amet tempor velit reprehenderit. Ad minim eiusmod tempor nostrud eu aliquip consectetur commodo ut in aliqua enim. Cupidatat voluptate laborum consequat qui nulla laborum laborum aute ea culpa nulla dolor cillum veniam. Commodo esse tempor qui labore aute aliqua sint nulla do. - -Ad deserunt esse nostrud labore. Amet reprehenderit fugiat nostrud eu reprehenderit sit reprehenderit minim deserunt esse id occaecat cillum. Ad qui Lorem cillum laboris ipsum anim in culpa ad dolor consectetur minim culpa. - -Lorem cupidatat officia aute in eu commodo anim nulla deserunt occaecat reprehenderit dolore. Eu cupidatat reprehenderit ipsum sit laboris proident. Duis quis nulla tempor adipisicing. Adipisicing amet ad reprehenderit non mollit. Cupidatat proident tempor laborum sit ipsum adipisicing sunt magna labore. Eu irure nostrud cillum exercitation tempor proident. Laborum magna nisi consequat do sint occaecat magna incididunt. - -Sit mollit amet esse dolore in labore aliquip eu duis officia incididunt. Esse veniam labore excepteur eiusmod occaecat ullamco magna sunt. Ipsum occaecat exercitation anim fugiat in amet excepteur excepteur aliquip laborum. Aliquip aliqua consequat officia sit sint amet aliqua ipsum eu veniam. Id enim quis ea in eu consequat exercitation occaecat veniam consequat anim nulla adipisicing minim. Ut duis cillum laboris duis non commodo eu aliquip tempor nisi aute do. - -Ipsum nulla esse excepteur ut aliqua esse incididunt deserunt veniam dolore est laborum nisi veniam. Magna eiusmod Lorem do tempor incididunt ut aute aliquip ipsum ea laboris culpa. Occaecat do officia velit fugiat culpa eu minim magna sint occaecat sunt. Duis magna proident incididunt est cupidatat proident esse proident ut ipsum non dolor Lorem eiusmod. Officia quis irure id eu aliquip. - -Duis anim elit in officia in in aliquip est. Aliquip nisi labore qui elit elit cupidatat ut labore incididunt eiusmod ipsum. Sit irure nulla non cupidatat exercitation sit culpa nisi ex dolore. Culpa nisi duis duis eiusmod commodo nulla. - -Et magna aliqua amet qui mollit. Eiusmod aute ut anim ea est fugiat non nisi in laborum ullamco. Proident mollit sunt nostrud irure esse sunt eiusmod deserunt dolor. Irure aute ad magna est consequat duis cupidatat consequat. Enim tempor aute cillum quis ea do enim proident incididunt aliquip cillum tempor minim. Nulla minim tempor proident in excepteur consectetur veniam. - -Exercitation tempor nulla incididunt deserunt laboris ad incididunt aliqua exercitation. Adipisicing laboris veniam aute eiusmod qui magna fugiat velit. Aute quis officia anim commodo id fugiat nostrud est. Quis ipsum amet velit adipisicing eu anim minim eu est in culpa aute. Esse in commodo irure enim proident reprehenderit ullamco in dolore aute cillum. - -Irure excepteur ex occaecat ipsum laboris fugiat exercitation. Exercitation adipisicing velit excepteur eu culpa consequat exercitation dolore. In laboris aute quis qui mollit minim culpa. Magna velit ea aliquip veniam fugiat mollit veniam. --- i18n/en.toml -- -[a] -other = 'Reading time: {{ .ReadingTime }}' --- layouts/index.html -- -i18n: {{ i18n "a" . }}| - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` - i18n: Reading time: 3| - `) -} - -// Issue 9216 -func TestI18nDefaultContentLanguage(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -disableKinds = ['RSS','sitemap','taxonomy','term','page','section'] -defaultContentLanguage = 'es' -defaultContentLanguageInSubdir = true -[languages.es] -[languages.fr] --- i18n/es.toml -- -cat = 'gato' --- i18n/fr.toml -- -# this file intentionally empty --- layouts/index.html -- -{{ .Title }}_{{ T "cat" }} --- content/_index.fr.md -- ---- -title: home_fr ---- --- content/_index.md -- ---- -title: home_es ---- -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/es/index.html", `home_es_gato`) - b.AssertFileContent("public/fr/index.html", `home_fr_gato`) -} diff --git a/markup/goldmark/codeblocks/codeblocks_integration_test.go b/markup/goldmark/codeblocks/codeblocks_integration_test.go new file mode 100644 index 000000000..5597fc507 --- /dev/null +++ b/markup/goldmark/codeblocks/codeblocks_integration_test.go @@ -0,0 +1,422 @@ +// Copyright 2024 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 codeblocks_test + +import ( + "path/filepath" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestCodeblocks(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup] + [markup.highlight] + anchorLineNos = false + codeFences = true + guessSyntax = false + hl_Lines = '' + lineAnchors = '' + lineNoStart = 1 + lineNos = false + lineNumbersInTable = true + noClasses = false + style = 'monokai' + tabWidth = 4 +-- layouts/_default/_markup/render-codeblock-goat.html -- +{{ $diagram := diagrams.Goat .Inner }} +Goat SVG:{{ substr $diagram.Wrapped 0 100 | safeHTML }} }}| +Goat Attribute: {{ .Attributes.width}}| +-- layouts/_default/_markup/render-codeblock-go.html -- +Go Code: {{ .Inner | safeHTML }}| +Go Language: {{ .Type }}| +-- layouts/_default/single.html -- +{{ .Content }} +-- content/p1.md -- +--- +title: "p1" +--- + +## Ascii Diagram + +§§§goat { width="600" } +---> +§§§ + +## Go Code + +§§§go +fmt.Println("Hello, World!"); +§§§ + +## Golang Code + +§§§golang +fmt.Println("Hello, Golang!"); +§§§ + +## Bash Code + +§§§bash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue } +echo "l1"; +echo "l2"; +echo "l3"; +echo "l4"; +echo "l5"; +echo "l6"; +echo "l7"; +echo "l8"; +§§§ +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +Goat SVG:Go Code\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|", + "

Golang Code

\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|", + "

Bash Code

\n
32echo "l1";\n33",
+	)
+}
+
+func TestHighlightCodeblock(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+[markup]
+[markup.highlight]
+anchorLineNos = false
+codeFences = true
+guessSyntax = false
+hl_Lines = ''
+lineAnchors = ''
+lineNoStart = 1
+lineNos = false
+lineNumbersInTable = true
+noClasses = false
+style = 'monokai'
+tabWidth = 4
+-- layouts/_default/_markup/render-codeblock.html --
+{{ $result := transform.HighlightCodeBlock . }}
+Inner: |{{ $result.Inner | safeHTML }}|
+Wrapped: |{{ $result.Wrapped | safeHTML }}|
+-- layouts/_default/single.html --
+{{ .Content }}
+-- content/p1.md --
+---
+title: "p1"
+---
+
+## Go Code
+
+§§§go
+fmt.Println("Hello, World!");
+§§§
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			NeedsOsFS:   false,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html",
+		"Inner: |fmt.Println("Hello, World!");|",
+		"Wrapped: |
fmt.Println("Hello, World!");
|", + ) +} + +func TestCodeblocksBugs(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/_default/_markup/render-codeblock.html -- +{{ .Position | safeHTML }} +-- layouts/_default/single.html -- +{{ .Content }} +-- content/p1.md -- +--- +title: "p1" +--- + +## Issue 9627 + +§§§text +{{}} +§§§ + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +# Issue 9627: For the Position in code blocks we try to match the .Inner with the original source. This isn't always possible. +p1.md:0:0 + `, + ) +} + +func TestCodeChomp(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- + +§§§bash +echo "p1"; +§§§ +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-codeblock.html -- +|{{ .Inner | safeHTML }}| + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|") +} + +func TestCodePosition(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- + +## Code + +§§§ +echo "p1"; +§§§ +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-codeblock.html -- +Position: {{ .Position | safeHTML }} + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"/content/p1.md:7:1\"")) +} + +// Issue 10118 +func TestAttributes(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- + +## Issue 10118 + +§§§ {foo="bar"} +Hello, World! +§§§ + +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-codeblock.html -- +Attributes: {{ .Attributes }}|Type: {{ .Type }}| +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", "

Issue 10118

\nAttributes: map[foo:bar]|Type: |") +} + +// Issue 9571 +func TestAttributesChroma(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- + +## Code + +§§§LANGUAGE {style=monokai} +echo "p1"; +§§§ +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-codeblock.html -- +Attributes: {{ .Attributes }}|Options: {{ .Options }}| + + +` + testLanguage := func(language, expect string) { + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: strings.ReplaceAll(files, "LANGUAGE", language), + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", expect) + } + + testLanguage("bash", "Attributes: map[]|Options: map[style:monokai]|") + testLanguage("hugo", "Attributes: map[style:monokai]|Options: map[]|") +} + +func TestPanics(t *testing.T) { + files := ` +-- config.toml -- +[markup] +[markup.goldmark] +[markup.goldmark.parser] +autoHeadingID = true +autoHeadingIDType = "github" +[markup.goldmark.parser.attribute] +block = true +title = true +-- content/p1.md -- +--- +title: "p1" +--- + +BLOCK + +Common + +-- layouts/_default/single.html -- +{{ .Content }} + + +` + + for _, test := range []struct { + name string + markdown string + }{ + {"issue-9819", "asdf\n: {#myid}"}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: strings.ReplaceAll(files, "BLOCK", test.markdown), + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", "Common") + }) + } +} + +// Issue 10835 +func TestAttributesValidation(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ["taxonomy", "term"] +-- content/p1.md -- +--- +title: "p1" +--- + +## Issue 10835 + +§§§bash { color=red dimensions=300x200 } +Hello, World! +§§§ + +-- layouts/index.html -- +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-codeblock.html -- +Attributes: {{ .Attributes }}|Type: {{ .Type }}| +` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.Not(qt.IsNil)) + b.Assert(err.Error(), qt.Contains, "p1.md:7:9\": failed to parse Markdown attributes; you may need to quote the values") +} diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go deleted file mode 100644 index 5597fc507..000000000 --- a/markup/goldmark/codeblocks/integration_test.go +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright 2024 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 codeblocks_test - -import ( - "path/filepath" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestCodeblocks(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup] - [markup.highlight] - anchorLineNos = false - codeFences = true - guessSyntax = false - hl_Lines = '' - lineAnchors = '' - lineNoStart = 1 - lineNos = false - lineNumbersInTable = true - noClasses = false - style = 'monokai' - tabWidth = 4 --- layouts/_default/_markup/render-codeblock-goat.html -- -{{ $diagram := diagrams.Goat .Inner }} -Goat SVG:{{ substr $diagram.Wrapped 0 100 | safeHTML }} }}| -Goat Attribute: {{ .Attributes.width}}| --- layouts/_default/_markup/render-codeblock-go.html -- -Go Code: {{ .Inner | safeHTML }}| -Go Language: {{ .Type }}| --- layouts/_default/single.html -- -{{ .Content }} --- content/p1.md -- ---- -title: "p1" ---- - -## Ascii Diagram - -§§§goat { width="600" } ----> -§§§ - -## Go Code - -§§§go -fmt.Println("Hello, World!"); -§§§ - -## Golang Code - -§§§golang -fmt.Println("Hello, Golang!"); -§§§ - -## Bash Code - -§§§bash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue } -echo "l1"; -echo "l2"; -echo "l3"; -echo "l4"; -echo "l5"; -echo "l6"; -echo "l7"; -echo "l8"; -§§§ -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -Goat SVG:Go Code\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|", - "

Golang Code

\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|", - "

Bash Code

\n
32echo "l1";\n33",
-	)
-}
-
-func TestHighlightCodeblock(t *testing.T) {
-	t.Parallel()
-
-	files := `
--- config.toml --
-[markup]
-[markup.highlight]
-anchorLineNos = false
-codeFences = true
-guessSyntax = false
-hl_Lines = ''
-lineAnchors = ''
-lineNoStart = 1
-lineNos = false
-lineNumbersInTable = true
-noClasses = false
-style = 'monokai'
-tabWidth = 4
--- layouts/_default/_markup/render-codeblock.html --
-{{ $result := transform.HighlightCodeBlock . }}
-Inner: |{{ $result.Inner | safeHTML }}|
-Wrapped: |{{ $result.Wrapped | safeHTML }}|
--- layouts/_default/single.html --
-{{ .Content }}
--- content/p1.md --
----
-title: "p1"
----
-
-## Go Code
-
-§§§go
-fmt.Println("Hello, World!");
-§§§
-
-`
-
-	b := hugolib.NewIntegrationTestBuilder(
-		hugolib.IntegrationTestConfig{
-			T:           t,
-			TxtarString: files,
-			NeedsOsFS:   false,
-		},
-	).Build()
-
-	b.AssertFileContent("public/p1/index.html",
-		"Inner: |fmt.Println("Hello, World!");|",
-		"Wrapped: |
fmt.Println("Hello, World!");
|", - ) -} - -func TestCodeblocksBugs(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- layouts/_default/_markup/render-codeblock.html -- -{{ .Position | safeHTML }} --- layouts/_default/single.html -- -{{ .Content }} --- content/p1.md -- ---- -title: "p1" ---- - -## Issue 9627 - -§§§text -{{}} -§§§ - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -# Issue 9627: For the Position in code blocks we try to match the .Inner with the original source. This isn't always possible. -p1.md:0:0 - `, - ) -} - -func TestCodeChomp(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/p1.md -- ---- -title: "p1" ---- - -§§§bash -echo "p1"; -§§§ --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-codeblock.html -- -|{{ .Inner | safeHTML }}| - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|") -} - -func TestCodePosition(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/p1.md -- ---- -title: "p1" ---- - -## Code - -§§§ -echo "p1"; -§§§ --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-codeblock.html -- -Position: {{ .Position | safeHTML }} - - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"/content/p1.md:7:1\"")) -} - -// Issue 10118 -func TestAttributes(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/p1.md -- ---- -title: "p1" ---- - -## Issue 10118 - -§§§ {foo="bar"} -Hello, World! -§§§ - --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-codeblock.html -- -Attributes: {{ .Attributes }}|Type: {{ .Type }}| -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", "

Issue 10118

\nAttributes: map[foo:bar]|Type: |") -} - -// Issue 9571 -func TestAttributesChroma(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/p1.md -- ---- -title: "p1" ---- - -## Code - -§§§LANGUAGE {style=monokai} -echo "p1"; -§§§ --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-codeblock.html -- -Attributes: {{ .Attributes }}|Options: {{ .Options }}| - - -` - testLanguage := func(language, expect string) { - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: strings.ReplaceAll(files, "LANGUAGE", language), - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", expect) - } - - testLanguage("bash", "Attributes: map[]|Options: map[style:monokai]|") - testLanguage("hugo", "Attributes: map[style:monokai]|Options: map[]|") -} - -func TestPanics(t *testing.T) { - files := ` --- config.toml -- -[markup] -[markup.goldmark] -[markup.goldmark.parser] -autoHeadingID = true -autoHeadingIDType = "github" -[markup.goldmark.parser.attribute] -block = true -title = true --- content/p1.md -- ---- -title: "p1" ---- - -BLOCK - -Common - --- layouts/_default/single.html -- -{{ .Content }} - - -` - - for _, test := range []struct { - name string - markdown string - }{ - {"issue-9819", "asdf\n: {#myid}"}, - } { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: strings.ReplaceAll(files, "BLOCK", test.markdown), - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", "Common") - }) - } -} - -// Issue 10835 -func TestAttributesValidation(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ["taxonomy", "term"] --- content/p1.md -- ---- -title: "p1" ---- - -## Issue 10835 - -§§§bash { color=red dimensions=300x200 } -Hello, World! -§§§ - --- layouts/index.html -- --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-codeblock.html -- -Attributes: {{ .Attributes }}|Type: {{ .Type }}| -` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - - b.Assert(err, qt.Not(qt.IsNil)) - b.Assert(err.Error(), qt.Contains, "p1.md:7:9\": failed to parse Markdown attributes; you may need to quote the values") -} diff --git a/markup/goldmark/goldmark_integration_test.go b/markup/goldmark/goldmark_integration_test.go new file mode 100644 index 000000000..c9c6ef338 --- /dev/null +++ b/markup/goldmark/goldmark_integration_test.go @@ -0,0 +1,813 @@ +// Copyright 2021 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 goldmark_test + +import ( + "fmt" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 9463 +func TestAttributeExclusion(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.renderer] + unsafe = false +[markup.goldmark.parser.attribute] + block = true + title = true +-- content/p1.md -- +--- +title: "p1" +--- +## Heading {class="a" onclick="alert('heading')"} + +> Blockquote +{class="b" ondblclick="alert('blockquote')"} + +~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true} +foo +~~~ +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +

+
+
+ `) +} + +// Issue 9511 +func TestAttributeExclusionWithRenderHook(t *testing.T) { + t.Parallel() + + files := ` +-- content/p1.md -- +--- +title: "p1" +--- +## Heading {onclick="alert('renderhook')" data-foo="bar"} +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-heading.html -- +{{ .Text | safeHTML }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +

Heading

+ `) +} + +func TestAttributesDefaultRenderer(t *testing.T) { + t.Parallel() + + files := ` +-- content/p1.md -- +--- +title: "p1" +--- +## Heading Attribute Which Needs Escaping { class="a < b" } +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +class="a < b" + `) +} + +// Issue 9558. +func TestAttributesHookNoEscape(t *testing.T) { + t.Parallel() + + files := ` +-- content/p1.md -- +--- +title: "p1" +--- +## Heading Attribute Which Needs Escaping { class="Smith & Wesson" } +-- layouts/_default/_markup/render-heading.html -- +plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}| +safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}| +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` +plain: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| +safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| + `) +} + +// Issue 9504 +func TestLinkInTitle(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- +## Hello [Test](https://example.com) +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-heading.html -- + + {{ .Text | safeHTML }} + # + +-- layouts/_default/_markup/render-link.html -- +{{ .Text | safeHTML }} + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "

\n Hello Test\n\n #\n

", + ) +} + +func TestHighlight(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup] +[markup.highlight] +anchorLineNos = false +codeFences = true +guessSyntax = false +hl_Lines = '' +lineAnchors = '' +lineNoStart = 1 +lineNos = false +lineNumbersInTable = true +noClasses = false +style = 'monokai' +tabWidth = 4 +-- layouts/_default/single.html -- +{{ .Content }} +-- content/p1.md -- +--- +title: "p1" +--- + +## Code Fences + +§§§bash +LINE1 +§§§ + +## Code Fences No Lexer + +§§§moo +LINE1 +§§§ + +## Code Fences Simple Attributes + +§§A§bash { .myclass id="myid" } +LINE1 +§§A§ + +## Code Fences Line Numbers + +§§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199} +LINE1 +LINE2 +LINE3 +LINE4 +LINE5 +LINE6 +LINE7 +LINE8 +§§§ + + + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "
LINE1\n
", + "Code Fences No Lexer

\n
LINE1\n
", + "lnt", + ) +} + +func BenchmarkRenderHooks(b *testing.B) { + files := ` +-- config.toml -- +-- layouts/_default/_markup/render-heading.html -- + + {{ .Text | safeHTML }} + # + +-- layouts/_default/_markup/render-link.html -- +{{ .Text | safeHTML }} +-- layouts/_default/single.html -- +{{ .Content }} +` + + content := ` + +## Hello1 [Test](https://example.com) + +A. + +## Hello2 [Test](https://example.com) + +B. + +## Hello3 [Test](https://example.com) + +C. + +## Hello4 [Test](https://example.com) + +D. + +[Test](https://example.com) + +## Hello5 + + +` + + for i := 1; i < 100; i++ { + files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) + } + + cfg := hugolib.IntegrationTestConfig{ + T: b, + TxtarString: files, + } + builders := make([]*hugolib.IntegrationTestBuilder, b.N) + + for i := range builders { + builders[i] = hugolib.NewIntegrationTestBuilder(cfg) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + builders[i].Build() + } +} + +func BenchmarkCodeblocks(b *testing.B) { + filesTemplate := ` +-- config.toml -- +[markup] + [markup.highlight] + anchorLineNos = false + codeFences = true + guessSyntax = false + hl_Lines = '' + lineAnchors = '' + lineNoStart = 1 + lineNos = false + lineNumbersInTable = true + noClasses = true + style = 'monokai' + tabWidth = 4 +-- layouts/_default/single.html -- +{{ .Content }} +` + + content := ` + +FENCEgo +package main +import "fmt" +func main() { + fmt.Println("hello world") +} +FENCE + +FENCEunknownlexer +hello +FENCE +` + + content = strings.ReplaceAll(content, "FENCE", "```") + + for i := 1; i < 100; i++ { + filesTemplate += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) + } + + runBenchmark := func(files string, b *testing.B) { + cfg := hugolib.IntegrationTestConfig{ + T: b, + TxtarString: files, + } + builders := make([]*hugolib.IntegrationTestBuilder, b.N) + + for i := range builders { + builders[i] = hugolib.NewIntegrationTestBuilder(cfg) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + builders[i].Build() + } + } + + b.Run("Default", func(b *testing.B) { + runBenchmark(filesTemplate, b) + }) + + b.Run("Hook no higlight", func(b *testing.B) { + files := filesTemplate + ` +-- layouts/_default/_markup/render-codeblock.html -- +{{ .Inner }} +` + + runBenchmark(files, b) + }) + +} + +// Iisse #8959 +func TestHookInfiniteRecursion(t *testing.T) { + t.Parallel() + + for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} { + t.Run(renderFunc, func(t *testing.T) { + + files := ` +-- config.toml -- +-- layouts/_default/_markup/render-link.html -- +{{ .Text | RENDERFUNC }} +-- layouts/_default/single.html -- +{{ .Content }} +-- content/p1.md -- +--- +title: "p1" +--- + +https://example.org + +a@b.com + + + ` + + files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc) + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion") + + }) + + } + +} + +// Issue 9594 +func TestQuotesInImgAltAttr(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.extensions] + typographer = false +-- content/p1.md -- +--- +title: "p1" +--- +!["a"](b.jpg) +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", ` + "a" + `) +} + +func TestLinkifyProtocol(t *testing.T) { + t.Parallel() + + runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder { + + files := ` +-- config.toml -- +[markup.goldmark] +[markup.goldmark.extensions] +linkify = true +linkifyProtocol = "PROTOCOL" +-- content/p1.md -- +--- +title: "p1" +--- +Link no procol: www.example.org +Link http procol: http://www.example.org +Link https procol: https://www.example.org + +-- layouts/_default/single.html -- +{{ .Content }} +` + files = strings.ReplaceAll(files, "PROTOCOL", protocol) + + if withHook { + files += `-- layouts/_default/_markup/render-link.html -- +{{ .Text | safeHTML }}` + } + + return hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + } + + for _, withHook := range []bool{false, true} { + + b := runTest("https", withHook) + + b.AssertFileContent("public/p1/index.html", + "Link no procol: www.example.org", + "Link http procol: http://www.example.org", + "Link https procol: https://www.example.org

", + ) + + b = runTest("http", withHook) + + b.AssertFileContent("public/p1/index.html", + "Link no procol: www.example.org", + "Link http procol: http://www.example.org", + "Link https procol: https://www.example.org

", + ) + + b = runTest("gopher", withHook) + + b.AssertFileContent("public/p1/index.html", + "Link no procol: www.example.org", + "Link http procol: http://www.example.org", + "Link https procol: https://www.example.org

", + ) + + } +} + +func TestGoldmarkBugs(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.renderer] +unsafe = true +-- content/p1.md -- +--- +title: "p1" +--- + +## Issue 9650 + +a c + +## Issue 9658 + +- This is a list item + + +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContentExact("public/p1/index.html", + // Issue 9650 + "

a c

", + // Issue 9658 (crash) + "
  • This is a list item
  • ", + ) +} + +// Issue #7332 +// Issue #11587 +func TestGoldmarkEmojiExtension(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +enableEmoji = true +-- content/p1.md -- +--- +title: "p1" +--- +~~~text +:x: +~~~ + +{{% include "/p2" %}} + +{{< sc1 >}}:smiley:{{< /sc1 >}} + +{{< sc2 >}}:+1:{{< /sc2 >}} + +{{% sc3 %}}:-1:{{% /sc3 %}} + +-- content/p2.md -- +--- +title: "p2" +--- +:heavy_check_mark: +-- layouts/shortcodes/include.html -- +{{ $p := site.GetPage (.Get 0) }} +{{ $p.RenderShortcodes }} +-- layouts/shortcodes/sc1.html -- +sc1_begin|{{ .Inner }}|sc1_end +-- layouts/shortcodes/sc2.html -- +sc2_begin|{{ .Inner | .Page.RenderString }}|sc2_end +-- layouts/shortcodes/sc3.html -- +sc3_begin|{{ .Inner }}|sc3_end +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContentExact("public/p1/index.html", + // Issue #7332 + ":x:\n", + // Issue #11587 + "

    ✔️

    ", + // Should not be converted to emoji + "sc1_begin|:smiley:|sc1_end", + // Should be converted to emoji + "sc2_begin|👍|sc2_end", + // Should be converted to emoji + "sc3_begin|👎|sc3_end", + ) +} + +func TestEmojiDisabled(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +enableEmoji = false +-- content/p1.md -- +--- +title: "p1" +--- +:x: +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") +} + +func TestEmojiDefaultConfig(t *testing.T) { + t.Parallel() + + files := ` +-- content/p1.md -- +--- +title: "p1" +--- +:x: +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") +} + +// Issue #5748 +func TestGoldmarkTemplateDelims(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[minify] + minifyOutput = true +[minify.tdewolff.html] + templateDelims = [""] +-- layouts/index.html -- +
    +{{ safeHTML "" }} +
    +` + + b := hugolib.Test(t, files) + b.AssertFileContent("public/index.html", "
    \n
    ") +} + +// Issue #10894 +func TestPassthroughInlineFences(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.extensions.passthrough] +enable = true +[markup.goldmark.extensions.passthrough.delimiters] +inline = [['$', '$'], ['\(', '\)']] +-- content/p1.md -- +--- +title: "p1" +--- +## LaTeX test + +Inline equation that would be mangled by default parser: $a^*=x-b^*$ + +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.Test(t, files) + b.AssertFileContent("public/p1/index.html", ` + $a^*=x-b^*$ + `) +} + +func TestPassthroughBlockFences(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.extensions.passthrough] +enable = true +[markup.goldmark.extensions.passthrough.delimiters] +block = [['$$', '$$']] +-- content/p1.md -- +--- +title: "p1" +--- +## LaTeX test + +Block equation that would be mangled by default parser: + +$$a^*=x-b^*$$ + +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.Test(t, files) + b.AssertFileContent("public/p1/index.html", ` + $$a^*=x-b^*$$ + `) +} + +func TestPassthroughWithAlternativeFences(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.goldmark.extensions.passthrough] +enable = true +[markup.goldmark.extensions.passthrough.delimiters] +inline = [['(((', ')))']] +block = [['%!%', '%!%']] +-- content/p1.md -- +--- +title: "p1" +--- +## LaTeX test + +Inline equation that would be mangled by default parser: (((a^*=x-b^*))) +Inline equation that should be mangled by default parser: $a^*=x-b^*$ + +Block equation that would be mangled by default parser: + +%!% +a^*=x-b^* +%!% + +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.Test(t, files) + b.AssertFileContent("public/p1/index.html", ` + (((a^*=x-b^*))) + `) + b.AssertFileContent("public/p1/index.html", ` + $a^=x-b^$ + `) + b.AssertFileContent("public/p1/index.html", ` +%!% +a^*=x-b^* +%!% + `) +} diff --git a/markup/goldmark/images/images_integration_test.go b/markup/goldmark/images/images_integration_test.go new file mode 100644 index 000000000..8b0ba99c1 --- /dev/null +++ b/markup/goldmark/images/images_integration_test.go @@ -0,0 +1,112 @@ +package images_test + +import ( + "strings" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestDisableWrapStandAloneImageWithinParagraph(t *testing.T) { + t.Parallel() + + filesTemplate := ` +-- config.toml -- +[markup.goldmark.renderer] + unsafe = false +[markup.goldmark.parser] +wrapStandAloneImageWithinParagraph = CONFIG_VALUE +[markup.goldmark.parser.attribute] + block = true + title = true +-- content/p1.md -- +--- +title: "p1" +--- + +This is an inline image: ![Inline Image](/inline.jpg). Some more text. + +![Block Image](/block.jpg) +{.b} + + +-- layouts/_default/single.html -- +{{ .Content }} +` + + t.Run("With Hook, no wrap", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false") + files = files + `-- layouts/_default/_markup/render-image.html -- +{{ if .IsBlock }} +
    + {{ .Text }}|{{ .Ordinal }} +
    +{{ else }} + {{ .Text }}|{{ .Ordinal }} +{{ end }} +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "This is an inline image: \n\t\"Inline\n. Some more text.

    ", + "
    \n\t\"Block", + ) + }) + + t.Run("With Hook, wrap", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true") + files = files + `-- layouts/_default/_markup/render-image.html -- +{{ if .IsBlock }} +
    + {{ .Text }} +
    +{{ else }} + {{ .Text }} +{{ end }} +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "This is an inline image: \n\t\"Inline\n. Some more text.

    ", + "

    \n\t\"Block\n

    ", + ) + }) + + t.Run("No Hook, no wrap", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false") + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", "

    This is an inline image: \"Inline. Some more text.

    \n\"Block") + }) + + t.Run("No Hook, wrap", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true") + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", "

    \"Block

    ") + }) +} diff --git a/markup/goldmark/images/integration_test.go b/markup/goldmark/images/integration_test.go deleted file mode 100644 index 8b0ba99c1..000000000 --- a/markup/goldmark/images/integration_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package images_test - -import ( - "strings" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestDisableWrapStandAloneImageWithinParagraph(t *testing.T) { - t.Parallel() - - filesTemplate := ` --- config.toml -- -[markup.goldmark.renderer] - unsafe = false -[markup.goldmark.parser] -wrapStandAloneImageWithinParagraph = CONFIG_VALUE -[markup.goldmark.parser.attribute] - block = true - title = true --- content/p1.md -- ---- -title: "p1" ---- - -This is an inline image: ![Inline Image](/inline.jpg). Some more text. - -![Block Image](/block.jpg) -{.b} - - --- layouts/_default/single.html -- -{{ .Content }} -` - - t.Run("With Hook, no wrap", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false") - files = files + `-- layouts/_default/_markup/render-image.html -- -{{ if .IsBlock }} -
    - {{ .Text }}|{{ .Ordinal }} -
    -{{ else }} - {{ .Text }}|{{ .Ordinal }} -{{ end }} -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", - "This is an inline image: \n\t\"Inline\n. Some more text.

    ", - "
    \n\t\"Block", - ) - }) - - t.Run("With Hook, wrap", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true") - files = files + `-- layouts/_default/_markup/render-image.html -- -{{ if .IsBlock }} -
    - {{ .Text }} -
    -{{ else }} - {{ .Text }} -{{ end }} -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", - "This is an inline image: \n\t\"Inline\n. Some more text.

    ", - "

    \n\t\"Block\n

    ", - ) - }) - - t.Run("No Hook, no wrap", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false") - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", "

    This is an inline image: \"Inline. Some more text.

    \n\"Block") - }) - - t.Run("No Hook, wrap", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true") - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", "

    \"Block

    ") - }) -} diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go deleted file mode 100644 index c9c6ef338..000000000 --- a/markup/goldmark/integration_test.go +++ /dev/null @@ -1,813 +0,0 @@ -// Copyright 2021 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 goldmark_test - -import ( - "fmt" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 9463 -func TestAttributeExclusion(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.renderer] - unsafe = false -[markup.goldmark.parser.attribute] - block = true - title = true --- content/p1.md -- ---- -title: "p1" ---- -## Heading {class="a" onclick="alert('heading')"} - -> Blockquote -{class="b" ondblclick="alert('blockquote')"} - -~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true} -foo -~~~ --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -

    -
    -
    - `) -} - -// Issue 9511 -func TestAttributeExclusionWithRenderHook(t *testing.T) { - t.Parallel() - - files := ` --- content/p1.md -- ---- -title: "p1" ---- -## Heading {onclick="alert('renderhook')" data-foo="bar"} --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-heading.html -- -{{ .Text | safeHTML }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -

    Heading

    - `) -} - -func TestAttributesDefaultRenderer(t *testing.T) { - t.Parallel() - - files := ` --- content/p1.md -- ---- -title: "p1" ---- -## Heading Attribute Which Needs Escaping { class="a < b" } --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -class="a < b" - `) -} - -// Issue 9558. -func TestAttributesHookNoEscape(t *testing.T) { - t.Parallel() - - files := ` --- content/p1.md -- ---- -title: "p1" ---- -## Heading Attribute Which Needs Escaping { class="Smith & Wesson" } --- layouts/_default/_markup/render-heading.html -- -plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}| -safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}| --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` -plain: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| -safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| - `) -} - -// Issue 9504 -func TestLinkInTitle(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- content/p1.md -- ---- -title: "p1" ---- -## Hello [Test](https://example.com) --- layouts/_default/single.html -- -{{ .Content }} --- layouts/_default/_markup/render-heading.html -- - - {{ .Text | safeHTML }} - # - --- layouts/_default/_markup/render-link.html -- -{{ .Text | safeHTML }} - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", - "

    \n Hello Test\n\n #\n

    ", - ) -} - -func TestHighlight(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup] -[markup.highlight] -anchorLineNos = false -codeFences = true -guessSyntax = false -hl_Lines = '' -lineAnchors = '' -lineNoStart = 1 -lineNos = false -lineNumbersInTable = true -noClasses = false -style = 'monokai' -tabWidth = 4 --- layouts/_default/single.html -- -{{ .Content }} --- content/p1.md -- ---- -title: "p1" ---- - -## Code Fences - -§§§bash -LINE1 -§§§ - -## Code Fences No Lexer - -§§§moo -LINE1 -§§§ - -## Code Fences Simple Attributes - -§§A§bash { .myclass id="myid" } -LINE1 -§§A§ - -## Code Fences Line Numbers - -§§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199} -LINE1 -LINE2 -LINE3 -LINE4 -LINE5 -LINE6 -LINE7 -LINE8 -§§§ - - - - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", - "
    LINE1\n
    ", - "Code Fences No Lexer

    \n
    LINE1\n
    ", - "lnt", - ) -} - -func BenchmarkRenderHooks(b *testing.B) { - files := ` --- config.toml -- --- layouts/_default/_markup/render-heading.html -- - - {{ .Text | safeHTML }} - # - --- layouts/_default/_markup/render-link.html -- -{{ .Text | safeHTML }} --- layouts/_default/single.html -- -{{ .Content }} -` - - content := ` - -## Hello1 [Test](https://example.com) - -A. - -## Hello2 [Test](https://example.com) - -B. - -## Hello3 [Test](https://example.com) - -C. - -## Hello4 [Test](https://example.com) - -D. - -[Test](https://example.com) - -## Hello5 - - -` - - for i := 1; i < 100; i++ { - files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) - } - - cfg := hugolib.IntegrationTestConfig{ - T: b, - TxtarString: files, - } - builders := make([]*hugolib.IntegrationTestBuilder, b.N) - - for i := range builders { - builders[i] = hugolib.NewIntegrationTestBuilder(cfg) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - builders[i].Build() - } -} - -func BenchmarkCodeblocks(b *testing.B) { - filesTemplate := ` --- config.toml -- -[markup] - [markup.highlight] - anchorLineNos = false - codeFences = true - guessSyntax = false - hl_Lines = '' - lineAnchors = '' - lineNoStart = 1 - lineNos = false - lineNumbersInTable = true - noClasses = true - style = 'monokai' - tabWidth = 4 --- layouts/_default/single.html -- -{{ .Content }} -` - - content := ` - -FENCEgo -package main -import "fmt" -func main() { - fmt.Println("hello world") -} -FENCE - -FENCEunknownlexer -hello -FENCE -` - - content = strings.ReplaceAll(content, "FENCE", "```") - - for i := 1; i < 100; i++ { - filesTemplate += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) - } - - runBenchmark := func(files string, b *testing.B) { - cfg := hugolib.IntegrationTestConfig{ - T: b, - TxtarString: files, - } - builders := make([]*hugolib.IntegrationTestBuilder, b.N) - - for i := range builders { - builders[i] = hugolib.NewIntegrationTestBuilder(cfg) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - builders[i].Build() - } - } - - b.Run("Default", func(b *testing.B) { - runBenchmark(filesTemplate, b) - }) - - b.Run("Hook no higlight", func(b *testing.B) { - files := filesTemplate + ` --- layouts/_default/_markup/render-codeblock.html -- -{{ .Inner }} -` - - runBenchmark(files, b) - }) - -} - -// Iisse #8959 -func TestHookInfiniteRecursion(t *testing.T) { - t.Parallel() - - for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} { - t.Run(renderFunc, func(t *testing.T) { - - files := ` --- config.toml -- --- layouts/_default/_markup/render-link.html -- -{{ .Text | RENDERFUNC }} --- layouts/_default/single.html -- -{{ .Content }} --- content/p1.md -- ---- -title: "p1" ---- - -https://example.org - -a@b.com - - - ` - - files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc) - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion") - - }) - - } - -} - -// Issue 9594 -func TestQuotesInImgAltAttr(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.extensions] - typographer = false --- content/p1.md -- ---- -title: "p1" ---- -!["a"](b.jpg) --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", ` - "a" - `) -} - -func TestLinkifyProtocol(t *testing.T) { - t.Parallel() - - runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder { - - files := ` --- config.toml -- -[markup.goldmark] -[markup.goldmark.extensions] -linkify = true -linkifyProtocol = "PROTOCOL" --- content/p1.md -- ---- -title: "p1" ---- -Link no procol: www.example.org -Link http procol: http://www.example.org -Link https procol: https://www.example.org - --- layouts/_default/single.html -- -{{ .Content }} -` - files = strings.ReplaceAll(files, "PROTOCOL", protocol) - - if withHook { - files += `-- layouts/_default/_markup/render-link.html -- -{{ .Text | safeHTML }}` - } - - return hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - } - - for _, withHook := range []bool{false, true} { - - b := runTest("https", withHook) - - b.AssertFileContent("public/p1/index.html", - "Link no procol: www.example.org", - "Link http procol: http://www.example.org", - "Link https procol: https://www.example.org

    ", - ) - - b = runTest("http", withHook) - - b.AssertFileContent("public/p1/index.html", - "Link no procol: www.example.org", - "Link http procol: http://www.example.org", - "Link https procol: https://www.example.org

    ", - ) - - b = runTest("gopher", withHook) - - b.AssertFileContent("public/p1/index.html", - "Link no procol: www.example.org", - "Link http procol: http://www.example.org", - "Link https procol: https://www.example.org

    ", - ) - - } -} - -func TestGoldmarkBugs(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.renderer] -unsafe = true --- content/p1.md -- ---- -title: "p1" ---- - -## Issue 9650 - -a c - -## Issue 9658 - -- This is a list item - - --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContentExact("public/p1/index.html", - // Issue 9650 - "

    a c

    ", - // Issue 9658 (crash) - "
  • This is a list item
  • ", - ) -} - -// Issue #7332 -// Issue #11587 -func TestGoldmarkEmojiExtension(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -enableEmoji = true --- content/p1.md -- ---- -title: "p1" ---- -~~~text -:x: -~~~ - -{{% include "/p2" %}} - -{{< sc1 >}}:smiley:{{< /sc1 >}} - -{{< sc2 >}}:+1:{{< /sc2 >}} - -{{% sc3 %}}:-1:{{% /sc3 %}} - --- content/p2.md -- ---- -title: "p2" ---- -:heavy_check_mark: --- layouts/shortcodes/include.html -- -{{ $p := site.GetPage (.Get 0) }} -{{ $p.RenderShortcodes }} --- layouts/shortcodes/sc1.html -- -sc1_begin|{{ .Inner }}|sc1_end --- layouts/shortcodes/sc2.html -- -sc2_begin|{{ .Inner | .Page.RenderString }}|sc2_end --- layouts/shortcodes/sc3.html -- -sc3_begin|{{ .Inner }}|sc3_end --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContentExact("public/p1/index.html", - // Issue #7332 - ":x:\n", - // Issue #11587 - "

    ✔️

    ", - // Should not be converted to emoji - "sc1_begin|:smiley:|sc1_end", - // Should be converted to emoji - "sc2_begin|👍|sc2_end", - // Should be converted to emoji - "sc3_begin|👎|sc3_end", - ) -} - -func TestEmojiDisabled(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -enableEmoji = false --- content/p1.md -- ---- -title: "p1" ---- -:x: --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") -} - -func TestEmojiDefaultConfig(t *testing.T) { - t.Parallel() - - files := ` --- content/p1.md -- ---- -title: "p1" ---- -:x: --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") -} - -// Issue #5748 -func TestGoldmarkTemplateDelims(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[minify] - minifyOutput = true -[minify.tdewolff.html] - templateDelims = [""] --- layouts/index.html -- -
    -{{ safeHTML "" }} -
    -` - - b := hugolib.Test(t, files) - b.AssertFileContent("public/index.html", "
    \n
    ") -} - -// Issue #10894 -func TestPassthroughInlineFences(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.extensions.passthrough] -enable = true -[markup.goldmark.extensions.passthrough.delimiters] -inline = [['$', '$'], ['\(', '\)']] --- content/p1.md -- ---- -title: "p1" ---- -## LaTeX test - -Inline equation that would be mangled by default parser: $a^*=x-b^*$ - --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.Test(t, files) - b.AssertFileContent("public/p1/index.html", ` - $a^*=x-b^*$ - `) -} - -func TestPassthroughBlockFences(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.extensions.passthrough] -enable = true -[markup.goldmark.extensions.passthrough.delimiters] -block = [['$$', '$$']] --- content/p1.md -- ---- -title: "p1" ---- -## LaTeX test - -Block equation that would be mangled by default parser: - -$$a^*=x-b^*$$ - --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.Test(t, files) - b.AssertFileContent("public/p1/index.html", ` - $$a^*=x-b^*$$ - `) -} - -func TestPassthroughWithAlternativeFences(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.goldmark.extensions.passthrough] -enable = true -[markup.goldmark.extensions.passthrough.delimiters] -inline = [['(((', ')))']] -block = [['%!%', '%!%']] --- content/p1.md -- ---- -title: "p1" ---- -## LaTeX test - -Inline equation that would be mangled by default parser: (((a^*=x-b^*))) -Inline equation that should be mangled by default parser: $a^*=x-b^*$ - -Block equation that would be mangled by default parser: - -%!% -a^*=x-b^* -%!% - --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.Test(t, files) - b.AssertFileContent("public/p1/index.html", ` - (((a^*=x-b^*))) - `) - b.AssertFileContent("public/p1/index.html", ` - $a^=x-b^$ - `) - b.AssertFileContent("public/p1/index.html", ` -%!% -a^*=x-b^* -%!% - `) -} diff --git a/markup/highlight/highlight_integration_test.go b/markup/highlight/highlight_integration_test.go new file mode 100644 index 000000000..b53b585c0 --- /dev/null +++ b/markup/highlight/highlight_integration_test.go @@ -0,0 +1,117 @@ +// Copyright 2024 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 highlight_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestHighlightInline(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup] +[markup.highlight] +codeFences = true +noClasses = false +-- content/p1.md -- +--- +title: "p1" +--- + +## Inline in Shortcode + +Inline:{{< highlight emacs "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End. +Inline Unknown:{{< highlight foo "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End. + +## Inline in code block + +Not sure if this makes sense, but add a test for it: + +§§§bash {hl_inline=true} +(message "highlight me") +§§§ + +## HighlightCodeBlock in hook + +§§§html +(message "highlight me 2") +§§§ + +## Unknown lexer + +§§§foo {hl_inline=true} +(message "highlight me 3") +§§§ + + +-- layouts/_default/_markup/render-codeblock-html.html -- +{{ $opts := dict "hl_inline" true }} +{{ $result := transform.HighlightCodeBlock . $opts }} +HighlightCodeBlock: Wrapped:{{ $result.Wrapped }}|Inner:{{ $result.Inner }} +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "Inline:(message "this highlight shortcode"):End.", + "Inline Unknown:(message "this highlight shortcode"):End.", + "Not sure if this makes sense, but add a test for it:

    \n(message "highlight me")\n", + "HighlightCodeBlock: Wrapped:(message "highlight me 2")|Inner:(message "highlight me 2")", + "(message "highlight me 3")\n", + ) +} + +// Issue #11311 +func TestIssue11311(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +[markup.highlight] +noClasses = false +-- content/_index.md -- +--- +title: home +--- +§§§go +xəx := 0 +§§§ +-- layouts/index.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` + xəx + `) +} diff --git a/markup/highlight/integration_test.go b/markup/highlight/integration_test.go deleted file mode 100644 index b53b585c0..000000000 --- a/markup/highlight/integration_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2024 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 highlight_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestHighlightInline(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup] -[markup.highlight] -codeFences = true -noClasses = false --- content/p1.md -- ---- -title: "p1" ---- - -## Inline in Shortcode - -Inline:{{< highlight emacs "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End. -Inline Unknown:{{< highlight foo "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End. - -## Inline in code block - -Not sure if this makes sense, but add a test for it: - -§§§bash {hl_inline=true} -(message "highlight me") -§§§ - -## HighlightCodeBlock in hook - -§§§html -(message "highlight me 2") -§§§ - -## Unknown lexer - -§§§foo {hl_inline=true} -(message "highlight me 3") -§§§ - - --- layouts/_default/_markup/render-codeblock-html.html -- -{{ $opts := dict "hl_inline" true }} -{{ $result := transform.HighlightCodeBlock . $opts }} -HighlightCodeBlock: Wrapped:{{ $result.Wrapped }}|Inner:{{ $result.Inner }} --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", - "Inline:(message "this highlight shortcode"):End.", - "Inline Unknown:(message "this highlight shortcode"):End.", - "Not sure if this makes sense, but add a test for it:

    \n(message "highlight me")\n", - "HighlightCodeBlock: Wrapped:(message "highlight me 2")|Inner:(message "highlight me 2")", - "(message "highlight me 3")\n", - ) -} - -// Issue #11311 -func TestIssue11311(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -[markup.highlight] -noClasses = false --- content/_index.md -- ---- -title: home ---- -§§§go -xəx := 0 -§§§ --- layouts/index.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: false, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` - xəx - `) -} diff --git a/markup/tableofcontents/integration_test.go b/markup/tableofcontents/integration_test.go deleted file mode 100644 index 87a7c0108..000000000 --- a/markup/tableofcontents/integration_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2024 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 tableofcontents_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue #10776 -func TestHeadingsLevel(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ['page','rss','section','sitemap','taxonomy','term'] --- layouts/index.html -- -{{ range .Fragments.HeadingsMap }} - {{ printf "%s|%d|%s" .ID .Level .Title }} -{{ end }} --- content/_index.md -- -## Heading L2 -### Heading L3 -##### Heading L5 -` - - b := hugolib.Test(t, files) - b.AssertFileContent("public/index.html", - "heading-l2|2|Heading L2", - "heading-l3|3|Heading L3", - "heading-l5|5|Heading L5", - ) -} diff --git a/markup/tableofcontents/tableofcontents_integration_test.go b/markup/tableofcontents/tableofcontents_integration_test.go new file mode 100644 index 000000000..87a7c0108 --- /dev/null +++ b/markup/tableofcontents/tableofcontents_integration_test.go @@ -0,0 +1,45 @@ +// Copyright 2024 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 tableofcontents_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue #10776 +func TestHeadingsLevel(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +-- layouts/index.html -- +{{ range .Fragments.HeadingsMap }} + {{ printf "%s|%d|%s" .ID .Level .Title }} +{{ end }} +-- content/_index.md -- +## Heading L2 +### Heading L3 +##### Heading L5 +` + + b := hugolib.Test(t, files) + b.AssertFileContent("public/index.html", + "heading-l2|2|Heading L2", + "heading-l3|3|Heading L3", + "heading-l5|5|Heading L5", + ) +} diff --git a/related/integration_test.go b/related/integration_test.go deleted file mode 100644 index 2c71c1d1a..000000000 --- a/related/integration_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2024 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 related_test - -import ( - "fmt" - "math/rand" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestRelatedFragments(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -baseURL = "http://example.com/" -disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"] -[related] - includeNewer = false - threshold = 80 - toLower = false -[[related.indices]] - name = 'pagerefs' - type = 'fragments' - applyFilter = true - weight = 90 -[[related.indices]] - name = 'keywords' - weight = 80 --- content/p1.md -- ---- -title: p1 -pagerefs: ['ref1'] ---- -{{< see-also >}} - -## P1 title - --- content/p2.md -- ---- -title: p2 ---- - -## P2 title 1 - -## P2 title 2 - -## First title {#ref1} -{{< see-also "ref1" >}} --- content/p3.md -- ---- -title: p3 -keywords: ['foo'] ---- - -## P3 title 1 - -## P3 title 2 - -## Common p3, p4, p5 --- content/p4.md -- ---- -title: p4 ---- - -## Common p3, p4, p5 - -## P4 title 1 - --- content/p5.md -- ---- -title: p5 -keywords: ['foo'] ---- - -## P5 title 1 - -## Common p3, p4, p5 - --- layouts/shortcodes/see-also.html -- -{{ $p1 := site.GetPage "p1" }} -{{ $p2 := site.GetPage "p2" }} -{{ $p3 := site.GetPage "p3" }} -P1 Fragments: {{ $p1.Fragments.Identifiers }} -P2 Fragments: {{ $p2.Fragments.Identifiers }} -Contains ref1: {{ $p2.Fragments.Identifiers.Contains "ref1" }} -Count ref1: {{ $p2.Fragments.Identifiers.Count "ref1" }} -{{ $opts := dict "document" .Page "fragments" $.Params }} -{{ $related1 := site.RegularPages.Related $opts }} -{{ $related2 := site.RegularPages.Related $p3 }} -Len Related 1: {{ len $related1 }} -Len Related 2: {{ len $related2 }} -Related 1: {{ template "list-related" $related1 }} -Related 2: {{ template "list-related" $related2 }} - -{{ define "list-related" }}{{ range $i, $e := . }} {{ $i }}: {{ .Title }}: {{ with .HeadingsFiltered}}{{ range $i, $e := .}}h{{ $i }}: {{ .Title }}|{{ .ID }}|{{ end }}{{ end }}::END{{ end }}{{ end }} - --- layouts/_default/single.html -- -Content: {{ .Content }} - - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - expect := ` -P1 Fragments: [p1-title] -P2 Fragments: [p2-title-1 p2-title-2 ref1] -Len Related 1: 1 -Related 2: 2 -` - - for _, p := range []string{"p1", "p2"} { - b.AssertFileContent("public/"+p+"/index.html", expect) - } - - b.AssertFileContent("public/p1/index.html", - "Related 1: 0: p2: h0: First title|ref1|::END", - "Related 2: 0: p5: h0: Common p3, p4, p5|common-p3-p4-p5|::END 1: p4: h0: Common p3, p4, p5|common-p3-p4-p5|::END", - ) -} - -func BenchmarkRelatedSite(b *testing.B) { - files := ` --- config.toml -- -baseURL = "http://example.com/" -disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"] -[related] - includeNewer = false - threshold = 80 - toLower = false -[[related.indices]] - name = 'keywords' - weight = 70 -[[related.indices]] - name = 'pagerefs' - type = 'fragments' - weight = 30 --- layouts/_default/single.html -- -Len related: {{ site.RegularPages.Related . | len }} -` - - createContent := func(n int) string { - base := `--- -title: "Page %d" -keywords: ['k%d'] ---- -` - - for i := 0; i < 32; i++ { - base += fmt.Sprintf("\n## Title %d", rand.Intn(100)) - } - - return fmt.Sprintf(base, n, rand.Intn(32)) - } - - for i := 1; i < 100; i++ { - files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+createContent(i+1), i+1) - } - - cfg := hugolib.IntegrationTestConfig{ - T: b, - TxtarString: files, - } - builders := make([]*hugolib.IntegrationTestBuilder, b.N) - - for i := range builders { - builders[i] = hugolib.NewIntegrationTestBuilder(cfg) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - builders[i].Build() - } -} diff --git a/related/related_integration_test.go b/related/related_integration_test.go new file mode 100644 index 000000000..2c71c1d1a --- /dev/null +++ b/related/related_integration_test.go @@ -0,0 +1,193 @@ +// Copyright 2024 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 related_test + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestRelatedFragments(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "http://example.com/" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"] +[related] + includeNewer = false + threshold = 80 + toLower = false +[[related.indices]] + name = 'pagerefs' + type = 'fragments' + applyFilter = true + weight = 90 +[[related.indices]] + name = 'keywords' + weight = 80 +-- content/p1.md -- +--- +title: p1 +pagerefs: ['ref1'] +--- +{{< see-also >}} + +## P1 title + +-- content/p2.md -- +--- +title: p2 +--- + +## P2 title 1 + +## P2 title 2 + +## First title {#ref1} +{{< see-also "ref1" >}} +-- content/p3.md -- +--- +title: p3 +keywords: ['foo'] +--- + +## P3 title 1 + +## P3 title 2 + +## Common p3, p4, p5 +-- content/p4.md -- +--- +title: p4 +--- + +## Common p3, p4, p5 + +## P4 title 1 + +-- content/p5.md -- +--- +title: p5 +keywords: ['foo'] +--- + +## P5 title 1 + +## Common p3, p4, p5 + +-- layouts/shortcodes/see-also.html -- +{{ $p1 := site.GetPage "p1" }} +{{ $p2 := site.GetPage "p2" }} +{{ $p3 := site.GetPage "p3" }} +P1 Fragments: {{ $p1.Fragments.Identifiers }} +P2 Fragments: {{ $p2.Fragments.Identifiers }} +Contains ref1: {{ $p2.Fragments.Identifiers.Contains "ref1" }} +Count ref1: {{ $p2.Fragments.Identifiers.Count "ref1" }} +{{ $opts := dict "document" .Page "fragments" $.Params }} +{{ $related1 := site.RegularPages.Related $opts }} +{{ $related2 := site.RegularPages.Related $p3 }} +Len Related 1: {{ len $related1 }} +Len Related 2: {{ len $related2 }} +Related 1: {{ template "list-related" $related1 }} +Related 2: {{ template "list-related" $related2 }} + +{{ define "list-related" }}{{ range $i, $e := . }} {{ $i }}: {{ .Title }}: {{ with .HeadingsFiltered}}{{ range $i, $e := .}}h{{ $i }}: {{ .Title }}|{{ .ID }}|{{ end }}{{ end }}::END{{ end }}{{ end }} + +-- layouts/_default/single.html -- +Content: {{ .Content }} + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + expect := ` +P1 Fragments: [p1-title] +P2 Fragments: [p2-title-1 p2-title-2 ref1] +Len Related 1: 1 +Related 2: 2 +` + + for _, p := range []string{"p1", "p2"} { + b.AssertFileContent("public/"+p+"/index.html", expect) + } + + b.AssertFileContent("public/p1/index.html", + "Related 1: 0: p2: h0: First title|ref1|::END", + "Related 2: 0: p5: h0: Common p3, p4, p5|common-p3-p4-p5|::END 1: p4: h0: Common p3, p4, p5|common-p3-p4-p5|::END", + ) +} + +func BenchmarkRelatedSite(b *testing.B) { + files := ` +-- config.toml -- +baseURL = "http://example.com/" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"] +[related] + includeNewer = false + threshold = 80 + toLower = false +[[related.indices]] + name = 'keywords' + weight = 70 +[[related.indices]] + name = 'pagerefs' + type = 'fragments' + weight = 30 +-- layouts/_default/single.html -- +Len related: {{ site.RegularPages.Related . | len }} +` + + createContent := func(n int) string { + base := `--- +title: "Page %d" +keywords: ['k%d'] +--- +` + + for i := 0; i < 32; i++ { + base += fmt.Sprintf("\n## Title %d", rand.Intn(100)) + } + + return fmt.Sprintf(base, n, rand.Intn(32)) + } + + for i := 1; i < 100; i++ { + files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+createContent(i+1), i+1) + } + + cfg := hugolib.IntegrationTestConfig{ + T: b, + TxtarString: files, + } + builders := make([]*hugolib.IntegrationTestBuilder, b.N) + + for i := range builders { + builders[i] = hugolib.NewIntegrationTestBuilder(cfg) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + builders[i].Build() + } +} diff --git a/resources/integration_test.go b/resources/integration_test.go deleted file mode 100644 index 9540b0976..000000000 --- a/resources/integration_test.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2024 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 resources_test - -import ( - "strings" - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 8931 -func TestImageCache(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = "https://example.org" --- content/mybundle/index.md -- ---- -title: "My Bundle" ---- --- content/mybundle/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- content/mybundle/giphy.gif -- -sourcefilename: testdata/giphy.gif --- layouts/foo.html -- --- layouts/index.html -- -{{ $p := site.GetPage "mybundle"}} -{{ $img := $p.Resources.Get "pixel.png" }} -{{ $giphy := $p.Resources.Get "giphy.gif" }} -{{ $gif := $img.Resize "1x2 gif" }} -{{ $bmp := $img.Resize "2x3 bmp" }} -{{ $anigif := $giphy.Resize "4x5" }} - - -gif: {{ $gif.RelPermalink }}|}|{{ $gif.Width }}|{{ $gif.Height }}|{{ $gif.MediaType }}| -bmp: {{ $bmp.RelPermalink }}|}|{{ $bmp.Width }}|{{ $bmp.Height }}|{{ $bmp.MediaType }}| -anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $anigif.MediaType }}| -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - Running: true, - }).Build() - - assertImages := func() { - b.AssertFileContent("public/index.html", ` - gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x2_resize_box_3.gif|}|1|2|image/gif| - bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x3_resize_box_3.bmp|}|2|3|image/bmp| - anigif: /mybundle/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_4x5_resize_box_1.gif|4|5|image/gif| - `) - } - - assertImages() - - b.EditFileReplaceFunc("content/mybundle/index.md", func(s string) string { return strings.ReplaceAll(s, "Bundle", "BUNDLE") }) - b.Build() - - assertImages() -} - -func TestSVGError(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- assets/circle.svg -- - --- layouts/index.html -- -{{ $svg := resources.Get "circle.svg" }} -Width: {{ $svg.Width }} -` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - Running: true, - }).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `error calling Width: this method is only available for raster images. To determine if an image is SVG, you can do {{ if eq .MediaType.SubType "svg" }}{{ end }}`) -} - -// Issue 10255. -func TestNoPublishOfUnusedProcessedImage(t *testing.T) { - t.Parallel() - - workingDir := t.TempDir() - - files := ` --- assets/images/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{ $image := resources.Get "images/pixel.png" }} -{{ $image = $image.Resize "400x" }} -{{ $image = $image.Resize "300x" }} -{{ $image = $image.Resize "200x" }} -{{ $image = $image.Resize "100x" }} -{{ $image = $image.Crop "50x50" }} -{{ $image = $image.Filter (images.GaussianBlur 6) }} -{{ ($image | fingerprint).Permalink }} - - -` - - for i := 0; i < 3; i++ { - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - WorkingDir: workingDir, - }).Build() - - b.AssertFileCount("resources/_gen/images", 6) - b.AssertFileCount("public/images", 1) - b.Build() - } -} - -func TestProcessFilter(t *testing.T) { - t.Parallel() - - files := ` --- assets/images/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{ $pixel := resources.Get "images/pixel.png" }} -{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) (images.Process "jpg") }} -{{ $image := $pixel.Filter $filters }} -jpg|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| -{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) (images.Process "jpg resize 20x30") }} -{{ $image := $pixel.Filter $filters }} -resize 1|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| -{{ $image := $pixel.Filter $filters }} -resize 2|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/index.html", - "jpg|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_17010532266664966692.jpg|MediaType: image/jpeg|Width: 1|Height: 1|", - "resize 1|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|", - "resize 2|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|", - ) -} - -// Issue #11563 -func TestGroupByParamDate(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -disableKinds = ['section','rss','sitemap','taxonomy','term'] --- layouts/index.html -- -{{- range site.RegularPages.GroupByParamDate "eventDate" "2006-01" }} - {{- .Key }}|{{ range .Pages }}{{ .Title }}|{{ end }} -{{- end }} --- content/p1.md -- -+++ -title = 'p1' -eventDate = 2023-09-01 -+++ --- content/p2.md -- -+++ -title = 'p2' -eventDate = '2023-09-01' -+++ --- content/p3.md -- ---- -title: p3 -eventDate: 2023-09-01 ---- --- content/p4.md -- -+++ -title = 'p4' -eventDate = 2023-10-01T08:00:00 -+++ --- content/p5.md -- -+++ -title = 'p5' -eventDate = '2023-10-01T08:00:00' -+++ --- content/p6.md -- ---- -title: p6 -eventDate: 2023-10-01T08:00:00 ---- --- content/p7.md -- -+++ -title = 'p7' -eventDate = 2023-11-01T07:00:00-08:00 -+++ --- content/p8.md -- -+++ -title = 'p8' -eventDate = '2023-11-01T07:00:00-08:00' -+++ --- content/p9.md -- ---- -title: p9 -eventDate: 2023-11-01T07:00:00-08:00 ---- - ` - - b := hugolib.Test(t, files) - - b.AssertFileContent("public/index.html", "2023-11|p9|p8|p7|2023-10|p6|p5|p4|2023-09|p3|p2|p1|") -} diff --git a/resources/page/integration_test.go b/resources/page/integration_test.go deleted file mode 100644 index 0761de249..000000000 --- a/resources/page/integration_test.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2021 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 page_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestGroupByLocalizedDate(t *testing.T) { - - files := ` --- config.toml -- -defaultContentLanguage = 'en' -defaultContentLanguageInSubdir = true -[languages] -[languages.en] -title = 'My blog' -weight = 1 -[languages.fr] -title = 'Mon blogue' -weight = 2 -[languages.nn] -title = 'Bloggen min' -weight = 3 --- content/p1.md -- ---- -title: "Post 1" -date: "2020-01-01" ---- --- content/p2.md -- ---- -title: "Post 2" -date: "2020-02-01" ---- --- content/p1.fr.md -- ---- -title: "Post 1" -date: "2020-01-01" ---- --- content/p2.fr.md -- ---- -title: "Post 2" -date: "2020-02-01" ---- --- layouts/index.html -- -{{ range $k, $v := site.RegularPages.GroupByDate "January, 2006" }}{{ $k }}|{{ $v.Key }}|{{ $v.Pages }}{{ end }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/en/index.html", "0|February, 2020|Pages(1)1|January, 2020|Pages(1)") - b.AssertFileContent("public/fr/index.html", "0|février, 2020|Pages(1)1|janvier, 2020|Pages(1)") -} - -func TestPagesSortCollation(t *testing.T) { - - files := ` --- config.toml -- -defaultContentLanguage = 'en' -defaultContentLanguageInSubdir = true -[languages] -[languages.en] -title = 'My blog' -weight = 1 -[languages.fr] -title = 'Mon blogue' -weight = 2 -[languages.nn] -title = 'Bloggen min' -weight = 3 --- content/p1.md -- ---- -title: "zulu" -date: "2020-01-01" -param1: "xylophone" -tags: ["xylophone", "éclair", "zulu", "emma"] ---- --- content/p2.md -- ---- -title: "émotion" -date: "2020-01-01" -param1: "violin" ---- --- content/p3.md -- ---- -title: "alpha" -date: "2020-01-01" -param1: "éclair" ---- --- layouts/index.html -- -ByTitle: {{ range site.RegularPages.ByTitle }}{{ .Title }}|{{ end }} -ByLinkTitle: {{ range site.RegularPages.ByLinkTitle }}{{ .Title }}|{{ end }} -ByParam: {{ range site.RegularPages.ByParam "param1" }}{{ .Params.param1 }}|{{ end }} -Tags Alphabetical: {{ range site.Taxonomies.tags.Alphabetical }}{{ .Term }}|{{ end }} -GroupBy: {{ range site.RegularPages.GroupBy "Title" }}{{ .Key }}|{{ end }} -{{ with (site.GetPage "p1").Params.tags }} -Sort: {{ sort . }} -ByWeight: {{ range site.RegularPages.ByWeight }}{{ .Title }}|{{ end }} -{{ end }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/en/index.html", ` -ByTitle: alpha|émotion|zulu| -ByLinkTitle: alpha|émotion|zulu| -ByParam: éclair|violin|xylophone -Tags Alphabetical: éclair|emma|xylophone|zulu| -GroupBy: alpha|émotion|zulu| -Sort: [éclair emma xylophone zulu] -ByWeight: alpha|émotion|zulu| -`) -} - -// See #10377 -func TestPermalinkExpansionSectionsRepeated(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ["home", "taxonomy", "taxonomyTerm", "sitemap"] -[outputs] -home = ["HTML"] -page = ["HTML"] -section = ["HTML"] -[outputFormats] -[permalinks] -posts = '/:sections[1]/:sections[last]/:slug' --- content/posts/_index.md -- --- content/posts/a/_index.md -- --- content/posts/a/b/_index.md -- --- content/posts/a/b/c/_index.md -- --- content/posts/a/b/c/d.md -- ---- -title: "D" -slug: "d" ---- -D --- layouts/_default/single.html -- -RelPermalink: {{ .RelPermalink }} - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/a/c/d/index.html", "RelPermalink: /a/c/d/") - -} diff --git a/resources/page/page_integration_test.go b/resources/page/page_integration_test.go new file mode 100644 index 000000000..0761de249 --- /dev/null +++ b/resources/page/page_integration_test.go @@ -0,0 +1,177 @@ +// Copyright 2021 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 page_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestGroupByLocalizedDate(t *testing.T) { + + files := ` +-- config.toml -- +defaultContentLanguage = 'en' +defaultContentLanguageInSubdir = true +[languages] +[languages.en] +title = 'My blog' +weight = 1 +[languages.fr] +title = 'Mon blogue' +weight = 2 +[languages.nn] +title = 'Bloggen min' +weight = 3 +-- content/p1.md -- +--- +title: "Post 1" +date: "2020-01-01" +--- +-- content/p2.md -- +--- +title: "Post 2" +date: "2020-02-01" +--- +-- content/p1.fr.md -- +--- +title: "Post 1" +date: "2020-01-01" +--- +-- content/p2.fr.md -- +--- +title: "Post 2" +date: "2020-02-01" +--- +-- layouts/index.html -- +{{ range $k, $v := site.RegularPages.GroupByDate "January, 2006" }}{{ $k }}|{{ $v.Key }}|{{ $v.Pages }}{{ end }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/en/index.html", "0|February, 2020|Pages(1)1|January, 2020|Pages(1)") + b.AssertFileContent("public/fr/index.html", "0|février, 2020|Pages(1)1|janvier, 2020|Pages(1)") +} + +func TestPagesSortCollation(t *testing.T) { + + files := ` +-- config.toml -- +defaultContentLanguage = 'en' +defaultContentLanguageInSubdir = true +[languages] +[languages.en] +title = 'My blog' +weight = 1 +[languages.fr] +title = 'Mon blogue' +weight = 2 +[languages.nn] +title = 'Bloggen min' +weight = 3 +-- content/p1.md -- +--- +title: "zulu" +date: "2020-01-01" +param1: "xylophone" +tags: ["xylophone", "éclair", "zulu", "emma"] +--- +-- content/p2.md -- +--- +title: "émotion" +date: "2020-01-01" +param1: "violin" +--- +-- content/p3.md -- +--- +title: "alpha" +date: "2020-01-01" +param1: "éclair" +--- +-- layouts/index.html -- +ByTitle: {{ range site.RegularPages.ByTitle }}{{ .Title }}|{{ end }} +ByLinkTitle: {{ range site.RegularPages.ByLinkTitle }}{{ .Title }}|{{ end }} +ByParam: {{ range site.RegularPages.ByParam "param1" }}{{ .Params.param1 }}|{{ end }} +Tags Alphabetical: {{ range site.Taxonomies.tags.Alphabetical }}{{ .Term }}|{{ end }} +GroupBy: {{ range site.RegularPages.GroupBy "Title" }}{{ .Key }}|{{ end }} +{{ with (site.GetPage "p1").Params.tags }} +Sort: {{ sort . }} +ByWeight: {{ range site.RegularPages.ByWeight }}{{ .Title }}|{{ end }} +{{ end }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/en/index.html", ` +ByTitle: alpha|émotion|zulu| +ByLinkTitle: alpha|émotion|zulu| +ByParam: éclair|violin|xylophone +Tags Alphabetical: éclair|emma|xylophone|zulu| +GroupBy: alpha|émotion|zulu| +Sort: [éclair emma xylophone zulu] +ByWeight: alpha|émotion|zulu| +`) +} + +// See #10377 +func TestPermalinkExpansionSectionsRepeated(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ["home", "taxonomy", "taxonomyTerm", "sitemap"] +[outputs] +home = ["HTML"] +page = ["HTML"] +section = ["HTML"] +[outputFormats] +[permalinks] +posts = '/:sections[1]/:sections[last]/:slug' +-- content/posts/_index.md -- +-- content/posts/a/_index.md -- +-- content/posts/a/b/_index.md -- +-- content/posts/a/b/c/_index.md -- +-- content/posts/a/b/c/d.md -- +--- +title: "D" +slug: "d" +--- +D +-- layouts/_default/single.html -- +RelPermalink: {{ .RelPermalink }} + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/a/c/d/index.html", "RelPermalink: /a/c/d/") + +} diff --git a/resources/resource_factories/create/create_integration_test.go b/resources/resource_factories/create/create_integration_test.go new file mode 100644 index 000000000..61bc17adb --- /dev/null +++ b/resources/resource_factories/create/create_integration_test.go @@ -0,0 +1,141 @@ +// Copyright 2024 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 create_test + +import ( + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestGetRemoteHead(t *testing.T) { + files := ` +-- config.toml -- +[security] + [security.http] + methods = ['(?i)GET|POST|HEAD'] + urls = ['.*gohugo\.io.*'] + +-- layouts/index.html -- +{{ $url := "https://gohugo.io/img/hugo.png" }} +{{ $opts := dict "method" "head" }} +{{ with resources.GetRemote $url $opts }} + {{ with .Err }} + {{ errorf "Unable to get remote resource: %s" . }} + {{ else }} + Head Content: {{ .Content }}. Head Data: {{ .Data }} + {{ end }} +{{ else }} + {{ errorf "Unable to get remote resource: %s" $url }} +{{ end }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + + b.Build() + + b.AssertFileContent("public/index.html", + "Head Content: .", + "Head Data: map[ContentLength:18210 ContentType:image/png Status:200 OK StatusCode:200 TransferEncoding:[]]", + ) +} + +func TestGetRemoteRetry(t *testing.T) { + t.Parallel() + + temporaryHTTPCodes := []int{408, 429, 500, 502, 503, 504} + numPages := 20 + + handler := func(w http.ResponseWriter, r *http.Request) { + if rand.Intn(3) == 0 { + w.WriteHeader(temporaryHTTPCodes[rand.Intn(len(temporaryHTTPCodes))]) + return + } + w.Header().Add("Content-Type", "text/plain") + w.Write([]byte("Response for " + r.URL.Path + ".")) + } + + srv := httptest.NewServer(http.HandlerFunc(handler)) + t.Cleanup(func() { srv.Close() }) + + filesTemplate := ` +-- hugo.toml -- +disableKinds = ["home", "taxonomy", "term"] +timeout = "TIMEOUT" +[security] +[security.http] +urls = ['.*'] +mediaTypes = ['text/plain'] +-- layouts/_default/single.html -- +{{ $url := printf "%s%s" "URL" .RelPermalink}} +{{ $opts := dict }} +{{ with resources.GetRemote $url $opts }} + {{ with .Err }} + {{ errorf "Got Err: %s. Data: %v" . .Data }} + {{ else }} + Content: {{ .Content }} + {{ end }} +{{ else }} + {{ errorf "Unable to get remote resource: %s" $url }} +{{ end }} +` + + for i := 0; i < numPages; i++ { + filesTemplate += fmt.Sprintf("-- content/post/p%d.md --\n", i) + } + + filesTemplate = strings.ReplaceAll(filesTemplate, "URL", srv.URL) + + t.Run("OK", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "TIMEOUT", "60s") + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + + b.Build() + + for i := 0; i < numPages; i++ { + b.AssertFileContent(fmt.Sprintf("public/post/p%d/index.html", i), fmt.Sprintf("Content: Response for /post/p%d/.", i)) + } + }) + + t.Run("Timeout", func(t *testing.T) { + files := strings.ReplaceAll(filesTemplate, "TIMEOUT", "100ms") + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + // This is hard to get stable on GitHub Actions, it sometimes succeeds due to timing issues. + if err != nil { + b.AssertLogContains("Got Err") + b.AssertLogContains("Retry timeout") + b.AssertLogContains("ContentLength:0") + } + }) +} diff --git a/resources/resource_factories/create/integration_test.go b/resources/resource_factories/create/integration_test.go deleted file mode 100644 index 61bc17adb..000000000 --- a/resources/resource_factories/create/integration_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2024 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 create_test - -import ( - "fmt" - "math/rand" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestGetRemoteHead(t *testing.T) { - files := ` --- config.toml -- -[security] - [security.http] - methods = ['(?i)GET|POST|HEAD'] - urls = ['.*gohugo\.io.*'] - --- layouts/index.html -- -{{ $url := "https://gohugo.io/img/hugo.png" }} -{{ $opts := dict "method" "head" }} -{{ with resources.GetRemote $url $opts }} - {{ with .Err }} - {{ errorf "Unable to get remote resource: %s" . }} - {{ else }} - Head Content: {{ .Content }}. Head Data: {{ .Data }} - {{ end }} -{{ else }} - {{ errorf "Unable to get remote resource: %s" $url }} -{{ end }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - - b.Build() - - b.AssertFileContent("public/index.html", - "Head Content: .", - "Head Data: map[ContentLength:18210 ContentType:image/png Status:200 OK StatusCode:200 TransferEncoding:[]]", - ) -} - -func TestGetRemoteRetry(t *testing.T) { - t.Parallel() - - temporaryHTTPCodes := []int{408, 429, 500, 502, 503, 504} - numPages := 20 - - handler := func(w http.ResponseWriter, r *http.Request) { - if rand.Intn(3) == 0 { - w.WriteHeader(temporaryHTTPCodes[rand.Intn(len(temporaryHTTPCodes))]) - return - } - w.Header().Add("Content-Type", "text/plain") - w.Write([]byte("Response for " + r.URL.Path + ".")) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - t.Cleanup(func() { srv.Close() }) - - filesTemplate := ` --- hugo.toml -- -disableKinds = ["home", "taxonomy", "term"] -timeout = "TIMEOUT" -[security] -[security.http] -urls = ['.*'] -mediaTypes = ['text/plain'] --- layouts/_default/single.html -- -{{ $url := printf "%s%s" "URL" .RelPermalink}} -{{ $opts := dict }} -{{ with resources.GetRemote $url $opts }} - {{ with .Err }} - {{ errorf "Got Err: %s. Data: %v" . .Data }} - {{ else }} - Content: {{ .Content }} - {{ end }} -{{ else }} - {{ errorf "Unable to get remote resource: %s" $url }} -{{ end }} -` - - for i := 0; i < numPages; i++ { - filesTemplate += fmt.Sprintf("-- content/post/p%d.md --\n", i) - } - - filesTemplate = strings.ReplaceAll(filesTemplate, "URL", srv.URL) - - t.Run("OK", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "TIMEOUT", "60s") - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - - b.Build() - - for i := 0; i < numPages; i++ { - b.AssertFileContent(fmt.Sprintf("public/post/p%d/index.html", i), fmt.Sprintf("Content: Response for /post/p%d/.", i)) - } - }) - - t.Run("Timeout", func(t *testing.T) { - files := strings.ReplaceAll(filesTemplate, "TIMEOUT", "100ms") - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - // This is hard to get stable on GitHub Actions, it sometimes succeeds due to timing issues. - if err != nil { - b.AssertLogContains("Got Err") - b.AssertLogContains("Retry timeout") - b.AssertLogContains("ContentLength:0") - } - }) -} diff --git a/resources/resource_transformers/babel/babel_integration_test.go b/resources/resource_transformers/babel/babel_integration_test.go new file mode 100644 index 000000000..44a13f103 --- /dev/null +++ b/resources/resource_transformers/babel/babel_integration_test.go @@ -0,0 +1,93 @@ +// Copyright 2021 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 babel_test + +import ( + "testing" + + "github.com/bep/logg" + "github.com/gohugoio/hugo/htesting" + "github.com/gohugoio/hugo/hugolib" +) + +func TestTransformBabel(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + files := ` +-- assets/js/main.js -- +/* A Car */ +class Car { + constructor(brand) { + this.carname = brand; + } +} +-- assets/js/main2.js -- +/* A Car2 */ +class Car2 { + constructor(brand) { + this.carname = brand; + } +} +-- babel.config.js -- +console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT ); + +module.exports = { + presets: ["@babel/preset-env"], +}; +-- config.toml -- +disablekinds = ['taxonomy', 'term', 'page'] +[security] + [security.exec] + allow = ['^npx$', '^babel$'] +-- layouts/index.html -- +{{ $options := dict "noComments" true }} +{{ $transpiled := resources.Get "js/main.js" | babel -}} +Transpiled: {{ $transpiled.Content | safeJS }} + +{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "inline") -}} +Transpiled2: {{ $transpiled.Content | safeJS }} + +{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "external") -}} +Transpiled3: {{ $transpiled.Permalink }} +-- package.json -- +{ + "scripts": {}, + + "devDependencies": { + "@babel/cli": "7.8.4", + "@babel/core": "7.9.0", + "@babel/preset-env": "7.9.5" + } +} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: logg.LevelInfo, + }).Build() + + b.AssertLogContains("babel: Hugo Environment: production") + b.AssertFileContent("public/index.html", `var Car2 =`) + b.AssertFileContent("public/js/main2.js", `var Car2 =`) + b.AssertFileContent("public/js/main2.js.map", `{"version":3,`) + b.AssertFileContent("public/index.html", ` +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozL`) +} diff --git a/resources/resource_transformers/babel/integration_test.go b/resources/resource_transformers/babel/integration_test.go deleted file mode 100644 index 44a13f103..000000000 --- a/resources/resource_transformers/babel/integration_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2021 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 babel_test - -import ( - "testing" - - "github.com/bep/logg" - "github.com/gohugoio/hugo/htesting" - "github.com/gohugoio/hugo/hugolib" -) - -func TestTransformBabel(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - files := ` --- assets/js/main.js -- -/* A Car */ -class Car { - constructor(brand) { - this.carname = brand; - } -} --- assets/js/main2.js -- -/* A Car2 */ -class Car2 { - constructor(brand) { - this.carname = brand; - } -} --- babel.config.js -- -console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT ); - -module.exports = { - presets: ["@babel/preset-env"], -}; --- config.toml -- -disablekinds = ['taxonomy', 'term', 'page'] -[security] - [security.exec] - allow = ['^npx$', '^babel$'] --- layouts/index.html -- -{{ $options := dict "noComments" true }} -{{ $transpiled := resources.Get "js/main.js" | babel -}} -Transpiled: {{ $transpiled.Content | safeJS }} - -{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "inline") -}} -Transpiled2: {{ $transpiled.Content | safeJS }} - -{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "external") -}} -Transpiled3: {{ $transpiled.Permalink }} --- package.json -- -{ - "scripts": {}, - - "devDependencies": { - "@babel/cli": "7.8.4", - "@babel/core": "7.9.0", - "@babel/preset-env": "7.9.5" - } -} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - NeedsNpmInstall: true, - LogLevel: logg.LevelInfo, - }).Build() - - b.AssertLogContains("babel: Hugo Environment: production") - b.AssertFileContent("public/index.html", `var Car2 =`) - b.AssertFileContent("public/js/main2.js", `var Car2 =`) - b.AssertFileContent("public/js/main2.js.map", `{"version":3,`) - b.AssertFileContent("public/index.html", ` -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozL`) -} diff --git a/resources/resource_transformers/js/integration_test.go b/resources/resource_transformers/js/integration_test.go deleted file mode 100644 index 304c51d33..000000000 --- a/resources/resource_transformers/js/integration_test.go +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2021 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 js_test - -import ( - "path/filepath" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/htesting" - "github.com/gohugoio/hugo/hugolib" -) - -func TestBuildVariants(t *testing.T) { - c := qt.New(t) - - mainWithImport := ` --- config.toml -- -disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] -disableLiveReload = true --- assets/js/main.js -- -import { hello1, hello2 } from './util1'; -hello1(); -hello2(); --- assets/js/util1.js -- -import { hello3 } from './util2'; -export function hello1() { - return 'abcd'; -} -export function hello2() { - return hello3(); -} --- assets/js/util2.js -- -export function hello3() { - return 'efgh'; -} --- layouts/index.html -- -{{ $js := resources.Get "js/main.js" | js.Build }} -JS Content:{{ $js.Content }}:End: - - ` - - c.Run("Basic", func(c *qt.C) { - b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: mainWithImport}).Build() - - b.AssertFileContent("public/index.html", `abcd`) - }) - - c.Run("Edit Import", func(c *qt.C) { - b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, Running: true, NeedsOsFS: true, TxtarString: mainWithImport}).Build() - - b.AssertFileContent("public/index.html", `abcd`) - b.EditFileReplaceFunc("assets/js/util1.js", func(s string) string { return strings.ReplaceAll(s, "abcd", "1234") }).Build() - b.AssertFileContent("public/index.html", `1234`) - }) - - c.Run("Edit Import Nested", func(c *qt.C) { - b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, Running: true, NeedsOsFS: true, TxtarString: mainWithImport}).Build() - - b.AssertFileContent("public/index.html", `efgh`) - b.EditFileReplaceFunc("assets/js/util2.js", func(s string) string { return strings.ReplaceAll(s, "efgh", "1234") }).Build() - b.AssertFileContent("public/index.html", `1234`) - }) -} - -func TestBuildWithModAndNpm(t *testing.T) { - if !htesting.IsCI() { - t.Skip("skip (relative) long running modules test when running locally") - } - - c := qt.New(t) - - files := ` --- config.toml -- -baseURL = "https://example.org" -disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] -[module] -[[module.imports]] -path="github.com/gohugoio/hugoTestProjectJSModImports" --- go.mod -- -module github.com/gohugoio/tests/testHugoModules - -go 1.16 - -require github.com/gohugoio/hugoTestProjectJSModImports v0.10.0 // indirect --- package.json -- -{ - "dependencies": { - "date-fns": "^2.16.1" - } -} - -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - TxtarString: files, - Verbose: true, - }).Build() - - b.AssertFileContent("public/js/main.js", ` -greeting: "greeting configured in mod2" -Hello1 from mod1: $ -return "Hello2 from mod1"; -var Hugo = "Rocks!"; -Hello3 from mod2. Date from date-fns: ${today} -Hello from lib in the main project -Hello5 from mod2. -var myparam = "Hugo Rocks!"; -shim cwd -`) - - // React JSX, verify the shimming. - b.AssertFileContent("public/js/like.js", filepath.FromSlash(`@v0.10.0/assets/js/shims/react.js -module.exports = window.ReactDOM; -`)) -} - -func TestBuildWithNpm(t *testing.T) { - if !htesting.IsCI() { - t.Skip("skip (relative) long running modules test when running locally") - } - - c := qt.New(t) - - files := ` --- assets/js/included.js -- -console.log("included"); --- assets/js/main.js -- -import "./included"; - import { toCamelCase } from "to-camel-case"; - - console.log("main"); - console.log("To camel:", toCamelCase("space case")); --- assets/js/myjsx.jsx -- -import * as React from 'react' -import * as ReactDOM from 'react-dom' - - ReactDOM.render( -

    Hello, world!

    , - document.getElementById('root') - ); --- assets/js/myts.ts -- -function greeter(person: string) { - return "Hello, " + person; -} -let user = [0, 1, 2]; -document.body.textContent = greeter(user); --- config.toml -- -disablekinds = ['taxonomy', 'term', 'page'] --- content/p1.md -- -Content. --- data/hugo.toml -- -slogan = "Hugo Rocks!" --- i18n/en.yaml -- -hello: - other: "Hello" --- i18n/fr.yaml -- -hello: - other: "Bonjour" --- layouts/index.html -- -{{ $options := dict "minify" false "externals" (slice "react" "react-dom") }} -{{ $js := resources.Get "js/main.js" | js.Build $options }} -JS: {{ template "print" $js }} -{{ $jsx := resources.Get "js/myjsx.jsx" | js.Build $options }} -JSX: {{ template "print" $jsx }} -{{ $ts := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "inline")}} -TS: {{ template "print" $ts }} -{{ $ts2 := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "external" "TargetPath" "js/myts2.js")}} -TS2: {{ template "print" $ts2 }} -{{ define "print" }}RelPermalink: {{.RelPermalink}}|MIME: {{ .MediaType }}|Content: {{ .Content | safeJS }}{{ end }} --- package.json -- -{ - "scripts": {}, - - "dependencies": { - "to-camel-case": "1.0.0" - } -} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/js/myts.js", `//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJz`) - b.AssertFileContent("public/js/myts2.js.map", `"version": 3,`) - b.AssertFileContent("public/index.html", ` - console.log("included"); - if (hasSpace.test(string)) - var React = __toESM(__require("react")); - function greeter(person) { -`) -} - -func TestBuildError(t *testing.T) { - c := qt.New(t) - - filesTemplate := ` --- config.toml -- -disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] --- assets/js/main.js -- -// A comment. -import { hello1, hello2 } from './util1'; -hello1(); -hello2(); --- assets/js/util1.js -- -/* Some -comments. -*/ -import { hello3 } from './util2'; -export function hello1() { - return 'abcd'; -} -export function hello2() { - return hello3(); -} --- assets/js/util2.js -- -export function hello3() { - return 'efgh'; -} --- layouts/index.html -- -{{ $js := resources.Get "js/main.js" | js.Build }} -JS Content:{{ $js.Content }}:End: - - ` - - c.Run("Import from main not found", func(c *qt.C) { - c.Parallel() - files := strings.Replace(filesTemplate, "import { hello1, hello2 }", "import { hello1, hello2, FOOBAR }", 1) - b, err := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: files}).BuildE() - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `main.js:2:25": No matching export`) - }) - - c.Run("Import from import not found", func(c *qt.C) { - c.Parallel() - files := strings.Replace(filesTemplate, "import { hello3 } from './util2';", "import { hello3, FOOBAR } from './util2';", 1) - b, err := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: files}).BuildE() - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `util1.js:4:17": No matching export in`) - }) -} - -// See issue 10527. -func TestImportHugoVsESBuild(t *testing.T) { - c := qt.New(t) - - for _, importSrcDir := range []string{"node_modules", "assets"} { - c.Run(importSrcDir, func(c *qt.C) { - files := ` --- IMPORT_SRC_DIR/imp1/index.js -- -console.log("IMPORT_SRC_DIR:imp1/index.js"); --- IMPORT_SRC_DIR/imp2/index.ts -- -console.log("IMPORT_SRC_DIR:imp2/index.ts"); --- IMPORT_SRC_DIR/imp3/foo.ts -- -console.log("IMPORT_SRC_DIR:imp3/foo.ts"); --- assets/js/main.js -- -import 'imp1/index.js'; -import 'imp2/index.js'; -import 'imp3/foo.js'; --- layouts/index.html -- -{{ $js := resources.Get "js/main.js" | js.Build }} -{{ $js.RelPermalink }} - ` - - files = strings.ReplaceAll(files, "IMPORT_SRC_DIR", importSrcDir) - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - TxtarString: files, - }).Build() - - expected := ` -IMPORT_SRC_DIR:imp1/index.js -IMPORT_SRC_DIR:imp2/index.ts -IMPORT_SRC_DIR:imp3/foo.ts -` - expected = strings.ReplaceAll(expected, "IMPORT_SRC_DIR", importSrcDir) - - b.AssertFileContent("public/js/main.js", expected) - }) - } -} - -// See https://github.com/evanw/esbuild/issues/2745 -func TestPreserveLegalComments(t *testing.T) { - t.Parallel() - - files := ` --- assets/js/main.js -- -/* @license - * Main license. - */ -import * as foo from 'js/utils'; -console.log("Hello Main"); --- assets/js/utils/index.js -- -export * from './util1'; -export * from './util2'; --- assets/js/utils/util1.js -- -/*! License util1 */ -console.log("Hello 1"); --- assets/js/utils/util2.js -- -//! License util2 */ -console.log("Hello 2"); --- layouts/index.html -- -{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" false) }} -{{ $js.RelPermalink }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - NeedsOsFS: true, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/js/main.js", ` -License util1 -License util2 -Main license - - `) -} - -// Issue #11232 -func TestTypeScriptExperimentalDecorators(t *testing.T) { - t.Parallel() - files := ` --- hugo.toml -- -disableKinds = ['RSS','sitemap','taxonomy','term'] --- tsconfig.json -- -{ - "compilerOptions": { - "experimentalDecorators": true, - } -} --- assets/ts/main.ts -- -function addFoo(target: any) {target.prototype.foo = 'bar'} -@addFoo -class A {} --- layouts/index.html -- -{{ $opts := dict "target" "es2020" "targetPath" "js/main.js" }} -{{ (resources.Get "ts/main.ts" | js.Build $opts).Publish }} -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - NeedsOsFS: true, - TxtarString: files, - }).Build() - b.AssertFileContent("public/js/main.js", "__decorateClass") -} diff --git a/resources/resource_transformers/js/js_integration_test.go b/resources/resource_transformers/js/js_integration_test.go new file mode 100644 index 000000000..304c51d33 --- /dev/null +++ b/resources/resource_transformers/js/js_integration_test.go @@ -0,0 +1,373 @@ +// Copyright 2021 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 js_test + +import ( + "path/filepath" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/htesting" + "github.com/gohugoio/hugo/hugolib" +) + +func TestBuildVariants(t *testing.T) { + c := qt.New(t) + + mainWithImport := ` +-- config.toml -- +disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] +disableLiveReload = true +-- assets/js/main.js -- +import { hello1, hello2 } from './util1'; +hello1(); +hello2(); +-- assets/js/util1.js -- +import { hello3 } from './util2'; +export function hello1() { + return 'abcd'; +} +export function hello2() { + return hello3(); +} +-- assets/js/util2.js -- +export function hello3() { + return 'efgh'; +} +-- layouts/index.html -- +{{ $js := resources.Get "js/main.js" | js.Build }} +JS Content:{{ $js.Content }}:End: + + ` + + c.Run("Basic", func(c *qt.C) { + b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: mainWithImport}).Build() + + b.AssertFileContent("public/index.html", `abcd`) + }) + + c.Run("Edit Import", func(c *qt.C) { + b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, Running: true, NeedsOsFS: true, TxtarString: mainWithImport}).Build() + + b.AssertFileContent("public/index.html", `abcd`) + b.EditFileReplaceFunc("assets/js/util1.js", func(s string) string { return strings.ReplaceAll(s, "abcd", "1234") }).Build() + b.AssertFileContent("public/index.html", `1234`) + }) + + c.Run("Edit Import Nested", func(c *qt.C) { + b := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, Running: true, NeedsOsFS: true, TxtarString: mainWithImport}).Build() + + b.AssertFileContent("public/index.html", `efgh`) + b.EditFileReplaceFunc("assets/js/util2.js", func(s string) string { return strings.ReplaceAll(s, "efgh", "1234") }).Build() + b.AssertFileContent("public/index.html", `1234`) + }) +} + +func TestBuildWithModAndNpm(t *testing.T) { + if !htesting.IsCI() { + t.Skip("skip (relative) long running modules test when running locally") + } + + c := qt.New(t) + + files := ` +-- config.toml -- +baseURL = "https://example.org" +disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] +[module] +[[module.imports]] +path="github.com/gohugoio/hugoTestProjectJSModImports" +-- go.mod -- +module github.com/gohugoio/tests/testHugoModules + +go 1.16 + +require github.com/gohugoio/hugoTestProjectJSModImports v0.10.0 // indirect +-- package.json -- +{ + "dependencies": { + "date-fns": "^2.16.1" + } +} + +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + TxtarString: files, + Verbose: true, + }).Build() + + b.AssertFileContent("public/js/main.js", ` +greeting: "greeting configured in mod2" +Hello1 from mod1: $ +return "Hello2 from mod1"; +var Hugo = "Rocks!"; +Hello3 from mod2. Date from date-fns: ${today} +Hello from lib in the main project +Hello5 from mod2. +var myparam = "Hugo Rocks!"; +shim cwd +`) + + // React JSX, verify the shimming. + b.AssertFileContent("public/js/like.js", filepath.FromSlash(`@v0.10.0/assets/js/shims/react.js +module.exports = window.ReactDOM; +`)) +} + +func TestBuildWithNpm(t *testing.T) { + if !htesting.IsCI() { + t.Skip("skip (relative) long running modules test when running locally") + } + + c := qt.New(t) + + files := ` +-- assets/js/included.js -- +console.log("included"); +-- assets/js/main.js -- +import "./included"; + import { toCamelCase } from "to-camel-case"; + + console.log("main"); + console.log("To camel:", toCamelCase("space case")); +-- assets/js/myjsx.jsx -- +import * as React from 'react' +import * as ReactDOM from 'react-dom' + + ReactDOM.render( +

    Hello, world!

    , + document.getElementById('root') + ); +-- assets/js/myts.ts -- +function greeter(person: string) { + return "Hello, " + person; +} +let user = [0, 1, 2]; +document.body.textContent = greeter(user); +-- config.toml -- +disablekinds = ['taxonomy', 'term', 'page'] +-- content/p1.md -- +Content. +-- data/hugo.toml -- +slogan = "Hugo Rocks!" +-- i18n/en.yaml -- +hello: + other: "Hello" +-- i18n/fr.yaml -- +hello: + other: "Bonjour" +-- layouts/index.html -- +{{ $options := dict "minify" false "externals" (slice "react" "react-dom") }} +{{ $js := resources.Get "js/main.js" | js.Build $options }} +JS: {{ template "print" $js }} +{{ $jsx := resources.Get "js/myjsx.jsx" | js.Build $options }} +JSX: {{ template "print" $jsx }} +{{ $ts := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "inline")}} +TS: {{ template "print" $ts }} +{{ $ts2 := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "external" "TargetPath" "js/myts2.js")}} +TS2: {{ template "print" $ts2 }} +{{ define "print" }}RelPermalink: {{.RelPermalink}}|MIME: {{ .MediaType }}|Content: {{ .Content | safeJS }}{{ end }} +-- package.json -- +{ + "scripts": {}, + + "dependencies": { + "to-camel-case": "1.0.0" + } +} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/js/myts.js", `//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJz`) + b.AssertFileContent("public/js/myts2.js.map", `"version": 3,`) + b.AssertFileContent("public/index.html", ` + console.log("included"); + if (hasSpace.test(string)) + var React = __toESM(__require("react")); + function greeter(person) { +`) +} + +func TestBuildError(t *testing.T) { + c := qt.New(t) + + filesTemplate := ` +-- config.toml -- +disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"] +-- assets/js/main.js -- +// A comment. +import { hello1, hello2 } from './util1'; +hello1(); +hello2(); +-- assets/js/util1.js -- +/* Some +comments. +*/ +import { hello3 } from './util2'; +export function hello1() { + return 'abcd'; +} +export function hello2() { + return hello3(); +} +-- assets/js/util2.js -- +export function hello3() { + return 'efgh'; +} +-- layouts/index.html -- +{{ $js := resources.Get "js/main.js" | js.Build }} +JS Content:{{ $js.Content }}:End: + + ` + + c.Run("Import from main not found", func(c *qt.C) { + c.Parallel() + files := strings.Replace(filesTemplate, "import { hello1, hello2 }", "import { hello1, hello2, FOOBAR }", 1) + b, err := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: files}).BuildE() + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `main.js:2:25": No matching export`) + }) + + c.Run("Import from import not found", func(c *qt.C) { + c.Parallel() + files := strings.Replace(filesTemplate, "import { hello3 } from './util2';", "import { hello3, FOOBAR } from './util2';", 1) + b, err := hugolib.NewIntegrationTestBuilder(hugolib.IntegrationTestConfig{T: c, NeedsOsFS: true, TxtarString: files}).BuildE() + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `util1.js:4:17": No matching export in`) + }) +} + +// See issue 10527. +func TestImportHugoVsESBuild(t *testing.T) { + c := qt.New(t) + + for _, importSrcDir := range []string{"node_modules", "assets"} { + c.Run(importSrcDir, func(c *qt.C) { + files := ` +-- IMPORT_SRC_DIR/imp1/index.js -- +console.log("IMPORT_SRC_DIR:imp1/index.js"); +-- IMPORT_SRC_DIR/imp2/index.ts -- +console.log("IMPORT_SRC_DIR:imp2/index.ts"); +-- IMPORT_SRC_DIR/imp3/foo.ts -- +console.log("IMPORT_SRC_DIR:imp3/foo.ts"); +-- assets/js/main.js -- +import 'imp1/index.js'; +import 'imp2/index.js'; +import 'imp3/foo.js'; +-- layouts/index.html -- +{{ $js := resources.Get "js/main.js" | js.Build }} +{{ $js.RelPermalink }} + ` + + files = strings.ReplaceAll(files, "IMPORT_SRC_DIR", importSrcDir) + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + TxtarString: files, + }).Build() + + expected := ` +IMPORT_SRC_DIR:imp1/index.js +IMPORT_SRC_DIR:imp2/index.ts +IMPORT_SRC_DIR:imp3/foo.ts +` + expected = strings.ReplaceAll(expected, "IMPORT_SRC_DIR", importSrcDir) + + b.AssertFileContent("public/js/main.js", expected) + }) + } +} + +// See https://github.com/evanw/esbuild/issues/2745 +func TestPreserveLegalComments(t *testing.T) { + t.Parallel() + + files := ` +-- assets/js/main.js -- +/* @license + * Main license. + */ +import * as foo from 'js/utils'; +console.log("Hello Main"); +-- assets/js/utils/index.js -- +export * from './util1'; +export * from './util2'; +-- assets/js/utils/util1.js -- +/*! License util1 */ +console.log("Hello 1"); +-- assets/js/utils/util2.js -- +//! License util2 */ +console.log("Hello 2"); +-- layouts/index.html -- +{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" false) }} +{{ $js.RelPermalink }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + NeedsOsFS: true, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/js/main.js", ` +License util1 +License util2 +Main license + + `) +} + +// Issue #11232 +func TestTypeScriptExperimentalDecorators(t *testing.T) { + t.Parallel() + files := ` +-- hugo.toml -- +disableKinds = ['RSS','sitemap','taxonomy','term'] +-- tsconfig.json -- +{ + "compilerOptions": { + "experimentalDecorators": true, + } +} +-- assets/ts/main.ts -- +function addFoo(target: any) {target.prototype.foo = 'bar'} +@addFoo +class A {} +-- layouts/index.html -- +{{ $opts := dict "target" "es2020" "targetPath" "js/main.js" }} +{{ (resources.Get "ts/main.ts" | js.Build $opts).Publish }} +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + NeedsOsFS: true, + TxtarString: files, + }).Build() + b.AssertFileContent("public/js/main.js", "__decorateClass") +} diff --git a/resources/resource_transformers/minifier/integration_test.go b/resources/resource_transformers/minifier/integration_test.go deleted file mode 100644 index fb4cc7a65..000000000 --- a/resources/resource_transformers/minifier/integration_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 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 minifier_test - -import ( - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 8954 -func TestTransformMinify(t *testing.T) { - c := qt.New(t) - - files := ` --- assets/js/test.js -- -new Date(2002, 04, 11) --- config.toml -- --- layouts/index.html -- -{{ $js := resources.Get "js/test.js" | minify }} - -` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: files, - }, - ).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err, qt.ErrorMatches, "(?s).*legacy octal numbers.*line 1.*") -} diff --git a/resources/resource_transformers/minifier/minifier_integration_test.go b/resources/resource_transformers/minifier/minifier_integration_test.go new file mode 100644 index 000000000..fb4cc7a65 --- /dev/null +++ b/resources/resource_transformers/minifier/minifier_integration_test.go @@ -0,0 +1,47 @@ +// Copyright 2021 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 minifier_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 8954 +func TestTransformMinify(t *testing.T) { + c := qt.New(t) + + files := ` +-- assets/js/test.js -- +new Date(2002, 04, 11) +-- config.toml -- +-- layouts/index.html -- +{{ $js := resources.Get "js/test.js" | minify }} + +` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err, qt.ErrorMatches, "(?s).*legacy octal numbers.*line 1.*") +} diff --git a/resources/resource_transformers/postcss/integration_test.go b/resources/resource_transformers/postcss/integration_test.go deleted file mode 100644 index 957e69403..000000000 --- a/resources/resource_transformers/postcss/integration_test.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2021 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 postcss_test - -import ( - "fmt" - "path/filepath" - "runtime" - "strings" - "testing" - - "github.com/bep/logg" - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/htesting" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/hugolib" -) - -const postCSSIntegrationTestFiles = ` --- assets/css/components/a.css -- -/* A comment. */ -/* Another comment. */ -class-in-a { - color: blue; -} - --- assets/css/components/all.css -- -@import "a.css"; -@import "b.css"; --- assets/css/components/b.css -- -@import "a.css"; - -class-in-b { - color: blue; -} - --- assets/css/styles.css -- -@tailwind base; -@tailwind components; -@tailwind utilities; - @import "components/all.css"; -h1 { - @apply text-2xl font-bold; -} - --- config.toml -- -disablekinds = ['taxonomy', 'term', 'page'] -baseURL = "https://example.com" -[build] -useResourceCacheWhen = 'never' --- content/p1.md -- --- data/hugo.toml -- -slogan = "Hugo Rocks!" --- i18n/en.yaml -- -hello: - other: "Hello" --- i18n/fr.yaml -- -hello: - other: "Bonjour" --- layouts/index.html -- -{{ $options := dict "inlineImports" true }} -{{ $styles := resources.Get "css/styles.css" | resources.PostCSS $options }} -Styles RelPermalink: {{ $styles.RelPermalink }} -{{ $cssContent := $styles.Content }} -Styles Content: Len: {{ len $styles.Content }}| --- package.json -- -{ - "scripts": {}, - - "devDependencies": { - "postcss-cli": "7.1.0", - "tailwindcss": "1.2.0" - } -} --- postcss.config.js -- -console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT ); -console.error("Hugo PublishDir:", process.env.HUGO_PUBLISHDIR ); -// https://github.com/gohugoio/hugo/issues/7656 -console.error("package.json:", process.env.HUGO_FILE_PACKAGE_JSON ); -console.error("PostCSS Config File:", process.env.HUGO_FILE_POSTCSS_CONFIG_JS ); - -module.exports = { - plugins: [ - require('tailwindcss') - ] -} - -` - -func TestTransformPostCSS(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - c := qt.New(t) - tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test") - c.Assert(err, qt.IsNil) - c.Cleanup(clean) - - for _, s := range []string{"never", "always"} { - - repl := strings.NewReplacer( - "https://example.com", - "https://example.com/foo", - "useResourceCacheWhen = 'never'", - fmt.Sprintf("useResourceCacheWhen = '%s'", s), - ) - - files := repl.Replace(postCSSIntegrationTestFiles) - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - LogLevel: logg.LevelInfo, - WorkingDir: tempDir, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/index.html", ` -Styles RelPermalink: /foo/css/styles.css -Styles Content: Len: 770917| -`) - - if s == "never" { - b.AssertLogContains("Hugo Environment: production") - b.AssertLogContains("Hugo PublishDir: " + filepath.Join(tempDir, "public")) - } - } -} - -// 9880 -func TestTransformPostCSSError(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - if runtime.GOOS == "windows" { - // TODO(bep) This has started to fail on Windows with Go 1.19 on GitHub Actions for some mysterious reason. - t.Skip("Skip on Windows") - } - - c := qt.New(t) - - s, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - TxtarString: strings.ReplaceAll(postCSSIntegrationTestFiles, "color: blue;", "@apply foo;"), // Syntax error - }).BuildE() - - s.AssertIsFileError(err) - c.Assert(err.Error(), qt.Contains, "a.css:4:2") -} - -func TestTransformPostCSSNotInstalledError(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - c := qt.New(t) - - s, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - TxtarString: postCSSIntegrationTestFiles, - }).BuildE() - - s.AssertIsFileError(err) - c.Assert(err.Error(), qt.Contains, `binary with name "npx" not found`) -} - -// #9895 -func TestTransformPostCSSImportError(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - c := qt.New(t) - - s, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - LogLevel: logg.LevelInfo, - TxtarString: strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`), - }).BuildE() - - s.AssertIsFileError(err) - c.Assert(err.Error(), qt.Contains, "styles.css:4:3") - c.Assert(err.Error(), qt.Contains, filepath.FromSlash(`failed to resolve CSS @import "/css/components/doesnotexist.css"`)) -} - -func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - c := qt.New(t) - - files := strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`) - files = strings.ReplaceAll(files, `{{ $options := dict "inlineImports" true }}`, `{{ $options := dict "inlineImports" true "skipInlineImportsNotFound" true }}`) - - s := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - LogLevel: logg.LevelInfo, - TxtarString: files, - }).Build() - - s.AssertFileContent("public/css/styles.css", `@import "components/doesnotexist.css";`) -} - -// Issue 9787 -func TestTransformPostCSSResourceCacheWithPathInBaseURL(t *testing.T) { - if !htesting.IsCI() { - t.Skip("Skip long running test when running locally") - } - - c := qt.New(t) - tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test") - c.Assert(err, qt.IsNil) - c.Cleanup(clean) - - for i := 0; i < 2; i++ { - files := postCSSIntegrationTestFiles - - if i == 1 { - files = strings.ReplaceAll(files, "https://example.com", "https://example.com/foo") - files = strings.ReplaceAll(files, "useResourceCacheWhen = 'never'", " useResourceCacheWhen = 'always'") - } - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - NeedsOsFS: true, - NeedsNpmInstall: true, - LogLevel: logg.LevelInfo, - TxtarString: files, - WorkingDir: tempDir, - }).Build() - - b.AssertFileContent("public/index.html", ` -Styles Content: Len: 770917 -`) - - } -} diff --git a/resources/resource_transformers/postcss/postcss_integration_test.go b/resources/resource_transformers/postcss/postcss_integration_test.go new file mode 100644 index 000000000..957e69403 --- /dev/null +++ b/resources/resource_transformers/postcss/postcss_integration_test.go @@ -0,0 +1,265 @@ +// Copyright 2021 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 postcss_test + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/bep/logg" + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/htesting" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" +) + +const postCSSIntegrationTestFiles = ` +-- assets/css/components/a.css -- +/* A comment. */ +/* Another comment. */ +class-in-a { + color: blue; +} + +-- assets/css/components/all.css -- +@import "a.css"; +@import "b.css"; +-- assets/css/components/b.css -- +@import "a.css"; + +class-in-b { + color: blue; +} + +-- assets/css/styles.css -- +@tailwind base; +@tailwind components; +@tailwind utilities; + @import "components/all.css"; +h1 { + @apply text-2xl font-bold; +} + +-- config.toml -- +disablekinds = ['taxonomy', 'term', 'page'] +baseURL = "https://example.com" +[build] +useResourceCacheWhen = 'never' +-- content/p1.md -- +-- data/hugo.toml -- +slogan = "Hugo Rocks!" +-- i18n/en.yaml -- +hello: + other: "Hello" +-- i18n/fr.yaml -- +hello: + other: "Bonjour" +-- layouts/index.html -- +{{ $options := dict "inlineImports" true }} +{{ $styles := resources.Get "css/styles.css" | resources.PostCSS $options }} +Styles RelPermalink: {{ $styles.RelPermalink }} +{{ $cssContent := $styles.Content }} +Styles Content: Len: {{ len $styles.Content }}| +-- package.json -- +{ + "scripts": {}, + + "devDependencies": { + "postcss-cli": "7.1.0", + "tailwindcss": "1.2.0" + } +} +-- postcss.config.js -- +console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT ); +console.error("Hugo PublishDir:", process.env.HUGO_PUBLISHDIR ); +// https://github.com/gohugoio/hugo/issues/7656 +console.error("package.json:", process.env.HUGO_FILE_PACKAGE_JSON ); +console.error("PostCSS Config File:", process.env.HUGO_FILE_POSTCSS_CONFIG_JS ); + +module.exports = { + plugins: [ + require('tailwindcss') + ] +} + +` + +func TestTransformPostCSS(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test") + c.Assert(err, qt.IsNil) + c.Cleanup(clean) + + for _, s := range []string{"never", "always"} { + + repl := strings.NewReplacer( + "https://example.com", + "https://example.com/foo", + "useResourceCacheWhen = 'never'", + fmt.Sprintf("useResourceCacheWhen = '%s'", s), + ) + + files := repl.Replace(postCSSIntegrationTestFiles) + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: logg.LevelInfo, + WorkingDir: tempDir, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/index.html", ` +Styles RelPermalink: /foo/css/styles.css +Styles Content: Len: 770917| +`) + + if s == "never" { + b.AssertLogContains("Hugo Environment: production") + b.AssertLogContains("Hugo PublishDir: " + filepath.Join(tempDir, "public")) + } + } +} + +// 9880 +func TestTransformPostCSSError(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + if runtime.GOOS == "windows" { + // TODO(bep) This has started to fail on Windows with Go 1.19 on GitHub Actions for some mysterious reason. + t.Skip("Skip on Windows") + } + + c := qt.New(t) + + s, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + TxtarString: strings.ReplaceAll(postCSSIntegrationTestFiles, "color: blue;", "@apply foo;"), // Syntax error + }).BuildE() + + s.AssertIsFileError(err) + c.Assert(err.Error(), qt.Contains, "a.css:4:2") +} + +func TestTransformPostCSSNotInstalledError(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + s, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + TxtarString: postCSSIntegrationTestFiles, + }).BuildE() + + s.AssertIsFileError(err) + c.Assert(err.Error(), qt.Contains, `binary with name "npx" not found`) +} + +// #9895 +func TestTransformPostCSSImportError(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + s, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: logg.LevelInfo, + TxtarString: strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`), + }).BuildE() + + s.AssertIsFileError(err) + c.Assert(err.Error(), qt.Contains, "styles.css:4:3") + c.Assert(err.Error(), qt.Contains, filepath.FromSlash(`failed to resolve CSS @import "/css/components/doesnotexist.css"`)) +} + +func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + files := strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`) + files = strings.ReplaceAll(files, `{{ $options := dict "inlineImports" true }}`, `{{ $options := dict "inlineImports" true "skipInlineImportsNotFound" true }}`) + + s := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: logg.LevelInfo, + TxtarString: files, + }).Build() + + s.AssertFileContent("public/css/styles.css", `@import "components/doesnotexist.css";`) +} + +// Issue 9787 +func TestTransformPostCSSResourceCacheWithPathInBaseURL(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test") + c.Assert(err, qt.IsNil) + c.Cleanup(clean) + + for i := 0; i < 2; i++ { + files := postCSSIntegrationTestFiles + + if i == 1 { + files = strings.ReplaceAll(files, "https://example.com", "https://example.com/foo") + files = strings.ReplaceAll(files, "useResourceCacheWhen = 'never'", " useResourceCacheWhen = 'always'") + } + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: logg.LevelInfo, + TxtarString: files, + WorkingDir: tempDir, + }).Build() + + b.AssertFileContent("public/index.html", ` +Styles Content: Len: 770917 +`) + + } +} diff --git a/resources/resource_transformers/templates/integration_test.go b/resources/resource_transformers/templates/integration_test.go deleted file mode 100644 index 4eaac8e27..000000000 --- a/resources/resource_transformers/templates/integration_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 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 templates_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestExecuteAsTemplateMultipleLanguages(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = "http://example.com/blog" -defaultContentLanguage = "fr" -defaultContentLanguageInSubdir = true -[Languages] -[Languages.en] -weight = 10 -title = "In English" -languageName = "English" -[Languages.fr] -weight = 20 -title = "Le Français" -languageName = "Français" --- i18n/en.toml -- -[hello] -other = "Hello" --- i18n/fr.toml -- -[hello] -other = "Bonjour" --- layouts/index.fr.html -- -Lang: {{ site.Language.Lang }} -{{ $templ := "{{T \"hello\"}}" | resources.FromString "f1.html" }} -{{ $helloResource := $templ | resources.ExecuteAsTemplate (print "f%s.html" .Lang) . }} -Hello1: {{T "hello"}} -Hello2: {{ $helloResource.Content }} -LangURL: {{ relLangURL "foo" }} --- layouts/index.html -- -Lang: {{ site.Language.Lang }} -{{ $templ := "{{T \"hello\"}}" | resources.FromString "f1.html" }} -{{ $helloResource := $templ | resources.ExecuteAsTemplate (print "f%s.html" .Lang) . }} -Hello1: {{T "hello"}} -Hello2: {{ $helloResource.Content }} -LangURL: {{ relLangURL "foo" }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/en/index.html", ` - Hello1: Hello - Hello2: Hello - `) - - b.AssertFileContent("public/fr/index.html", ` - Hello1: Bonjour - Hello2: Bonjour - `) -} diff --git a/resources/resource_transformers/templates/templates_integration_test.go b/resources/resource_transformers/templates/templates_integration_test.go new file mode 100644 index 000000000..4eaac8e27 --- /dev/null +++ b/resources/resource_transformers/templates/templates_integration_test.go @@ -0,0 +1,77 @@ +// Copyright 2021 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 templates_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestExecuteAsTemplateMultipleLanguages(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = "http://example.com/blog" +defaultContentLanguage = "fr" +defaultContentLanguageInSubdir = true +[Languages] +[Languages.en] +weight = 10 +title = "In English" +languageName = "English" +[Languages.fr] +weight = 20 +title = "Le Français" +languageName = "Français" +-- i18n/en.toml -- +[hello] +other = "Hello" +-- i18n/fr.toml -- +[hello] +other = "Bonjour" +-- layouts/index.fr.html -- +Lang: {{ site.Language.Lang }} +{{ $templ := "{{T \"hello\"}}" | resources.FromString "f1.html" }} +{{ $helloResource := $templ | resources.ExecuteAsTemplate (print "f%s.html" .Lang) . }} +Hello1: {{T "hello"}} +Hello2: {{ $helloResource.Content }} +LangURL: {{ relLangURL "foo" }} +-- layouts/index.html -- +Lang: {{ site.Language.Lang }} +{{ $templ := "{{T \"hello\"}}" | resources.FromString "f1.html" }} +{{ $helloResource := $templ | resources.ExecuteAsTemplate (print "f%s.html" .Lang) . }} +Hello1: {{T "hello"}} +Hello2: {{ $helloResource.Content }} +LangURL: {{ relLangURL "foo" }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/en/index.html", ` + Hello1: Hello + Hello2: Hello + `) + + b.AssertFileContent("public/fr/index.html", ` + Hello1: Bonjour + Hello2: Bonjour + `) +} diff --git a/resources/resource_transformers/tocss/dartsass/dartsass_integration_test.go b/resources/resource_transformers/tocss/dartsass/dartsass_integration_test.go new file mode 100644 index 000000000..c370a1cc8 --- /dev/null +++ b/resources/resource_transformers/tocss/dartsass/dartsass_integration_test.go @@ -0,0 +1,532 @@ +// Copyright 2021 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 dartsass_test + +import ( + "strings" + "testing" + + "github.com/bep/logg" + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass" +) + +func TestTransformIncludePaths(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/main.scss -- +@import "moo"; +-- node_modules/foo/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- config.toml -- +-- layouts/index.html -- +{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo") "transpiler" "dartsass" ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: moo{color:#fff}`) +} + +func TestTransformImportRegularCSS(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- assets/scss/another.css -- + +-- assets/scss/main.scss -- +@import "moo"; +@import "regular.css"; +@import "moo"; +@import "another.css"; + +/* foo */ +-- assets/scss/regular.css -- + +-- config.toml -- +-- layouts/index.html -- +{{ $r := resources.Get "scss/main.scss" | toCSS (dict "transpiler" "dartsass") }} +T1: {{ $r.Content | safeHTML }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + // Dart Sass does not follow regular CSS import, but they + // get pulled to the top. + b.AssertFileContent("public/index.html", `T1: @import "regular.css"; + @import "another.css"; + moo { + color: #fff; + } + + moo { + color: #fff; + } + + /* foo */`) +} + +func TestTransformImportIndentedSASS(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/_moo.sass -- +#main + color: blue +-- assets/scss/main.scss -- +@import "moo"; + +/* foo */ +-- config.toml -- +-- layouts/index.html -- +{{ $r := resources.Get "scss/main.scss" | toCSS (dict "transpiler" "dartsass") }} +T1: {{ $r.Content | safeHTML }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + b.AssertFileContent("public/index.html", "T1: #main {\n color: blue;\n}\n\n/* foo */") +} + +// Issue 10592 +func TestTransformImportMountedCSS(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/main.scss -- +@import "import-this-file.css"; +@import "foo/import-this-mounted-file.css"; +@import "compile-this-file"; +@import "foo/compile-this-mounted-file"; +a {color: main-scss;} +-- assets/_compile-this-file.css -- +a {color: compile-this-file-css;} +-- assets/_import-this-file.css -- +a {color: import-this-file-css;} +-- foo/_compile-this-mounted-file.css -- +a {color: compile-this-mounted-file-css;} +-- foo/_import-this-mounted-file.css -- +a {color: import-this-mounted-file-css;} +-- layouts/index.html -- +{{- $opts := dict "transpiler" "dartsass" }} +{{- with resources.Get "main.scss" | toCSS $opts }}{{ .Content | safeHTML }}{{ end }} +-- config.toml -- +disableKinds = ['RSS','sitemap','taxonomy','term','page','section'] + +[[module.mounts]] +source = 'assets' +target = 'assets' + +[[module.mounts]] +source = 'foo' +target = 'assets/foo' + ` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` + @import "import-this-file.css"; + @import "foo/import-this-mounted-file.css"; + a { + color: compile-this-file-css; + } + + a { + color: compile-this-mounted-file-css; + } + + a { + color: main-scss; + } + `) +} + +func TestTransformThemeOverrides(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/components/_boo.scss -- +$boolor: green; + +boo { + color: $boolor; +} +-- assets/scss/components/_moo.scss -- +$moolor: #ccc; + +moo { + color: $moolor; +} +-- config.toml -- +theme = 'mytheme' +-- layouts/index.html -- +{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) "transpiler" "dartsass" ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} +-- themes/mytheme/assets/scss/components/_boo.scss -- +$boolor: orange; + +boo { + color: $boolor; +} +-- themes/mytheme/assets/scss/components/_imports.scss -- +@import "moo"; +@import "_boo"; +@import "_zoo"; +-- themes/mytheme/assets/scss/components/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- themes/mytheme/assets/scss/components/_zoo.scss -- +$zoolor: pink; + +zoo { + color: $zoolor; +} +-- themes/mytheme/assets/scss/main.scss -- +@import "components/imports"; + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`) +} + +func TestTransformLogging(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/main.scss -- +@warn "foo"; +@debug "bar"; + +-- config.toml -- +disableKinds = ["term", "taxonomy", "section", "page"] +-- layouts/index.html -- +{{ $cssOpts := (dict "transpiler" "dartsass" ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + LogLevel: logg.LevelInfo, + }).Build() + + b.AssertLogMatches(`Dart Sass: foo`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:1:0: bar`) + +} + +func TestTransformErrors(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + c := qt.New(t) + + const filesTemplate = ` +-- config.toml -- +-- assets/scss/components/_foo.scss -- +/* comment line 1 */ +$foocolor: #ccc; + +foo { + color: $foocolor; +} +-- assets/scss/main.scss -- +/* comment line 1 */ +/* comment line 2 */ +@import "components/foo"; +/* comment line 4 */ + + $maincolor: #eee; + +body { + color: $maincolor; +} + +-- layouts/index.html -- +{{ $cssOpts := dict "transpiler" "dartsass" }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} + + ` + + c.Run("error in main", func(c *qt.C) { + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1), + NeedsOsFS: true, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `main.scss:8:13":`) + b.Assert(err.Error(), qt.Contains, `: expected ":".`) + fe := b.AssertIsFileError(err) + b.Assert(fe.ErrorContext(), qt.IsNotNil) + b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{" $maincolor #eee;", "", "body {", "\tcolor: $maincolor;", "}"}) + b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") + + }) + + c.Run("error in import", func(c *qt.C) { + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1), + NeedsOsFS: true, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `_foo.scss:2:10":`) + b.Assert(err.Error(), qt.Contains, `: expected ":".`) + fe := b.AssertIsFileError(err) + b.Assert(fe.ErrorContext(), qt.IsNotNil) + b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"}) + b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") + + }) + +} + +func TestOptionVars(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/main.scss -- +@use "hugo:vars"; + +body { + body { + background: url(vars.$image) no-repeat center/cover; + font-family: vars.$font; + } +} + +p { + color: vars.$color1; + font-size: vars.$font_size; +} + +b { + color: vars.$color2; +} +-- layouts/index.html -- +{{ $image := "images/hero.jpg" }} +{{ $font := "Hugo's New Roman" }} +{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image "font" $font }} +{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo's New Roman}p{color:blue;font-size:24px}b{color:green}`) +} + +func TestOptionVarsParams(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- config.toml -- +[params] +[params.sassvars] +color1 = "blue" +color2 = "green" +font_size = "24px" +image = "images/hero.jpg" +-- assets/scss/main.scss -- +@use "hugo:vars"; + +body { + body { + background: url(vars.$image) no-repeat center/cover; + } +} + +p { + color: vars.$color1; + font-size: vars.$font_size; +} + +b { + color: vars.$color2; +} +-- layouts/index.html -- +{{ $vars := site.Params.sassvars}} +{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover}p{color:blue;font-size:24px}b{color:green}`) +} + +func TestVarsCasting(t *testing.T) { + t.Parallel() + if !dartsass.Supports() { + t.Skip() + } + + files := ` +-- config.toml -- +disableKinds = ["term", "taxonomy", "section", "page"] + +[params] +[params.sassvars] +color_hex = "#fff" +color_rgb = "rgb(255, 255, 255)" +color_hsl = "hsl(0, 0%, 100%)" +dimension = "24px" +percentage = "10%" +flex = "5fr" +name = "Hugo" +url = "https://gohugo.io" +integer = 32 +float = 3.14 +-- assets/scss/main.scss -- +@use "hugo:vars"; +@use "sass:meta"; + +@debug meta.type-of(vars.$color_hex); +@debug meta.type-of(vars.$color_rgb); +@debug meta.type-of(vars.$color_hsl); +@debug meta.type-of(vars.$dimension); +@debug meta.type-of(vars.$percentage); +@debug meta.type-of(vars.$flex); +@debug meta.type-of(vars.$name); +@debug meta.type-of(vars.$url); +@debug meta.type-of(vars.$not_a_number); +@debug meta.type-of(vars.$integer); +@debug meta.type-of(vars.$float); +@debug meta.type-of(vars.$a_number); +-- layouts/index.html -- +{{ $vars := site.Params.sassvars}} +{{ $vars = merge $vars (dict "not_a_number" ("32xxx" | css.Quoted) "a_number" ("234" | css.Unquoted) )}} +{{ $cssOpts := (dict "transpiler" "dartsass" "vars" $vars ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + LogLevel: logg.LevelInfo, + }).Build() + + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:3:0: color`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:4:0: color`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:5:0: color`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:6:0: number`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:7:0: number`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:8:0: number`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:9:0: string`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:10:0: string`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:11:0: string`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:12:0: number`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:13:0: number`) + b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:14:0: number`) + +} diff --git a/resources/resource_transformers/tocss/dartsass/integration_test.go b/resources/resource_transformers/tocss/dartsass/integration_test.go deleted file mode 100644 index c370a1cc8..000000000 --- a/resources/resource_transformers/tocss/dartsass/integration_test.go +++ /dev/null @@ -1,532 +0,0 @@ -// Copyright 2021 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 dartsass_test - -import ( - "strings" - "testing" - - "github.com/bep/logg" - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass" -) - -func TestTransformIncludePaths(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/main.scss -- -@import "moo"; --- node_modules/foo/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- config.toml -- --- layouts/index.html -- -{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo") "transpiler" "dartsass" ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: moo{color:#fff}`) -} - -func TestTransformImportRegularCSS(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- assets/scss/another.css -- - --- assets/scss/main.scss -- -@import "moo"; -@import "regular.css"; -@import "moo"; -@import "another.css"; - -/* foo */ --- assets/scss/regular.css -- - --- config.toml -- --- layouts/index.html -- -{{ $r := resources.Get "scss/main.scss" | toCSS (dict "transpiler" "dartsass") }} -T1: {{ $r.Content | safeHTML }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - // Dart Sass does not follow regular CSS import, but they - // get pulled to the top. - b.AssertFileContent("public/index.html", `T1: @import "regular.css"; - @import "another.css"; - moo { - color: #fff; - } - - moo { - color: #fff; - } - - /* foo */`) -} - -func TestTransformImportIndentedSASS(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/_moo.sass -- -#main - color: blue --- assets/scss/main.scss -- -@import "moo"; - -/* foo */ --- config.toml -- --- layouts/index.html -- -{{ $r := resources.Get "scss/main.scss" | toCSS (dict "transpiler" "dartsass") }} -T1: {{ $r.Content | safeHTML }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - b.AssertFileContent("public/index.html", "T1: #main {\n color: blue;\n}\n\n/* foo */") -} - -// Issue 10592 -func TestTransformImportMountedCSS(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/main.scss -- -@import "import-this-file.css"; -@import "foo/import-this-mounted-file.css"; -@import "compile-this-file"; -@import "foo/compile-this-mounted-file"; -a {color: main-scss;} --- assets/_compile-this-file.css -- -a {color: compile-this-file-css;} --- assets/_import-this-file.css -- -a {color: import-this-file-css;} --- foo/_compile-this-mounted-file.css -- -a {color: compile-this-mounted-file-css;} --- foo/_import-this-mounted-file.css -- -a {color: import-this-mounted-file-css;} --- layouts/index.html -- -{{- $opts := dict "transpiler" "dartsass" }} -{{- with resources.Get "main.scss" | toCSS $opts }}{{ .Content | safeHTML }}{{ end }} --- config.toml -- -disableKinds = ['RSS','sitemap','taxonomy','term','page','section'] - -[[module.mounts]] -source = 'assets' -target = 'assets' - -[[module.mounts]] -source = 'foo' -target = 'assets/foo' - ` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` - @import "import-this-file.css"; - @import "foo/import-this-mounted-file.css"; - a { - color: compile-this-file-css; - } - - a { - color: compile-this-mounted-file-css; - } - - a { - color: main-scss; - } - `) -} - -func TestTransformThemeOverrides(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/components/_boo.scss -- -$boolor: green; - -boo { - color: $boolor; -} --- assets/scss/components/_moo.scss -- -$moolor: #ccc; - -moo { - color: $moolor; -} --- config.toml -- -theme = 'mytheme' --- layouts/index.html -- -{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) "transpiler" "dartsass" ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} --- themes/mytheme/assets/scss/components/_boo.scss -- -$boolor: orange; - -boo { - color: $boolor; -} --- themes/mytheme/assets/scss/components/_imports.scss -- -@import "moo"; -@import "_boo"; -@import "_zoo"; --- themes/mytheme/assets/scss/components/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- themes/mytheme/assets/scss/components/_zoo.scss -- -$zoolor: pink; - -zoo { - color: $zoolor; -} --- themes/mytheme/assets/scss/main.scss -- -@import "components/imports"; - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`) -} - -func TestTransformLogging(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/main.scss -- -@warn "foo"; -@debug "bar"; - --- config.toml -- -disableKinds = ["term", "taxonomy", "section", "page"] --- layouts/index.html -- -{{ $cssOpts := (dict "transpiler" "dartsass" ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - LogLevel: logg.LevelInfo, - }).Build() - - b.AssertLogMatches(`Dart Sass: foo`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:1:0: bar`) - -} - -func TestTransformErrors(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - c := qt.New(t) - - const filesTemplate = ` --- config.toml -- --- assets/scss/components/_foo.scss -- -/* comment line 1 */ -$foocolor: #ccc; - -foo { - color: $foocolor; -} --- assets/scss/main.scss -- -/* comment line 1 */ -/* comment line 2 */ -@import "components/foo"; -/* comment line 4 */ - - $maincolor: #eee; - -body { - color: $maincolor; -} - --- layouts/index.html -- -{{ $cssOpts := dict "transpiler" "dartsass" }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} - - ` - - c.Run("error in main", func(c *qt.C) { - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1), - NeedsOsFS: true, - }).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `main.scss:8:13":`) - b.Assert(err.Error(), qt.Contains, `: expected ":".`) - fe := b.AssertIsFileError(err) - b.Assert(fe.ErrorContext(), qt.IsNotNil) - b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{" $maincolor #eee;", "", "body {", "\tcolor: $maincolor;", "}"}) - b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") - - }) - - c.Run("error in import", func(c *qt.C) { - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1), - NeedsOsFS: true, - }).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `_foo.scss:2:10":`) - b.Assert(err.Error(), qt.Contains, `: expected ":".`) - fe := b.AssertIsFileError(err) - b.Assert(fe.ErrorContext(), qt.IsNotNil) - b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"}) - b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") - - }) - -} - -func TestOptionVars(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- assets/scss/main.scss -- -@use "hugo:vars"; - -body { - body { - background: url(vars.$image) no-repeat center/cover; - font-family: vars.$font; - } -} - -p { - color: vars.$color1; - font-size: vars.$font_size; -} - -b { - color: vars.$color2; -} --- layouts/index.html -- -{{ $image := "images/hero.jpg" }} -{{ $font := "Hugo's New Roman" }} -{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image "font" $font }} -{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo's New Roman}p{color:blue;font-size:24px}b{color:green}`) -} - -func TestOptionVarsParams(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- config.toml -- -[params] -[params.sassvars] -color1 = "blue" -color2 = "green" -font_size = "24px" -image = "images/hero.jpg" --- assets/scss/main.scss -- -@use "hugo:vars"; - -body { - body { - background: url(vars.$image) no-repeat center/cover; - } -} - -p { - color: vars.$color1; - font-size: vars.$font_size; -} - -b { - color: vars.$color2; -} --- layouts/index.html -- -{{ $vars := site.Params.sassvars}} -{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover}p{color:blue;font-size:24px}b{color:green}`) -} - -func TestVarsCasting(t *testing.T) { - t.Parallel() - if !dartsass.Supports() { - t.Skip() - } - - files := ` --- config.toml -- -disableKinds = ["term", "taxonomy", "section", "page"] - -[params] -[params.sassvars] -color_hex = "#fff" -color_rgb = "rgb(255, 255, 255)" -color_hsl = "hsl(0, 0%, 100%)" -dimension = "24px" -percentage = "10%" -flex = "5fr" -name = "Hugo" -url = "https://gohugo.io" -integer = 32 -float = 3.14 --- assets/scss/main.scss -- -@use "hugo:vars"; -@use "sass:meta"; - -@debug meta.type-of(vars.$color_hex); -@debug meta.type-of(vars.$color_rgb); -@debug meta.type-of(vars.$color_hsl); -@debug meta.type-of(vars.$dimension); -@debug meta.type-of(vars.$percentage); -@debug meta.type-of(vars.$flex); -@debug meta.type-of(vars.$name); -@debug meta.type-of(vars.$url); -@debug meta.type-of(vars.$not_a_number); -@debug meta.type-of(vars.$integer); -@debug meta.type-of(vars.$float); -@debug meta.type-of(vars.$a_number); --- layouts/index.html -- -{{ $vars := site.Params.sassvars}} -{{ $vars = merge $vars (dict "not_a_number" ("32xxx" | css.Quoted) "a_number" ("234" | css.Unquoted) )}} -{{ $cssOpts := (dict "transpiler" "dartsass" "vars" $vars ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - LogLevel: logg.LevelInfo, - }).Build() - - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:3:0: color`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:4:0: color`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:5:0: color`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:6:0: number`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:7:0: number`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:8:0: number`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:9:0: string`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:10:0: string`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:11:0: string`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:12:0: number`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:13:0: number`) - b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:14:0: number`) - -} diff --git a/resources/resource_transformers/tocss/scss/integration_test.go b/resources/resource_transformers/tocss/scss/integration_test.go deleted file mode 100644 index d0dd65e20..000000000 --- a/resources/resource_transformers/tocss/scss/integration_test.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2021 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 scss_test - -import ( - "path/filepath" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss" -) - -func TestTransformIncludePaths(t *testing.T) { - t.Parallel() - if !scss.Supports() { - t.Skip() - } - c := qt.New(t) - - files := ` --- assets/scss/main.scss -- -@import "moo"; --- node_modules/foo/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- config.toml -- --- layouts/index.html -- -{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo") ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: moo{color:#fff}`) -} - -func TestTransformImportRegularCSS(t *testing.T) { - t.Parallel() - if !scss.Supports() { - t.Skip() - } - - c := qt.New(t) - - files := ` --- assets/scss/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- assets/scss/another.css -- - --- assets/scss/main.scss -- -@import "moo"; -@import "regular.css"; -@import "moo"; -@import "another.css"; - -/* foo */ --- assets/scss/regular.css -- - --- config.toml -- --- layouts/index.html -- -{{ $r := resources.Get "scss/main.scss" | toCSS }} -T1: {{ $r.Content | safeHTML }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - // LibSass does not support regular CSS imports. There - // is an open bug about it that probably will never be resolved. - // Hugo works around this by preserving them in place: - b.AssertFileContent("public/index.html", ` - T1: moo { - color: #fff; } - -@import "regular.css"; -moo { - color: #fff; } - -@import "another.css"; -/* foo */ - -`) -} - -func TestTransformThemeOverrides(t *testing.T) { - t.Parallel() - if !scss.Supports() { - t.Skip() - } - - c := qt.New(t) - - files := ` --- assets/scss/components/_boo.scss -- -$boolor: green; - -boo { - color: $boolor; -} --- assets/scss/components/_moo.scss -- -$moolor: #ccc; - -moo { - color: $moolor; -} --- config.toml -- -theme = 'mytheme' --- layouts/index.html -- -{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} --- themes/mytheme/assets/scss/components/_boo.scss -- -$boolor: orange; - -boo { - color: $boolor; -} --- themes/mytheme/assets/scss/components/_imports.scss -- -@import "moo"; -@import "_boo"; -@import "_zoo"; --- themes/mytheme/assets/scss/components/_moo.scss -- -$moolor: #fff; - -moo { - color: $moolor; -} --- themes/mytheme/assets/scss/components/_zoo.scss -- -$zoolor: pink; - -zoo { - color: $zoolor; -} --- themes/mytheme/assets/scss/main.scss -- -@import "components/imports"; - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`) -} - -func TestTransformErrors(t *testing.T) { - t.Parallel() - if !scss.Supports() { - t.Skip() - } - - c := qt.New(t) - - const filesTemplate = ` --- config.toml -- -theme = 'mytheme' --- assets/scss/components/_foo.scss -- -/* comment line 1 */ -$foocolor: #ccc; - -foo { - color: $foocolor; -} --- themes/mytheme/assets/scss/main.scss -- -/* comment line 1 */ -/* comment line 2 */ -@import "components/foo"; -/* comment line 4 */ - -$maincolor: #eee; - -body { - color: $maincolor; -} - --- layouts/index.html -- -{{ $cssOpts := dict }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} -T1: {{ $r.Content }} - - ` - - c.Run("error in main", func(c *qt.C) { - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1), - NeedsOsFS: true, - }).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`themes/mytheme/assets/scss/main.scss:6:1": expected ':' after $maincolor in assignment statement`)) - fe := b.AssertIsFileError(err) - b.Assert(fe.ErrorContext(), qt.IsNotNil) - b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 4 */", "", "$maincolor #eee;", "", "body {"}) - b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") - - }) - - c.Run("error in import", func(c *qt.C) { - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: c, - TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1), - NeedsOsFS: true, - }).BuildE() - - b.Assert(err, qt.IsNotNil) - b.Assert(err.Error(), qt.Contains, `assets/scss/components/_foo.scss:2:1": expected ':' after $foocolor in assignment statement`) - fe := b.AssertIsFileError(err) - b.Assert(fe.ErrorContext(), qt.IsNotNil) - b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"}) - b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") - - }) - -} - -func TestOptionVars(t *testing.T) { - t.Parallel() - if !scss.Supports() { - t.Skip() - } - - files := ` --- assets/scss/main.scss -- -@import "hugo:vars"; - -body { - body { - background: url($image) no-repeat center/cover; - font-family: $font; - } -} - -p { - color: $color1; - font-size: var$font_size; -} - -b { - color: $color2; -} --- layouts/index.html -- -{{ $image := "images/hero.jpg" }} -{{ $font := "Hugo's New Roman" }} -{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image "font" $font }} -{{ $cssOpts := (dict "transpiler" "libsass" "outputStyle" "compressed" "vars" $vars ) }} -{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} -T1: {{ $r.Content }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }).Build() - - b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo's New Roman}p{color:blue;font-size:var 24px}b{color:green}`) -} diff --git a/resources/resource_transformers/tocss/scss/scss_integration_test.go b/resources/resource_transformers/tocss/scss/scss_integration_test.go new file mode 100644 index 000000000..d0dd65e20 --- /dev/null +++ b/resources/resource_transformers/tocss/scss/scss_integration_test.go @@ -0,0 +1,295 @@ +// Copyright 2021 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 scss_test + +import ( + "path/filepath" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss" +) + +func TestTransformIncludePaths(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + c := qt.New(t) + + files := ` +-- assets/scss/main.scss -- +@import "moo"; +-- node_modules/foo/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- config.toml -- +-- layouts/index.html -- +{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo") ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: moo{color:#fff}`) +} + +func TestTransformImportRegularCSS(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + + c := qt.New(t) + + files := ` +-- assets/scss/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- assets/scss/another.css -- + +-- assets/scss/main.scss -- +@import "moo"; +@import "regular.css"; +@import "moo"; +@import "another.css"; + +/* foo */ +-- assets/scss/regular.css -- + +-- config.toml -- +-- layouts/index.html -- +{{ $r := resources.Get "scss/main.scss" | toCSS }} +T1: {{ $r.Content | safeHTML }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + // LibSass does not support regular CSS imports. There + // is an open bug about it that probably will never be resolved. + // Hugo works around this by preserving them in place: + b.AssertFileContent("public/index.html", ` + T1: moo { + color: #fff; } + +@import "regular.css"; +moo { + color: #fff; } + +@import "another.css"; +/* foo */ + +`) +} + +func TestTransformThemeOverrides(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + + c := qt.New(t) + + files := ` +-- assets/scss/components/_boo.scss -- +$boolor: green; + +boo { + color: $boolor; +} +-- assets/scss/components/_moo.scss -- +$moolor: #ccc; + +moo { + color: $moolor; +} +-- config.toml -- +theme = 'mytheme' +-- layouts/index.html -- +{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} +-- themes/mytheme/assets/scss/components/_boo.scss -- +$boolor: orange; + +boo { + color: $boolor; +} +-- themes/mytheme/assets/scss/components/_imports.scss -- +@import "moo"; +@import "_boo"; +@import "_zoo"; +-- themes/mytheme/assets/scss/components/_moo.scss -- +$moolor: #fff; + +moo { + color: $moolor; +} +-- themes/mytheme/assets/scss/components/_zoo.scss -- +$zoolor: pink; + +zoo { + color: $zoolor; +} +-- themes/mytheme/assets/scss/main.scss -- +@import "components/imports"; + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`) +} + +func TestTransformErrors(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + + c := qt.New(t) + + const filesTemplate = ` +-- config.toml -- +theme = 'mytheme' +-- assets/scss/components/_foo.scss -- +/* comment line 1 */ +$foocolor: #ccc; + +foo { + color: $foocolor; +} +-- themes/mytheme/assets/scss/main.scss -- +/* comment line 1 */ +/* comment line 2 */ +@import "components/foo"; +/* comment line 4 */ + +$maincolor: #eee; + +body { + color: $maincolor; +} + +-- layouts/index.html -- +{{ $cssOpts := dict }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }} +T1: {{ $r.Content }} + + ` + + c.Run("error in main", func(c *qt.C) { + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1), + NeedsOsFS: true, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`themes/mytheme/assets/scss/main.scss:6:1": expected ':' after $maincolor in assignment statement`)) + fe := b.AssertIsFileError(err) + b.Assert(fe.ErrorContext(), qt.IsNotNil) + b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 4 */", "", "$maincolor #eee;", "", "body {"}) + b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") + + }) + + c.Run("error in import", func(c *qt.C) { + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1), + NeedsOsFS: true, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `assets/scss/components/_foo.scss:2:1": expected ':' after $foocolor in assignment statement`) + fe := b.AssertIsFileError(err) + b.Assert(fe.ErrorContext(), qt.IsNotNil) + b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"}) + b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss") + + }) + +} + +func TestOptionVars(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/main.scss -- +@import "hugo:vars"; + +body { + body { + background: url($image) no-repeat center/cover; + font-family: $font; + } +} + +p { + color: $color1; + font-size: var$font_size; +} + +b { + color: $color2; +} +-- layouts/index.html -- +{{ $image := "images/hero.jpg" }} +{{ $font := "Hugo's New Roman" }} +{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image "font" $font }} +{{ $cssOpts := (dict "transpiler" "libsass" "outputStyle" "compressed" "vars" $vars ) }} +{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }).Build() + + b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo's New Roman}p{color:blue;font-size:var 24px}b{color:green}`) +} diff --git a/resources/resources_integration_test.go b/resources/resources_integration_test.go new file mode 100644 index 000000000..9540b0976 --- /dev/null +++ b/resources/resources_integration_test.go @@ -0,0 +1,233 @@ +// Copyright 2024 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 resources_test + +import ( + "strings" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 8931 +func TestImageCache(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = "https://example.org" +-- content/mybundle/index.md -- +--- +title: "My Bundle" +--- +-- content/mybundle/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- content/mybundle/giphy.gif -- +sourcefilename: testdata/giphy.gif +-- layouts/foo.html -- +-- layouts/index.html -- +{{ $p := site.GetPage "mybundle"}} +{{ $img := $p.Resources.Get "pixel.png" }} +{{ $giphy := $p.Resources.Get "giphy.gif" }} +{{ $gif := $img.Resize "1x2 gif" }} +{{ $bmp := $img.Resize "2x3 bmp" }} +{{ $anigif := $giphy.Resize "4x5" }} + + +gif: {{ $gif.RelPermalink }}|}|{{ $gif.Width }}|{{ $gif.Height }}|{{ $gif.MediaType }}| +bmp: {{ $bmp.RelPermalink }}|}|{{ $bmp.Width }}|{{ $bmp.Height }}|{{ $bmp.MediaType }}| +anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $anigif.MediaType }}| +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + Running: true, + }).Build() + + assertImages := func() { + b.AssertFileContent("public/index.html", ` + gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x2_resize_box_3.gif|}|1|2|image/gif| + bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x3_resize_box_3.bmp|}|2|3|image/bmp| + anigif: /mybundle/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_4x5_resize_box_1.gif|4|5|image/gif| + `) + } + + assertImages() + + b.EditFileReplaceFunc("content/mybundle/index.md", func(s string) string { return strings.ReplaceAll(s, "Bundle", "BUNDLE") }) + b.Build() + + assertImages() +} + +func TestSVGError(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- assets/circle.svg -- + +-- layouts/index.html -- +{{ $svg := resources.Get "circle.svg" }} +Width: {{ $svg.Width }} +` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + Running: true, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, `error calling Width: this method is only available for raster images. To determine if an image is SVG, you can do {{ if eq .MediaType.SubType "svg" }}{{ end }}`) +} + +// Issue 10255. +func TestNoPublishOfUnusedProcessedImage(t *testing.T) { + t.Parallel() + + workingDir := t.TempDir() + + files := ` +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ $image := resources.Get "images/pixel.png" }} +{{ $image = $image.Resize "400x" }} +{{ $image = $image.Resize "300x" }} +{{ $image = $image.Resize "200x" }} +{{ $image = $image.Resize "100x" }} +{{ $image = $image.Crop "50x50" }} +{{ $image = $image.Filter (images.GaussianBlur 6) }} +{{ ($image | fingerprint).Permalink }} + + +` + + for i := 0; i < 3; i++ { + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + WorkingDir: workingDir, + }).Build() + + b.AssertFileCount("resources/_gen/images", 6) + b.AssertFileCount("public/images", 1) + b.Build() + } +} + +func TestProcessFilter(t *testing.T) { + t.Parallel() + + files := ` +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ $pixel := resources.Get "images/pixel.png" }} +{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) (images.Process "jpg") }} +{{ $image := $pixel.Filter $filters }} +jpg|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| +{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) (images.Process "jpg resize 20x30") }} +{{ $image := $pixel.Filter $filters }} +resize 1|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| +{{ $image := $pixel.Filter $filters }} +resize 2|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType }}|Width: {{ $image.Width }}|Height: {{ $image.Height }}| + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/index.html", + "jpg|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_17010532266664966692.jpg|MediaType: image/jpeg|Width: 1|Height: 1|", + "resize 1|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|", + "resize 2|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|", + ) +} + +// Issue #11563 +func TestGroupByParamDate(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +disableKinds = ['section','rss','sitemap','taxonomy','term'] +-- layouts/index.html -- +{{- range site.RegularPages.GroupByParamDate "eventDate" "2006-01" }} + {{- .Key }}|{{ range .Pages }}{{ .Title }}|{{ end }} +{{- end }} +-- content/p1.md -- ++++ +title = 'p1' +eventDate = 2023-09-01 ++++ +-- content/p2.md -- ++++ +title = 'p2' +eventDate = '2023-09-01' ++++ +-- content/p3.md -- +--- +title: p3 +eventDate: 2023-09-01 +--- +-- content/p4.md -- ++++ +title = 'p4' +eventDate = 2023-10-01T08:00:00 ++++ +-- content/p5.md -- ++++ +title = 'p5' +eventDate = '2023-10-01T08:00:00' ++++ +-- content/p6.md -- +--- +title: p6 +eventDate: 2023-10-01T08:00:00 +--- +-- content/p7.md -- ++++ +title = 'p7' +eventDate = 2023-11-01T07:00:00-08:00 ++++ +-- content/p8.md -- ++++ +title = 'p8' +eventDate = '2023-11-01T07:00:00-08:00' ++++ +-- content/p9.md -- +--- +title: p9 +eventDate: 2023-11-01T07:00:00-08:00 +--- + ` + + b := hugolib.Test(t, files) + + b.AssertFileContent("public/index.html", "2023-11|p9|p8|p7|2023-10|p6|p5|p4|2023-09|p3|p2|p1|") +} diff --git a/tpl/collections/collections_integration_test.go b/tpl/collections/collections_integration_test.go new file mode 100644 index 000000000..24727a12c --- /dev/null +++ b/tpl/collections/collections_integration_test.go @@ -0,0 +1,262 @@ +// Copyright 2024 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 collections_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 9585 +func TestApplyWithContext(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ apply (seq 3) "partial" "foo.html"}} +-- layouts/partials/foo.html -- +{{ return "foo"}} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` + [foo foo foo] +`) +} + +// Issue 9865 +func TestSortStable(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +{{ $values := slice (dict "a" 1 "b" 2) (dict "a" 3 "b" 1) (dict "a" 2 "b" 0) (dict "a" 1 "b" 0) (dict "a" 3 "b" 1) (dict "a" 2 "b" 2) (dict "a" 2 "b" 1) (dict "a" 0 "b" 3) (dict "a" 3 "b" 3) (dict "a" 0 "b" 0) (dict "a" 0 "b" 0) (dict "a" 2 "b" 0) (dict "a" 1 "b" 2) (dict "a" 1 "b" 1) (dict "a" 3 "b" 0) (dict "a" 2 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 1) }} +Asc: {{ sort (sort $values "b" "asc") "a" "asc" }} +Desc: {{ sort (sort $values "b" "desc") "a" "desc" }} + + ` + + for i := 0; i < 4; i++ { + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +Asc: [map[a:0 b:0] map[a:0 b:0] map[a:0 b:3] map[a:1 b:0] map[a:1 b:1] map[a:1 b:2] map[a:1 b:2] map[a:2 b:0] map[a:2 b:0] map[a:2 b:0] map[a:2 b:1] map[a:2 b:2] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:1] map[a:3 b:1] map[a:3 b:1] map[a:3 b:3]] +Desc: [map[a:3 b:3] map[a:3 b:1] map[a:3 b:1] map[a:3 b:1] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:2 b:2] map[a:2 b:1] map[a:2 b:0] map[a:2 b:0] map[a:2 b:0] map[a:1 b:2] map[a:1 b:2] map[a:1 b:1] map[a:1 b:0] map[a:0 b:3] map[a:0 b:0] map[a:0 b:0]] +`) + + } +} + +// Issue #11004. +func TestAppendSliceToASliceOfSlices(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +-- layouts/index.html -- +{{ $obj := slice (slice "a") }} +{{ $obj = $obj | append (slice "b") }} +{{ $obj = $obj | append (slice "c") }} + +{{ $obj }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", "[[a] [b] [c]]") +} + +func TestAppendNilToSlice(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +-- layouts/index.html -- +{{ $obj := (slice "a") }} +{{ $obj = $obj | append nil }} + +{{ $obj }} + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", "[a <nil>]") +} + +func TestAppendNilsToSliceWithNils(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +-- layouts/index.html -- +{{ $obj := (slice "a" nil "c") }} +{{ $obj = $obj | append nil }} + +{{ $obj }} + + + ` + + for i := 0; i < 4; i++ { + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", "[a <nil> c <nil>]") + + } +} + +// Issue 11234. +func TestWhereWithWordCount(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +Home: {{ range where site.RegularPages "WordCount" "gt" 50 }}{{ .Title }}|{{ end }} +-- layouts/shortcodes/lorem.html -- +{{ "ipsum " | strings.Repeat (.Get 0 | int) }} + +-- content/p1.md -- +--- +title: "p1" +--- +{{< lorem 100 >}} +-- content/p2.md -- +--- +title: "p2" +--- +{{< lorem 20 >}} +-- content/p3.md -- +--- +title: "p3" +--- +{{< lorem 60 >}} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +Home: p1|p3| +`) +} + +// Issue #11279 +func TestWhereLikeOperator(t *testing.T) { + t.Parallel() + files := ` +-- content/p1.md -- +--- +title: P1 +foo: ab +--- +-- content/p2.md -- +--- +title: P2 +foo: abc +--- +-- content/p3.md -- +--- +title: P3 +foo: bc +--- +-- layouts/index.html -- +
      + {{- range where site.RegularPages "Params.foo" "like" "^ab" -}} +
    • {{ .Title }}
    • + {{- end -}} +
    + ` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + b.AssertFileContent("public/index.html", "
    • P1
    • P2
    ") +} + +// Issue #11498 +func TestEchoParams(t *testing.T) { + t.Parallel() + files := ` +-- hugo.toml -- +[params.footer] +string = 'foo' +int = 42 +float = 3.1415 +boolt = true +boolf = false +-- layouts/index.html -- +{{ echoParam .Site.Params.footer "string" }} +{{ echoParam .Site.Params.footer "int" }} +{{ echoParam .Site.Params.footer "float" }} +{{ echoParam .Site.Params.footer "boolt" }} +{{ echoParam .Site.Params.footer "boolf" }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + b.AssertFileContent("public/index.html", + "foo", + "42", + "3.1415", + "true", + "false", + ) +} diff --git a/tpl/collections/integration_test.go b/tpl/collections/integration_test.go deleted file mode 100644 index 24727a12c..000000000 --- a/tpl/collections/integration_test.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2024 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 collections_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 9585 -func TestApplyWithContext(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -{{ apply (seq 3) "partial" "foo.html"}} --- layouts/partials/foo.html -- -{{ return "foo"}} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` - [foo foo foo] -`) -} - -// Issue 9865 -func TestSortStable(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- layouts/index.html -- -{{ $values := slice (dict "a" 1 "b" 2) (dict "a" 3 "b" 1) (dict "a" 2 "b" 0) (dict "a" 1 "b" 0) (dict "a" 3 "b" 1) (dict "a" 2 "b" 2) (dict "a" 2 "b" 1) (dict "a" 0 "b" 3) (dict "a" 3 "b" 3) (dict "a" 0 "b" 0) (dict "a" 0 "b" 0) (dict "a" 2 "b" 0) (dict "a" 1 "b" 2) (dict "a" 1 "b" 1) (dict "a" 3 "b" 0) (dict "a" 2 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 0) (dict "a" 3 "b" 1) }} -Asc: {{ sort (sort $values "b" "asc") "a" "asc" }} -Desc: {{ sort (sort $values "b" "desc") "a" "desc" }} - - ` - - for i := 0; i < 4; i++ { - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -Asc: [map[a:0 b:0] map[a:0 b:0] map[a:0 b:3] map[a:1 b:0] map[a:1 b:1] map[a:1 b:2] map[a:1 b:2] map[a:2 b:0] map[a:2 b:0] map[a:2 b:0] map[a:2 b:1] map[a:2 b:2] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:1] map[a:3 b:1] map[a:3 b:1] map[a:3 b:3]] -Desc: [map[a:3 b:3] map[a:3 b:1] map[a:3 b:1] map[a:3 b:1] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:3 b:0] map[a:2 b:2] map[a:2 b:1] map[a:2 b:0] map[a:2 b:0] map[a:2 b:0] map[a:1 b:2] map[a:1 b:2] map[a:1 b:1] map[a:1 b:0] map[a:0 b:3] map[a:0 b:0] map[a:0 b:0]] -`) - - } -} - -// Issue #11004. -func TestAppendSliceToASliceOfSlices(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- --- layouts/index.html -- -{{ $obj := slice (slice "a") }} -{{ $obj = $obj | append (slice "b") }} -{{ $obj = $obj | append (slice "c") }} - -{{ $obj }} - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", "[[a] [b] [c]]") -} - -func TestAppendNilToSlice(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- --- layouts/index.html -- -{{ $obj := (slice "a") }} -{{ $obj = $obj | append nil }} - -{{ $obj }} - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", "[a <nil>]") -} - -func TestAppendNilsToSliceWithNils(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- --- layouts/index.html -- -{{ $obj := (slice "a" nil "c") }} -{{ $obj = $obj | append nil }} - -{{ $obj }} - - - ` - - for i := 0; i < 4; i++ { - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", "[a <nil> c <nil>]") - - } -} - -// Issue 11234. -func TestWhereWithWordCount(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -Home: {{ range where site.RegularPages "WordCount" "gt" 50 }}{{ .Title }}|{{ end }} --- layouts/shortcodes/lorem.html -- -{{ "ipsum " | strings.Repeat (.Get 0 | int) }} - --- content/p1.md -- ---- -title: "p1" ---- -{{< lorem 100 >}} --- content/p2.md -- ---- -title: "p2" ---- -{{< lorem 20 >}} --- content/p3.md -- ---- -title: "p3" ---- -{{< lorem 60 >}} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -Home: p1|p3| -`) -} - -// Issue #11279 -func TestWhereLikeOperator(t *testing.T) { - t.Parallel() - files := ` --- content/p1.md -- ---- -title: P1 -foo: ab ---- --- content/p2.md -- ---- -title: P2 -foo: abc ---- --- content/p3.md -- ---- -title: P3 -foo: bc ---- --- layouts/index.html -- -
      - {{- range where site.RegularPages "Params.foo" "like" "^ab" -}} -
    • {{ .Title }}
    • - {{- end -}} -
    - ` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - b.AssertFileContent("public/index.html", "
    • P1
    • P2
    ") -} - -// Issue #11498 -func TestEchoParams(t *testing.T) { - t.Parallel() - files := ` --- hugo.toml -- -[params.footer] -string = 'foo' -int = 42 -float = 3.1415 -boolt = true -boolf = false --- layouts/index.html -- -{{ echoParam .Site.Params.footer "string" }} -{{ echoParam .Site.Params.footer "int" }} -{{ echoParam .Site.Params.footer "float" }} -{{ echoParam .Site.Params.footer "boolt" }} -{{ echoParam .Site.Params.footer "boolf" }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - b.AssertFileContent("public/index.html", - "foo", - "42", - "3.1415", - "true", - "false", - ) -} diff --git a/tpl/debug/debug_integration_test.go b/tpl/debug/debug_integration_test.go new file mode 100644 index 000000000..9a36e2d12 --- /dev/null +++ b/tpl/debug/debug_integration_test.go @@ -0,0 +1,45 @@ +// Copyright 2024 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 requiredF 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 debug_test + +import ( + "testing" + + "github.com/bep/logg" + "github.com/gohugoio/hugo/hugolib" +) + +func TestTimer(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.org/" +disableKinds = ["taxonomy", "term"] +-- layouts/index.html -- +{{ range seq 5 }} +{{ $t := debug.Timer "foo" }} +{{ seq 1 1000 }} +{{ $t.Stop }} +{{ end }} + +` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + LogLevel: logg.LevelInfo, + }, + ).Build() + + b.AssertLogContains("timer: name foo count 5 duration") +} diff --git a/tpl/debug/integration_test.go b/tpl/debug/integration_test.go deleted file mode 100644 index 9a36e2d12..000000000 --- a/tpl/debug/integration_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2024 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 requiredF 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 debug_test - -import ( - "testing" - - "github.com/bep/logg" - "github.com/gohugoio/hugo/hugolib" -) - -func TestTimer(t *testing.T) { - files := ` --- hugo.toml -- -baseURL = "https://example.org/" -disableKinds = ["taxonomy", "term"] --- layouts/index.html -- -{{ range seq 5 }} -{{ $t := debug.Timer "foo" }} -{{ seq 1 1000 }} -{{ $t.Stop }} -{{ end }} - -` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - LogLevel: logg.LevelInfo, - }, - ).Build() - - b.AssertLogContains("timer: name foo count 5 duration") -} diff --git a/tpl/fmt/fmt_integration_test.go b/tpl/fmt/fmt_integration_test.go new file mode 100644 index 000000000..40bfefcdc --- /dev/null +++ b/tpl/fmt/fmt_integration_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 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 fmt_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue #11506 +func TestErroridf(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +ignoreErrors = ['error-b'] +-- layouts/index.html -- +{{ erroridf "error-a" "%s" "a"}} +{{ erroridf "error-b" "%s" "b"}} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + + b.BuildE() + b.AssertLogMatches(`^ERROR a\nYou can suppress this error by adding the following to your site configuration:\nignoreErrors = \['error-a'\]\n$`) +} diff --git a/tpl/fmt/integration_test.go b/tpl/fmt/integration_test.go deleted file mode 100644 index 40bfefcdc..000000000 --- a/tpl/fmt/integration_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 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 fmt_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue #11506 -func TestErroridf(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ['page','rss','section','sitemap','taxonomy','term'] -ignoreErrors = ['error-b'] --- layouts/index.html -- -{{ erroridf "error-a" "%s" "a"}} -{{ erroridf "error-b" "%s" "b"}} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - - b.BuildE() - b.AssertLogMatches(`^ERROR a\nYou can suppress this error by adding the following to your site configuration:\nignoreErrors = \['error-a'\]\n$`) -} diff --git a/tpl/images/images_integration_test.go b/tpl/images/images_integration_test.go new file mode 100644 index 000000000..81f35e39c --- /dev/null +++ b/tpl/images/images_integration_test.go @@ -0,0 +1,56 @@ +// Copyright 2024 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 images_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestImageConfigFromModule(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = 'http://example.com/' +theme = ["mytheme"] +-- static/images/pixel1.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- themes/mytheme/static/images/pixel2.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ $path := "static/images/pixel1.png" }} +fileExists OK: {{ fileExists $path }}| +imageConfig OK: {{ (imageConfig $path).Width }}| +{{ $path2 := "static/images/pixel2.png" }} +fileExists2 OK: {{ fileExists $path2 }}| +imageConfig2 OK: {{ (imageConfig $path2).Width }}| + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +fileExists OK: true| +imageConfig OK: 1| +fileExists2 OK: true| +imageConfig2 OK: 1| +`) +} diff --git a/tpl/images/integration_test.go b/tpl/images/integration_test.go deleted file mode 100644 index 81f35e39c..000000000 --- a/tpl/images/integration_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 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 images_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestImageConfigFromModule(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -baseURL = 'http://example.com/' -theme = ["mytheme"] --- static/images/pixel1.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- themes/mytheme/static/images/pixel2.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{ $path := "static/images/pixel1.png" }} -fileExists OK: {{ fileExists $path }}| -imageConfig OK: {{ (imageConfig $path).Width }}| -{{ $path2 := "static/images/pixel2.png" }} -fileExists2 OK: {{ fileExists $path2 }}| -imageConfig2 OK: {{ (imageConfig $path2).Width }}| - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -fileExists OK: true| -imageConfig OK: 1| -fileExists2 OK: true| -imageConfig2 OK: 1| -`) -} diff --git a/tpl/openapi/openapi3/integration_test.go b/tpl/openapi/openapi3/integration_test.go deleted file mode 100644 index 6914a60b3..000000000 --- a/tpl/openapi/openapi3/integration_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 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 openapi3_test - -import ( - "strings" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestUnmarshal(t *testing.T) { - t.Parallel() - - files := ` --- assets/api/myapi.yaml -- -openapi: 3.0.0 -info: - title: Sample API - description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. - version: 0.1.9 -servers: - - url: http://api.example.com/v1 - description: Optional server description, e.g. Main (production) server - - url: http://staging-api.example.com - description: Optional server description, e.g. Internal staging server for testing -paths: - /users: - get: - summary: Returns a list of users. - description: Optional extended description in CommonMark or HTML. - responses: - '200': # status code - description: A JSON array of user names - content: - application/json: - schema: - type: array - items: - type: string --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -{{ $api := resources.Get "api/myapi.yaml" | openapi3.Unmarshal }} -API: {{ $api.Info.Title | safeHTML }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - Running: true, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", `API: Sample API`) - - b. - EditFileReplaceFunc("assets/api/myapi.yaml", func(s string) string { return strings.ReplaceAll(s, "Sample API", "Hugo API") }). - Build() - - b.AssertFileContent("public/index.html", `API: Hugo API`) -} diff --git a/tpl/openapi/openapi3/openapi3_integration_test.go b/tpl/openapi/openapi3/openapi3_integration_test.go new file mode 100644 index 000000000..6914a60b3 --- /dev/null +++ b/tpl/openapi/openapi3/openapi3_integration_test.go @@ -0,0 +1,74 @@ +// Copyright 2021 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 openapi3_test + +import ( + "strings" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestUnmarshal(t *testing.T) { + t.Parallel() + + files := ` +-- assets/api/myapi.yaml -- +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /users: + get: + summary: Returns a list of users. + description: Optional extended description in CommonMark or HTML. + responses: + '200': # status code + description: A JSON array of user names + content: + application/json: + schema: + type: array + items: + type: string +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ $api := resources.Get "api/myapi.yaml" | openapi3.Unmarshal }} +API: {{ $api.Info.Title | safeHTML }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + Running: true, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", `API: Sample API`) + + b. + EditFileReplaceFunc("assets/api/myapi.yaml", func(s string) string { return strings.ReplaceAll(s, "Sample API", "Hugo API") }). + Build() + + b.AssertFileContent("public/index.html", `API: Hugo API`) +} diff --git a/tpl/os/integration_test.go b/tpl/os/integration_test.go deleted file mode 100644 index 58e0ef70a..000000000 --- a/tpl/os/integration_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2024 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 os_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -// Issue 9599 -func TestReadDirWorkDir(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -theme = "mytheme" --- myproject.txt -- -Hello project! --- themes/mytheme/mytheme.txt -- -Hello theme! --- layouts/index.html -- -{{ $entries := (readDir ".") }} -START:|{{ range $entry := $entries }}{{ if not $entry.IsDir }}{{ $entry.Name }}|{{ end }}{{ end }}:END: - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -START:|config.toml|myproject.txt|:END: -`) -} - -// Issue 9620 -func TestReadFileNotExists(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- layouts/index.html -- -{{ $fi := (readFile "doesnotexist") }} -{{ if $fi }}Failed{{ else }}OK{{ end }} - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -OK -`) -} diff --git a/tpl/os/os_integration_test.go b/tpl/os/os_integration_test.go new file mode 100644 index 000000000..58e0ef70a --- /dev/null +++ b/tpl/os/os_integration_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 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 os_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +// Issue 9599 +func TestReadDirWorkDir(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +theme = "mytheme" +-- myproject.txt -- +Hello project! +-- themes/mytheme/mytheme.txt -- +Hello theme! +-- layouts/index.html -- +{{ $entries := (readDir ".") }} +START:|{{ range $entry := $entries }}{{ if not $entry.IsDir }}{{ $entry.Name }}|{{ end }}{{ end }}:END: + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +START:|config.toml|myproject.txt|:END: +`) +} + +// Issue 9620 +func TestReadFileNotExists(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +{{ $fi := (readFile "doesnotexist") }} +{{ if $fi }}Failed{{ else }}OK{{ end }} + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +OK +`) +} diff --git a/tpl/page/integration_test.go b/tpl/page/integration_test.go deleted file mode 100644 index 632c3b64e..000000000 --- a/tpl/page/integration_test.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2024 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 page_test - -import ( - "fmt" - "strings" - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestThatPageIsAvailableEverywhere(t *testing.T) { - t.Parallel() - - filesTemplate := ` --- config.toml -- -baseURL = 'http://example.com/' -disableKinds = ["taxonomy", "term"] -enableInlineShortcodes = true -paginate = 1 -enableRobotsTXT = true -LANG_CONFIG --- content/_index.md -- ---- -title: "Home" -aliases: ["/homealias/"] ---- -{{< shortcode "Angled Brackets" >}} -{{% shortcode "Percentage" %}} - -{{< outer >}} -{{< inner >}} -{{< /outer >}} - -{{< foo.inline >}}{{ if page.IsHome }}Shortcode Inline OK.{{ end }}{{< /foo.inline >}} - -## Heading - -[I'm an inline-style link](https://www.google.com) - -![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") - -$$$bash -echo "hello"; -$$$ - --- content/p1.md -- --- content/p2/index.md -- --- content/p2/p2_1.md -- ---- -title: "P2_1" ---- -{{< foo.inline >}}{{ if page.IsHome }}Shortcode in bundled page OK.{{ else}}Failed.{{ end }}{{< /foo.inline >}} --- content/p3.md -- --- layouts/_default/_markup/render-heading.html -- -{{ if page.IsHome }} -Heading OK. -{{ end }} --- layouts/_default/_markup/render-image.html -- -{{ if page.IsHome }} -Image OK. -{{ end }} --- layouts/_default/_markup/render-link.html -- -{{ if page.IsHome }} -Link OK. -{{ end }} --- layouts/_default/myview.html -{{ if page.IsHome }} -Render OK. -{{ end }} --- layouts/_default/_markup/render-codeblock.html -- -{{ if page.IsHome }} -Codeblock OK. -{{ end }} --- layouts/_default/single.html -- -Single. --- layouts/index.html -- -{{ if eq page . }}Page OK.{{ end }} -{{ $r := "{{ if page.IsHome }}ExecuteAsTemplate OK.{{ end }}" | resources.FromString "foo.html" | resources.ExecuteAsTemplate "foo.html" . }} -{{ $r.Content }} -{{ .RenderString "{{< renderstring.inline >}}{{ if page.IsHome }}RenderString OK.{{ end }}{{< /renderstring.inline >}}}}"}} -{{ .Render "myview" }} -{{ .Content }} -partial: {{ partials.Include "foo.html" . }} -{{ $pag := (.Paginate site.RegularPages) }} -PageNumber: {{ $pag.PageNumber }}/{{ $pag.TotalPages }}| -{{ $p2 := site.GetPage "p2" }} -{{ $p2_1 := index $p2.Resources 0 }} -Bundled page: {{ $p2_1.Content }} --- layouts/alias.html -- -{{ if eq page .Page }}Alias OK.{{ else }}Failed.{{ end }} --- layouts/404.html -- -{{ if eq page . }}404 Page OK.{{ else }}Failed.{{ end }} --- layouts/partials/foo.html -- -{{ if page.IsHome }}Partial OK.{{ else }}Failed.{{ end }} --- layouts/shortcodes/outer.html -- -{{ .Inner }} --- layouts/shortcodes/inner.html -- -{{ if page.IsHome }}Shortcode Inner OK.{{ else }}Failed.{{ end }} --- layouts/shortcodes/shortcode.html -- -{{ if page.IsHome }}Shortcode {{ .Get 0 }} OK.{{ else }}Failed.{{ end }} --- layouts/sitemap.xml -- -{{ if eq page . }}Sitemap OK.{{ else }}Failed.{{ end }} --- layouts/robots.txt -- -{{ if eq page . }}Robots OK.{{ else }}Failed.{{ end }} --- layouts/sitemapindex.xml -- -{{ with page }}SitemapIndex OK: {{ .Kind }}{{ else }}Failed.{{ end }} - - ` - - for _, multilingual := range []bool{false, true} { - t.Run(fmt.Sprintf("multilingual-%t", multilingual), func(t *testing.T) { - // Fenced code blocks. - files := strings.ReplaceAll(filesTemplate, "$$$", "```") - - if multilingual { - files = strings.ReplaceAll(files, "LANG_CONFIG", ` -[languages] -[languages.en] -weight = 1 -[languages.no] -weight = 2 -`) - } else { - files = strings.ReplaceAll(files, "LANG_CONFIG", "") - } - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -Heading OK. -Image OK. -Link OK. -Codeblock OK. -Page OK. -Partial OK. -Shortcode Angled Brackets OK. -Shortcode Percentage OK. -Shortcode Inner OK. -Shortcode Inline OK. -ExecuteAsTemplate OK. -RenderString OK. -Render OK. -Shortcode in bundled page OK. - `) - - b.AssertFileContent("public/404.html", `404 Page OK.`) - b.AssertFileContent("public/robots.txt", `Robots OK.`) - b.AssertFileContent("public/homealias/index.html", `Alias OK.`) - b.AssertFileContent("public/page/1/index.html", `Alias OK.`) - b.AssertFileContent("public/page/2/index.html", `Page OK.`) - if multilingual { - b.AssertFileContent("public/sitemap.xml", `SitemapIndex OK: sitemapindex`) - } else { - b.AssertFileContent("public/sitemap.xml", `Sitemap OK.`) - } - }) - } -} - -// Issue 10791. -func TestPageTableOfContentsInShortcode(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -disableKinds = ["taxonomy", "term"] --- content/p1.md -- ---- -title: "P1" ---- -{{< toc >}} - -# Heading 1 --- layouts/shortcodes/toc.html -- -{{ page.TableOfContents }} --- layouts/_default/single.html -- -{{ .Content }} -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/p1/index.html", " \n

    Heading 1

    ") -} - -func TestFromStringRunning(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableLiveReload = true --- layouts/index.html -- -{{ with resources.FromString "foo" "{{ seq 3 }}" }} -{{ with resources.ExecuteAsTemplate "bar" $ . }} - {{ .Content | safeHTML }} -{{ end }} -{{ end }} - ` - - b := hugolib.TestRunning(t, files) - - b.AssertFileContent("public/index.html", "1\n2\n3") -} diff --git a/tpl/page/page_integration_test.go b/tpl/page/page_integration_test.go new file mode 100644 index 000000000..632c3b64e --- /dev/null +++ b/tpl/page/page_integration_test.go @@ -0,0 +1,226 @@ +// Copyright 2024 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 page_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestThatPageIsAvailableEverywhere(t *testing.T) { + t.Parallel() + + filesTemplate := ` +-- config.toml -- +baseURL = 'http://example.com/' +disableKinds = ["taxonomy", "term"] +enableInlineShortcodes = true +paginate = 1 +enableRobotsTXT = true +LANG_CONFIG +-- content/_index.md -- +--- +title: "Home" +aliases: ["/homealias/"] +--- +{{< shortcode "Angled Brackets" >}} +{{% shortcode "Percentage" %}} + +{{< outer >}} +{{< inner >}} +{{< /outer >}} + +{{< foo.inline >}}{{ if page.IsHome }}Shortcode Inline OK.{{ end }}{{< /foo.inline >}} + +## Heading + +[I'm an inline-style link](https://www.google.com) + +![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") + +$$$bash +echo "hello"; +$$$ + +-- content/p1.md -- +-- content/p2/index.md -- +-- content/p2/p2_1.md -- +--- +title: "P2_1" +--- +{{< foo.inline >}}{{ if page.IsHome }}Shortcode in bundled page OK.{{ else}}Failed.{{ end }}{{< /foo.inline >}} +-- content/p3.md -- +-- layouts/_default/_markup/render-heading.html -- +{{ if page.IsHome }} +Heading OK. +{{ end }} +-- layouts/_default/_markup/render-image.html -- +{{ if page.IsHome }} +Image OK. +{{ end }} +-- layouts/_default/_markup/render-link.html -- +{{ if page.IsHome }} +Link OK. +{{ end }} +-- layouts/_default/myview.html +{{ if page.IsHome }} +Render OK. +{{ end }} +-- layouts/_default/_markup/render-codeblock.html -- +{{ if page.IsHome }} +Codeblock OK. +{{ end }} +-- layouts/_default/single.html -- +Single. +-- layouts/index.html -- +{{ if eq page . }}Page OK.{{ end }} +{{ $r := "{{ if page.IsHome }}ExecuteAsTemplate OK.{{ end }}" | resources.FromString "foo.html" | resources.ExecuteAsTemplate "foo.html" . }} +{{ $r.Content }} +{{ .RenderString "{{< renderstring.inline >}}{{ if page.IsHome }}RenderString OK.{{ end }}{{< /renderstring.inline >}}}}"}} +{{ .Render "myview" }} +{{ .Content }} +partial: {{ partials.Include "foo.html" . }} +{{ $pag := (.Paginate site.RegularPages) }} +PageNumber: {{ $pag.PageNumber }}/{{ $pag.TotalPages }}| +{{ $p2 := site.GetPage "p2" }} +{{ $p2_1 := index $p2.Resources 0 }} +Bundled page: {{ $p2_1.Content }} +-- layouts/alias.html -- +{{ if eq page .Page }}Alias OK.{{ else }}Failed.{{ end }} +-- layouts/404.html -- +{{ if eq page . }}404 Page OK.{{ else }}Failed.{{ end }} +-- layouts/partials/foo.html -- +{{ if page.IsHome }}Partial OK.{{ else }}Failed.{{ end }} +-- layouts/shortcodes/outer.html -- +{{ .Inner }} +-- layouts/shortcodes/inner.html -- +{{ if page.IsHome }}Shortcode Inner OK.{{ else }}Failed.{{ end }} +-- layouts/shortcodes/shortcode.html -- +{{ if page.IsHome }}Shortcode {{ .Get 0 }} OK.{{ else }}Failed.{{ end }} +-- layouts/sitemap.xml -- +{{ if eq page . }}Sitemap OK.{{ else }}Failed.{{ end }} +-- layouts/robots.txt -- +{{ if eq page . }}Robots OK.{{ else }}Failed.{{ end }} +-- layouts/sitemapindex.xml -- +{{ with page }}SitemapIndex OK: {{ .Kind }}{{ else }}Failed.{{ end }} + + ` + + for _, multilingual := range []bool{false, true} { + t.Run(fmt.Sprintf("multilingual-%t", multilingual), func(t *testing.T) { + // Fenced code blocks. + files := strings.ReplaceAll(filesTemplate, "$$$", "```") + + if multilingual { + files = strings.ReplaceAll(files, "LANG_CONFIG", ` +[languages] +[languages.en] +weight = 1 +[languages.no] +weight = 2 +`) + } else { + files = strings.ReplaceAll(files, "LANG_CONFIG", "") + } + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +Heading OK. +Image OK. +Link OK. +Codeblock OK. +Page OK. +Partial OK. +Shortcode Angled Brackets OK. +Shortcode Percentage OK. +Shortcode Inner OK. +Shortcode Inline OK. +ExecuteAsTemplate OK. +RenderString OK. +Render OK. +Shortcode in bundled page OK. + `) + + b.AssertFileContent("public/404.html", `404 Page OK.`) + b.AssertFileContent("public/robots.txt", `Robots OK.`) + b.AssertFileContent("public/homealias/index.html", `Alias OK.`) + b.AssertFileContent("public/page/1/index.html", `Alias OK.`) + b.AssertFileContent("public/page/2/index.html", `Page OK.`) + if multilingual { + b.AssertFileContent("public/sitemap.xml", `SitemapIndex OK: sitemapindex`) + } else { + b.AssertFileContent("public/sitemap.xml", `Sitemap OK.`) + } + }) + } +} + +// Issue 10791. +func TestPageTableOfContentsInShortcode(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +disableKinds = ["taxonomy", "term"] +-- content/p1.md -- +--- +title: "P1" +--- +{{< toc >}} + +# Heading 1 +-- layouts/shortcodes/toc.html -- +{{ page.TableOfContents }} +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", " \n

    Heading 1

    ") +} + +func TestFromStringRunning(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableLiveReload = true +-- layouts/index.html -- +{{ with resources.FromString "foo" "{{ seq 3 }}" }} +{{ with resources.ExecuteAsTemplate "bar" $ . }} + {{ .Content | safeHTML }} +{{ end }} +{{ end }} + ` + + b := hugolib.TestRunning(t, files) + + b.AssertFileContent("public/index.html", "1\n2\n3") +} diff --git a/tpl/partials/integration_test.go b/tpl/partials/integration_test.go deleted file mode 100644 index e48f3bb20..000000000 --- a/tpl/partials/integration_test.go +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2024 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 partials_test - -import ( - "bytes" - "fmt" - "regexp" - "sort" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - - "github.com/gohugoio/hugo/htesting/hqt" - "github.com/gohugoio/hugo/hugolib" -) - -func TestInclude(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -partial: {{ partials.Include "foo.html" . }} --- layouts/partials/foo.html -- -foo - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -partial: foo -`) -} - -func TestIncludeCached(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -partialCached: {{ partials.IncludeCached "foo.html" . }} -partialCached: {{ partials.IncludeCached "foo.html" . }} --- layouts/partials/foo.html -- -foo - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -partialCached: foo -partialCached: foo -`) -} - -// Issue 9519 -func TestIncludeCachedRecursion(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -{{ partials.IncludeCached "p1.html" . }} --- layouts/partials/p1.html -- -{{ partials.IncludeCached "p2.html" . }} --- layouts/partials/p2.html -- -P2 - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -P2 -`) -} - -// Issue #588 -func TestIncludeCachedRecursionShortcode(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- content/_index.md -- ---- -title: "Index" ---- -{{< short >}} --- layouts/index.html -- -{{ partials.IncludeCached "p1.html" . }} --- layouts/partials/p1.html -- -{{ .Content }} -{{ partials.IncludeCached "p2.html" . }} --- layouts/partials/p2.html -- --- layouts/shortcodes/short.html -- -SHORT -{{ partials.IncludeCached "p2.html" . }} -P2 - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -SHORT -P2 -`) -} - -func TestIncludeCacheHints(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -templateMetrics=true -templateMetricsHints=true -disableKinds = ["page", "section", "taxonomy", "term", "sitemap"] -[outputs] -home = ["HTML"] --- layouts/index.html -- -{{ partials.IncludeCached "static1.html" . }} -{{ partials.IncludeCached "static1.html" . }} -{{ partials.Include "static2.html" . }} - -D1I: {{ partials.Include "dynamic1.html" . }} -D1C: {{ partials.IncludeCached "dynamic1.html" . }} -D1C: {{ partials.IncludeCached "dynamic1.html" . }} -D1C: {{ partials.IncludeCached "dynamic1.html" . }} -H1I: {{ partials.Include "halfdynamic1.html" . }} -H1C: {{ partials.IncludeCached "halfdynamic1.html" . }} -H1C: {{ partials.IncludeCached "halfdynamic1.html" . }} - --- layouts/partials/static1.html -- -P1 --- layouts/partials/static2.html -- -P2 --- layouts/partials/dynamic1.html -- -{{ math.Counter }} --- layouts/partials/halfdynamic1.html -- -D1 -{{ math.Counter }} - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - // fmt.Println(b.FileContent("public/index.html")) - - var buf bytes.Buffer - b.H.Metrics.WriteMetrics(&buf) - - got := buf.String() - - // Get rid of all the durations, they are never the same. - durationRe := regexp.MustCompile(`\b[\.\d]*(ms|µs|s)\b`) - - normalize := func(s string) string { - s = durationRe.ReplaceAllString(s, "") - linesIn := strings.Split(s, "\n")[3:] - var lines []string - for _, l := range linesIn { - l = strings.TrimSpace(l) - if l == "" { - continue - } - lines = append(lines, l) - } - - sort.Strings(lines) - - return strings.Join(lines, "\n") - } - - got = normalize(got) - - expect := ` - 0 0 0 1 index.html - 100 0 0 1 partials/static2.html - 100 50 1 2 partials/static1.html - 25 50 2 4 partials/dynamic1.html - 66 33 1 3 partials/halfdynamic1.html - ` - - b.Assert(got, hqt.IsSameString, expect) -} - -// gobench --package ./tpl/partials -func BenchmarkIncludeCached(b *testing.B) { - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- --- layouts/_default/single.html -- -{{ partialCached "heavy.html" "foo" }} -{{ partialCached "easy1.html" "bar" }} -{{ partialCached "easy1.html" "baz" }} -{{ partialCached "easy2.html" "baz" }} --- layouts/partials/easy1.html -- -ABCD --- layouts/partials/easy2.html -- -ABCDE --- layouts/partials/heavy.html -- -{{ $result := slice }} -{{ range site.RegularPages }} -{{ $result = $result | append (dict "title" .Title "link" .RelPermalink "readingTime" .ReadingTime) }} -{{ end }} -{{ range $result }} -* {{ .title }} {{ .link }} {{ .readingTime }} -{{ end }} - - -` - - for i := 1; i < 100; i++ { - files += fmt.Sprintf("\n-- content/p%d.md --\n---\ntitle: page\n---\n"+strings.Repeat("FOO ", i), i) - } - - cfg := hugolib.IntegrationTestConfig{ - T: b, - TxtarString: files, - } - builders := make([]*hugolib.IntegrationTestBuilder, b.N) - - for i := range builders { - builders[i] = hugolib.NewIntegrationTestBuilder(cfg) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - builders[i].Build() - } -} - -func TestIncludeTimeout(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -timeout = '200ms' --- layouts/index.html -- -{{ partials.Include "foo.html" . }} --- layouts/partials/foo.html -- -{{ partial "foo.html" . }} - ` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - - b.Assert(err, qt.Not(qt.IsNil)) - b.Assert(err.Error(), qt.Contains, "timed out") -} - -func TestIncludeCachedTimeout(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -timeout = '200ms' --- layouts/index.html -- -{{ partials.IncludeCached "foo.html" . }} --- layouts/partials/foo.html -- -{{ partialCached "foo.html" . }} - ` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - - b.Assert(err, qt.Not(qt.IsNil)) - b.Assert(err.Error(), qt.Contains, "timed out") -} - -// See Issue #10789 -func TestReturnExecuteFromTemplateInPartial(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -{{ $r := partial "foo" }} -FOO:{{ $r.Content }} --- layouts/partials/foo.html -- -{{ $r := §§{{ partial "bar" }}§§ | resources.FromString "bar.html" | resources.ExecuteAsTemplate "bar.html" . }} -{{ return $r }} --- layouts/partials/bar.html -- -BAR - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", "OO:BAR") -} diff --git a/tpl/partials/partials_integration_test.go b/tpl/partials/partials_integration_test.go new file mode 100644 index 000000000..e48f3bb20 --- /dev/null +++ b/tpl/partials/partials_integration_test.go @@ -0,0 +1,351 @@ +// Copyright 2024 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 partials_test + +import ( + "bytes" + "fmt" + "regexp" + "sort" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/htesting/hqt" + "github.com/gohugoio/hugo/hugolib" +) + +func TestInclude(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +partial: {{ partials.Include "foo.html" . }} +-- layouts/partials/foo.html -- +foo + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +partial: foo +`) +} + +func TestIncludeCached(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +partialCached: {{ partials.IncludeCached "foo.html" . }} +partialCached: {{ partials.IncludeCached "foo.html" . }} +-- layouts/partials/foo.html -- +foo + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +partialCached: foo +partialCached: foo +`) +} + +// Issue 9519 +func TestIncludeCachedRecursion(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ partials.IncludeCached "p1.html" . }} +-- layouts/partials/p1.html -- +{{ partials.IncludeCached "p2.html" . }} +-- layouts/partials/p2.html -- +P2 + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +P2 +`) +} + +// Issue #588 +func TestIncludeCachedRecursionShortcode(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- content/_index.md -- +--- +title: "Index" +--- +{{< short >}} +-- layouts/index.html -- +{{ partials.IncludeCached "p1.html" . }} +-- layouts/partials/p1.html -- +{{ .Content }} +{{ partials.IncludeCached "p2.html" . }} +-- layouts/partials/p2.html -- +-- layouts/shortcodes/short.html -- +SHORT +{{ partials.IncludeCached "p2.html" . }} +P2 + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +SHORT +P2 +`) +} + +func TestIncludeCacheHints(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +templateMetrics=true +templateMetricsHints=true +disableKinds = ["page", "section", "taxonomy", "term", "sitemap"] +[outputs] +home = ["HTML"] +-- layouts/index.html -- +{{ partials.IncludeCached "static1.html" . }} +{{ partials.IncludeCached "static1.html" . }} +{{ partials.Include "static2.html" . }} + +D1I: {{ partials.Include "dynamic1.html" . }} +D1C: {{ partials.IncludeCached "dynamic1.html" . }} +D1C: {{ partials.IncludeCached "dynamic1.html" . }} +D1C: {{ partials.IncludeCached "dynamic1.html" . }} +H1I: {{ partials.Include "halfdynamic1.html" . }} +H1C: {{ partials.IncludeCached "halfdynamic1.html" . }} +H1C: {{ partials.IncludeCached "halfdynamic1.html" . }} + +-- layouts/partials/static1.html -- +P1 +-- layouts/partials/static2.html -- +P2 +-- layouts/partials/dynamic1.html -- +{{ math.Counter }} +-- layouts/partials/halfdynamic1.html -- +D1 +{{ math.Counter }} + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + // fmt.Println(b.FileContent("public/index.html")) + + var buf bytes.Buffer + b.H.Metrics.WriteMetrics(&buf) + + got := buf.String() + + // Get rid of all the durations, they are never the same. + durationRe := regexp.MustCompile(`\b[\.\d]*(ms|µs|s)\b`) + + normalize := func(s string) string { + s = durationRe.ReplaceAllString(s, "") + linesIn := strings.Split(s, "\n")[3:] + var lines []string + for _, l := range linesIn { + l = strings.TrimSpace(l) + if l == "" { + continue + } + lines = append(lines, l) + } + + sort.Strings(lines) + + return strings.Join(lines, "\n") + } + + got = normalize(got) + + expect := ` + 0 0 0 1 index.html + 100 0 0 1 partials/static2.html + 100 50 1 2 partials/static1.html + 25 50 2 4 partials/dynamic1.html + 66 33 1 3 partials/halfdynamic1.html + ` + + b.Assert(got, hqt.IsSameString, expect) +} + +// gobench --package ./tpl/partials +func BenchmarkIncludeCached(b *testing.B) { + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +-- layouts/_default/single.html -- +{{ partialCached "heavy.html" "foo" }} +{{ partialCached "easy1.html" "bar" }} +{{ partialCached "easy1.html" "baz" }} +{{ partialCached "easy2.html" "baz" }} +-- layouts/partials/easy1.html -- +ABCD +-- layouts/partials/easy2.html -- +ABCDE +-- layouts/partials/heavy.html -- +{{ $result := slice }} +{{ range site.RegularPages }} +{{ $result = $result | append (dict "title" .Title "link" .RelPermalink "readingTime" .ReadingTime) }} +{{ end }} +{{ range $result }} +* {{ .title }} {{ .link }} {{ .readingTime }} +{{ end }} + + +` + + for i := 1; i < 100; i++ { + files += fmt.Sprintf("\n-- content/p%d.md --\n---\ntitle: page\n---\n"+strings.Repeat("FOO ", i), i) + } + + cfg := hugolib.IntegrationTestConfig{ + T: b, + TxtarString: files, + } + builders := make([]*hugolib.IntegrationTestBuilder, b.N) + + for i := range builders { + builders[i] = hugolib.NewIntegrationTestBuilder(cfg) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + builders[i].Build() + } +} + +func TestIncludeTimeout(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +timeout = '200ms' +-- layouts/index.html -- +{{ partials.Include "foo.html" . }} +-- layouts/partials/foo.html -- +{{ partial "foo.html" . }} + ` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.Not(qt.IsNil)) + b.Assert(err.Error(), qt.Contains, "timed out") +} + +func TestIncludeCachedTimeout(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +timeout = '200ms' +-- layouts/index.html -- +{{ partials.IncludeCached "foo.html" . }} +-- layouts/partials/foo.html -- +{{ partialCached "foo.html" . }} + ` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.Not(qt.IsNil)) + b.Assert(err.Error(), qt.Contains, "timed out") +} + +// See Issue #10789 +func TestReturnExecuteFromTemplateInPartial(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ $r := partial "foo" }} +FOO:{{ $r.Content }} +-- layouts/partials/foo.html -- +{{ $r := §§{{ partial "bar" }}§§ | resources.FromString "bar.html" | resources.ExecuteAsTemplate "bar.html" . }} +{{ return $r }} +-- layouts/partials/bar.html -- +BAR + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", "OO:BAR") +} diff --git a/tpl/resources/integration_test.go b/tpl/resources/integration_test.go deleted file mode 100644 index 02aa5d29d..000000000 --- a/tpl/resources/integration_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2022s 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 resources_test - -import ( - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" -) - -func TestCopy(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = "http://example.com/blog" --- assets/images/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{/* Image resources */}} -{{ $img := resources.Get "images/pixel.png" }} -{{ $imgCopy1 := $img | resources.Copy "images/copy.png" }} -{{ $imgCopy1 = $imgCopy1.Resize "3x4"}} -{{ $imgCopy2 := $imgCopy1 | resources.Copy "images/copy2.png" }} -{{ $imgCopy3 := $imgCopy1 | resources.Copy "images/copy3.png" }} -Image Orig: {{ $img.RelPermalink}}|{{ $img.MediaType }}|{{ $img.Width }}|{{ $img.Height }}| -Image Copy1: {{ $imgCopy1.RelPermalink}}|{{ $imgCopy1.MediaType }}|{{ $imgCopy1.Width }}|{{ $imgCopy1.Height }}| -Image Copy2: {{ $imgCopy2.RelPermalink}}|{{ $imgCopy2.MediaType }}|{{ $imgCopy2.Width }}|{{ $imgCopy2.Height }}| -Image Copy3: {{ $imgCopy3.MediaType }}|{{ $imgCopy3.Width }}|{{ $imgCopy3.Height }}| - -{{/* Generic resources */}} -{{ $targetPath := "js/vars.js" }} -{{ $orig := "let foo;" | resources.FromString "js/foo.js" }} -{{ $copy1 := $orig | resources.Copy "js/copies/bar.js" }} -{{ $copy2 := $orig | resources.Copy "js/copies/baz.js" | fingerprint "md5" }} -{{ $copy3 := $copy2 | resources.Copy "js/copies/moo.js" | minify }} - -Orig: {{ $orig.RelPermalink}}|{{ $orig.MediaType }}|{{ $orig.Content | safeJS }}| -Copy1: {{ $copy1.RelPermalink}}|{{ $copy1.MediaType }}|{{ $copy1.Content | safeJS }}| -Copy2: {{ $copy2.RelPermalink}}|{{ $copy2.MediaType }}|{{ $copy2.Content | safeJS }}| -Copy3: {{ $copy3.RelPermalink}}|{{ $copy3.MediaType }}|{{ $copy3.Content | safeJS }}| - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/index.html", ` -Image Orig: /blog/images/pixel.png|image/png|1|1| -Image Copy1: /blog/images/copy_hu8aa3346827e49d756ff4e630147c42b5_70_3x4_resize_box_3.png|image/png|3|4| -Image Copy2: /blog/images/copy2.png|image/png|3|4 -Image Copy3: image/png|3|4| -Orig: /blog/js/foo.js|text/javascript|let foo;| -Copy1: /blog/js/copies/bar.js|text/javascript|let foo;| -Copy2: /blog/js/copies/baz.a677329fc6c4ad947e0c7116d91f37a2.js|text/javascript|let foo;| -Copy3: /blog/js/copies/moo.a677329fc6c4ad947e0c7116d91f37a2.min.js|text/javascript|let foo| - - `) - - b.AssertFileExists("public/images/copy2.png", true) - // No permalink used. - b.AssertFileExists("public/images/copy3.png", false) -} - -func TestCopyPageShouldFail(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- layouts/index.html -- -{{/* This is currently not supported. */}} -{{ $copy := .Copy "copy.md" }} - - ` - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).BuildE() - - b.Assert(err, qt.IsNotNil) -} - -func TestGet(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = "http://example.com/blog" --- assets/images/pixel.png -- -iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== --- layouts/index.html -- -{{ with resources.Get "images/pixel.png" }}Image OK{{ else }}Image not found{{ end }} -{{ with resources.Get "" }}Failed{{ else }}Empty string not found{{ end }} - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }).Build() - - b.AssertFileContent("public/index.html", ` -Image OK -Empty string not found - - `) -} diff --git a/tpl/resources/resources_integration_test.go b/tpl/resources/resources_integration_test.go new file mode 100644 index 000000000..02aa5d29d --- /dev/null +++ b/tpl/resources/resources_integration_test.go @@ -0,0 +1,126 @@ +// Copyright 2022s 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 resources_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +func TestCopy(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = "http://example.com/blog" +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{/* Image resources */}} +{{ $img := resources.Get "images/pixel.png" }} +{{ $imgCopy1 := $img | resources.Copy "images/copy.png" }} +{{ $imgCopy1 = $imgCopy1.Resize "3x4"}} +{{ $imgCopy2 := $imgCopy1 | resources.Copy "images/copy2.png" }} +{{ $imgCopy3 := $imgCopy1 | resources.Copy "images/copy3.png" }} +Image Orig: {{ $img.RelPermalink}}|{{ $img.MediaType }}|{{ $img.Width }}|{{ $img.Height }}| +Image Copy1: {{ $imgCopy1.RelPermalink}}|{{ $imgCopy1.MediaType }}|{{ $imgCopy1.Width }}|{{ $imgCopy1.Height }}| +Image Copy2: {{ $imgCopy2.RelPermalink}}|{{ $imgCopy2.MediaType }}|{{ $imgCopy2.Width }}|{{ $imgCopy2.Height }}| +Image Copy3: {{ $imgCopy3.MediaType }}|{{ $imgCopy3.Width }}|{{ $imgCopy3.Height }}| + +{{/* Generic resources */}} +{{ $targetPath := "js/vars.js" }} +{{ $orig := "let foo;" | resources.FromString "js/foo.js" }} +{{ $copy1 := $orig | resources.Copy "js/copies/bar.js" }} +{{ $copy2 := $orig | resources.Copy "js/copies/baz.js" | fingerprint "md5" }} +{{ $copy3 := $copy2 | resources.Copy "js/copies/moo.js" | minify }} + +Orig: {{ $orig.RelPermalink}}|{{ $orig.MediaType }}|{{ $orig.Content | safeJS }}| +Copy1: {{ $copy1.RelPermalink}}|{{ $copy1.MediaType }}|{{ $copy1.Content | safeJS }}| +Copy2: {{ $copy2.RelPermalink}}|{{ $copy2.MediaType }}|{{ $copy2.Content | safeJS }}| +Copy3: {{ $copy3.RelPermalink}}|{{ $copy3.MediaType }}|{{ $copy3.Content | safeJS }}| + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/index.html", ` +Image Orig: /blog/images/pixel.png|image/png|1|1| +Image Copy1: /blog/images/copy_hu8aa3346827e49d756ff4e630147c42b5_70_3x4_resize_box_3.png|image/png|3|4| +Image Copy2: /blog/images/copy2.png|image/png|3|4 +Image Copy3: image/png|3|4| +Orig: /blog/js/foo.js|text/javascript|let foo;| +Copy1: /blog/js/copies/bar.js|text/javascript|let foo;| +Copy2: /blog/js/copies/baz.a677329fc6c4ad947e0c7116d91f37a2.js|text/javascript|let foo;| +Copy3: /blog/js/copies/moo.a677329fc6c4ad947e0c7116d91f37a2.min.js|text/javascript|let foo| + + `) + + b.AssertFileExists("public/images/copy2.png", true) + // No permalink used. + b.AssertFileExists("public/images/copy3.png", false) +} + +func TestCopyPageShouldFail(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +{{/* This is currently not supported. */}} +{{ $copy := .Copy "copy.md" }} + + ` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).BuildE() + + b.Assert(err, qt.IsNotNil) +} + +func TestGet(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = "http://example.com/blog" +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ with resources.Get "images/pixel.png" }}Image OK{{ else }}Image not found{{ end }} +{{ with resources.Get "" }}Failed{{ else }}Empty string not found{{ end }} + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/index.html", ` +Image OK +Empty string not found + + `) +} diff --git a/tpl/templates/integration_test.go b/tpl/templates/integration_test.go deleted file mode 100644 index 7e0bcc824..000000000 --- a/tpl/templates/integration_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2024 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 templates_test - -import ( - "testing" - - "github.com/gohugoio/hugo/hugolib" -) - -func TestExists(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -index.html: {{ templates.Exists "index.html" }} -post/single.html: {{ templates.Exists "post/single.html" }} -partials/foo.html: {{ templates.Exists "partials/foo.html" }} -partials/doesnotexist.html: {{ templates.Exists "partials/doesnotexist.html" }} --- layouts/post/single.html -- --- layouts/partials/foo.html -- - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -index.html: true -post/single.html: true -partials/foo.html: true -partials/doesnotexist.html: false -`) -} - -func TestExistsWithBaseOf(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/baseof.html -- -{{ block "main" . }}{{ end }} --- layouts/index.html -- -{{ define "main" }} -index.html: {{ templates.Exists "index.html" }} -post/single.html: {{ templates.Exists "post/single.html" }} -post/doesnotexist.html: {{ templates.Exists "post/doesnotexist.html" }} -{{ end }} --- layouts/post/single.html -- -{{ define "main" }}MAIN{{ end }} - - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -index.html: true -post/single.html: true -post/doesnotexist.html: false - -`) -} - -// See #10774 -func TestPageFunctionExists(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- layouts/index.html -- -Home: {{ page.IsHome }} - -` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).Build() - - b.AssertFileContent("public/index.html", ` -Home: true - -`) -} diff --git a/tpl/templates/templates_integration_test.go b/tpl/templates/templates_integration_test.go new file mode 100644 index 000000000..7e0bcc824 --- /dev/null +++ b/tpl/templates/templates_integration_test.go @@ -0,0 +1,110 @@ +// Copyright 2024 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 templates_test + +import ( + "testing" + + "github.com/gohugoio/hugo/hugolib" +) + +func TestExists(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +index.html: {{ templates.Exists "index.html" }} +post/single.html: {{ templates.Exists "post/single.html" }} +partials/foo.html: {{ templates.Exists "partials/foo.html" }} +partials/doesnotexist.html: {{ templates.Exists "partials/doesnotexist.html" }} +-- layouts/post/single.html -- +-- layouts/partials/foo.html -- + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +index.html: true +post/single.html: true +partials/foo.html: true +partials/doesnotexist.html: false +`) +} + +func TestExistsWithBaseOf(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/baseof.html -- +{{ block "main" . }}{{ end }} +-- layouts/index.html -- +{{ define "main" }} +index.html: {{ templates.Exists "index.html" }} +post/single.html: {{ templates.Exists "post/single.html" }} +post/doesnotexist.html: {{ templates.Exists "post/doesnotexist.html" }} +{{ end }} +-- layouts/post/single.html -- +{{ define "main" }}MAIN{{ end }} + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +index.html: true +post/single.html: true +post/doesnotexist.html: false + +`) +} + +// See #10774 +func TestPageFunctionExists(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +Home: {{ page.IsHome }} + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", ` +Home: true + +`) +} diff --git a/tpl/tplimpl/integration_test.go b/tpl/tplimpl/integration_test.go deleted file mode 100644 index 1b2cffce6..000000000 --- a/tpl/tplimpl/integration_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package tplimpl_test - -import ( - "path/filepath" - "strings" - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/tpl" -) - -func TestPrintUnusedTemplates(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -printUnusedTemplates=true --- content/p1.md -- ---- -title: "P1" ---- -{{< usedshortcode >}} --- layouts/baseof.html -- -{{ block "main" . }}{{ end }} --- layouts/baseof.json -- -{{ block "main" . }}{{ end }} --- layouts/index.html -- -{{ define "main" }}FOO{{ end }} --- layouts/_default/single.json -- --- layouts/_default/single.html -- -{{ define "main" }}MAIN{{ end }} --- layouts/post/single.html -- -{{ define "main" }}MAIN{{ end }} --- layouts/partials/usedpartial.html -- --- layouts/partials/unusedpartial.html -- --- layouts/shortcodes/usedshortcode.html -- -{{ partial "usedpartial.html" }} --- layouts/shortcodes/unusedshortcode.html -- - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ) - b.Build() - - unused := b.H.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates() - - var names []string - for _, tmpl := range unused { - names = append(names, tmpl.Name()) - } - - b.Assert(names, qt.DeepEquals, []string{"_default/single.json", "baseof.json", "partials/unusedpartial.html", "post/single.html", "shortcodes/unusedshortcode.html"}) - b.Assert(unused[0].Filename(), qt.Equals, filepath.Join(b.Cfg.WorkingDir, "layouts/_default/single.json")) -} - -// Verify that the new keywords in Go 1.18 is available. -func TestGo18Constructs(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' -disableKinds = ["section", "home", "rss", "taxonomy", "term", "rss"] --- content/p1.md -- ---- -title: "P1" ---- --- layouts/partials/counter.html -- -{{ if .Scratch.Get "counter" }}{{ .Scratch.Add "counter" 1 }}{{ else }}{{ .Scratch.Set "counter" 1 }}{{ end }}{{ return true }} --- layouts/_default/single.html -- -continue:{{ range seq 5 }}{{ if eq . 2 }}{{continue}}{{ end }}{{ . }}{{ end }}:END: -break:{{ range seq 5 }}{{ if eq . 2 }}{{break}}{{ end }}{{ . }}{{ end }}:END: -continue2:{{ range seq 5 }}{{ if eq . 2 }}{{ continue }}{{ end }}{{ . }}{{ end }}:END: -break2:{{ range seq 5 }}{{ if eq . 2 }}{{ break }}{{ end }}{{ . }}{{ end }}:END: - -counter1: {{ partial "counter.html" . }}/{{ .Scratch.Get "counter" }} -and1: {{ if (and false (partial "counter.html" .)) }}true{{ else }}false{{ end }} -or1: {{ if (or true (partial "counter.html" .)) }}true{{ else }}false{{ end }} -and2: {{ if (and true (partial "counter.html" .)) }}true{{ else }}false{{ end }} -or2: {{ if (or false (partial "counter.html" .)) }}true{{ else }}false{{ end }} - - -counter2: {{ .Scratch.Get "counter" }} - - - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - NeedsOsFS: true, - }, - ) - b.Build() - - b.AssertFileContent("public/p1/index.html", ` -continue:1345:END: -break:1:END: -continue2:1345:END: -break2:1:END: -counter1: true/1 -and1: false -or1: true -and2: true -or2: true -counter2: 3 -`) -} - -// Issue 10495 -func TestCommentsBeforeBlockDefinition(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -baseURL = 'http://example.com/' --- content/s1/p1.md -- ---- -title: "S1P1" ---- --- content/s2/p1.md -- ---- -title: "S2P1" ---- --- content/s3/p1.md -- ---- -title: "S3P1" ---- --- layouts/_default/baseof.html -- -{{ block "main" . }}{{ end }} --- layouts/s1/single.html -- -{{/* foo */}} -{{ define "main" }}{{ .Title }}{{ end }} --- layouts/s2/single.html -- -{{- /* foo */}} -{{ define "main" }}{{ .Title }}{{ end }} --- layouts/s3/single.html -- -{{- /* foo */ -}} -{{ define "main" }}{{ .Title }}{{ end }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - b.Build() - - b.AssertFileContent("public/s1/p1/index.html", `S1P1`) - b.AssertFileContent("public/s2/p1/index.html", `S2P1`) - b.AssertFileContent("public/s3/p1/index.html", `S3P1`) -} - -func TestGoTemplateBugs(t *testing.T) { - t.Run("Issue 11112", func(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- --- layouts/index.html -- -{{ $m := dict "key" "value" }} -{{ $k := "" }} -{{ $v := "" }} -{{ range $k, $v = $m }} -{{ $k }} = {{ $v }} -{{ end }} - ` - - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - b.Build() - - b.AssertFileContent("public/index.html", `key = value`) - }) -} - -func TestSecurityAllowActionJSTmpl(t *testing.T) { - filesTemplate := ` --- config.toml -- -SECURITYCONFIG --- layouts/index.html -- - - ` - - files := strings.ReplaceAll(filesTemplate, "SECURITYCONFIG", "") - - b, err := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ).BuildE() - - // This used to fail, but not in >= Hugo 0.121.0. - b.Assert(err, qt.IsNil) -} diff --git a/tpl/tplimpl/tplimpl_integration_test.go b/tpl/tplimpl/tplimpl_integration_test.go new file mode 100644 index 000000000..1b2cffce6 --- /dev/null +++ b/tpl/tplimpl/tplimpl_integration_test.go @@ -0,0 +1,212 @@ +package tplimpl_test + +import ( + "path/filepath" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/tpl" +) + +func TestPrintUnusedTemplates(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +printUnusedTemplates=true +-- content/p1.md -- +--- +title: "P1" +--- +{{< usedshortcode >}} +-- layouts/baseof.html -- +{{ block "main" . }}{{ end }} +-- layouts/baseof.json -- +{{ block "main" . }}{{ end }} +-- layouts/index.html -- +{{ define "main" }}FOO{{ end }} +-- layouts/_default/single.json -- +-- layouts/_default/single.html -- +{{ define "main" }}MAIN{{ end }} +-- layouts/post/single.html -- +{{ define "main" }}MAIN{{ end }} +-- layouts/partials/usedpartial.html -- +-- layouts/partials/unusedpartial.html -- +-- layouts/shortcodes/usedshortcode.html -- +{{ partial "usedpartial.html" }} +-- layouts/shortcodes/unusedshortcode.html -- + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ) + b.Build() + + unused := b.H.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates() + + var names []string + for _, tmpl := range unused { + names = append(names, tmpl.Name()) + } + + b.Assert(names, qt.DeepEquals, []string{"_default/single.json", "baseof.json", "partials/unusedpartial.html", "post/single.html", "shortcodes/unusedshortcode.html"}) + b.Assert(unused[0].Filename(), qt.Equals, filepath.Join(b.Cfg.WorkingDir, "layouts/_default/single.json")) +} + +// Verify that the new keywords in Go 1.18 is available. +func TestGo18Constructs(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +disableKinds = ["section", "home", "rss", "taxonomy", "term", "rss"] +-- content/p1.md -- +--- +title: "P1" +--- +-- layouts/partials/counter.html -- +{{ if .Scratch.Get "counter" }}{{ .Scratch.Add "counter" 1 }}{{ else }}{{ .Scratch.Set "counter" 1 }}{{ end }}{{ return true }} +-- layouts/_default/single.html -- +continue:{{ range seq 5 }}{{ if eq . 2 }}{{continue}}{{ end }}{{ . }}{{ end }}:END: +break:{{ range seq 5 }}{{ if eq . 2 }}{{break}}{{ end }}{{ . }}{{ end }}:END: +continue2:{{ range seq 5 }}{{ if eq . 2 }}{{ continue }}{{ end }}{{ . }}{{ end }}:END: +break2:{{ range seq 5 }}{{ if eq . 2 }}{{ break }}{{ end }}{{ . }}{{ end }}:END: + +counter1: {{ partial "counter.html" . }}/{{ .Scratch.Get "counter" }} +and1: {{ if (and false (partial "counter.html" .)) }}true{{ else }}false{{ end }} +or1: {{ if (or true (partial "counter.html" .)) }}true{{ else }}false{{ end }} +and2: {{ if (and true (partial "counter.html" .)) }}true{{ else }}false{{ end }} +or2: {{ if (or false (partial "counter.html" .)) }}true{{ else }}false{{ end }} + + +counter2: {{ .Scratch.Get "counter" }} + + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + }, + ) + b.Build() + + b.AssertFileContent("public/p1/index.html", ` +continue:1345:END: +break:1:END: +continue2:1345:END: +break2:1:END: +counter1: true/1 +and1: false +or1: true +and2: true +or2: true +counter2: 3 +`) +} + +// Issue 10495 +func TestCommentsBeforeBlockDefinition(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- content/s1/p1.md -- +--- +title: "S1P1" +--- +-- content/s2/p1.md -- +--- +title: "S2P1" +--- +-- content/s3/p1.md -- +--- +title: "S3P1" +--- +-- layouts/_default/baseof.html -- +{{ block "main" . }}{{ end }} +-- layouts/s1/single.html -- +{{/* foo */}} +{{ define "main" }}{{ .Title }}{{ end }} +-- layouts/s2/single.html -- +{{- /* foo */}} +{{ define "main" }}{{ .Title }}{{ end }} +-- layouts/s3/single.html -- +{{- /* foo */ -}} +{{ define "main" }}{{ .Title }}{{ end }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + b.Build() + + b.AssertFileContent("public/s1/p1/index.html", `S1P1`) + b.AssertFileContent("public/s2/p1/index.html", `S2P1`) + b.AssertFileContent("public/s3/p1/index.html", `S3P1`) +} + +func TestGoTemplateBugs(t *testing.T) { + t.Run("Issue 11112", func(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +{{ $m := dict "key" "value" }} +{{ $k := "" }} +{{ $v := "" }} +{{ range $k, $v = $m }} +{{ $k }} = {{ $v }} +{{ end }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + b.Build() + + b.AssertFileContent("public/index.html", `key = value`) + }) +} + +func TestSecurityAllowActionJSTmpl(t *testing.T) { + filesTemplate := ` +-- config.toml -- +SECURITYCONFIG +-- layouts/index.html -- + + ` + + files := strings.ReplaceAll(filesTemplate, "SECURITYCONFIG", "") + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + // This used to fail, but not in >= Hugo 0.121.0. + b.Assert(err, qt.IsNil) +} diff --git a/tpl/transform/integration_test.go b/tpl/transform/integration_test.go deleted file mode 100644 index 351420a67..000000000 --- a/tpl/transform/integration_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2024 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 transform_test - -import ( - "testing" - - qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/hugolib" -) - -// Issue #11698 -func TestMarkdownifyIssue11698(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -disableKinds = ['home','section','rss','sitemap','taxonomy','term'] -[markup.goldmark.parser.attribute] -title = true -block = true --- layouts/_default/single.html -- -_{{ markdownify .RawContent }}_ --- content/p1.md -- ---- -title: p1 ---- -foo bar --- content/p2.md -- ---- -title: p2 ---- -foo - -**bar** --- content/p3.md -- ---- -title: p3 ---- -## foo - -bar --- content/p4.md -- ---- -title: p4 ---- -foo -{#bar} - ` - - b := hugolib.Test(t, files) - - b.AssertFileContent("public/p1/index.html", "_foo bar_") - b.AssertFileContent("public/p2/index.html", "_

    foo

    \n

    bar

    \n_") - b.AssertFileContent("public/p3/index.html", "_

    foo

    \n

    bar

    \n_") - b.AssertFileContent("public/p4/index.html", "_

    foo

    \n_") -} - -func TestXMLEscape(t *testing.T) { - t.Parallel() - - files := ` --- config.toml -- -disableKinds = ['section','sitemap','taxonomy','term'] --- content/p1.md -- ---- -title: p1 ---- -a **b** ` + "\v" + ` c - - ` - b := hugolib.Test(t, files) - - b.AssertFileContent("public/index.xml", ` - <p>a <strong>b</strong> c</p> - `) -} - -// Issue #9642 -func TestHighlightError(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ['page','rss','section','sitemap','taxonomy','term'] --- layouts/index.html -- -{{ highlight "a" "b" 0 }} - ` - b := hugolib.NewIntegrationTestBuilder( - hugolib.IntegrationTestConfig{ - T: t, - TxtarString: files, - }, - ) - - _, err := b.BuildE() - b.Assert(err.Error(), qt.Contains, "error calling highlight: invalid Highlight option: 0") -} - -// Issue #11884 -func TestUnmarshalCSVLazyDecoding(t *testing.T) { - t.Parallel() - - files := ` --- hugo.toml -- -disableKinds = ['page','rss','section','sitemap','taxonomy','term'] --- assets/pets.csv -- -name,description,age -Spot,a nice dog,3 -Rover,"a big dog",5 -Felix,a "malicious" cat,7 -Bella,"an "evil" cat",9 -Scar,"a "dead cat",11 --- layouts/index.html -- -{{ $opts := dict "lazyQuotes" true }} -{{ $data := resources.Get "pets.csv" | transform.Unmarshal $opts }} -{{ printf "%v" $data | safeHTML }} - ` - b := hugolib.Test(t, files) - - b.AssertFileContent("public/index.html", ` -[[name description age] [Spot a nice dog 3] [Rover a big dog 5] [Felix a "malicious" cat 7] [Bella an "evil" cat 9] [Scar a "dead cat 11]] - `) -} diff --git a/tpl/transform/transform_integration_test.go b/tpl/transform/transform_integration_test.go new file mode 100644 index 000000000..351420a67 --- /dev/null +++ b/tpl/transform/transform_integration_test.go @@ -0,0 +1,135 @@ +// Copyright 2024 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 transform_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +// Issue #11698 +func TestMarkdownifyIssue11698(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +disableKinds = ['home','section','rss','sitemap','taxonomy','term'] +[markup.goldmark.parser.attribute] +title = true +block = true +-- layouts/_default/single.html -- +_{{ markdownify .RawContent }}_ +-- content/p1.md -- +--- +title: p1 +--- +foo bar +-- content/p2.md -- +--- +title: p2 +--- +foo + +**bar** +-- content/p3.md -- +--- +title: p3 +--- +## foo + +bar +-- content/p4.md -- +--- +title: p4 +--- +foo +{#bar} + ` + + b := hugolib.Test(t, files) + + b.AssertFileContent("public/p1/index.html", "_foo bar_") + b.AssertFileContent("public/p2/index.html", "_

    foo

    \n

    bar

    \n_") + b.AssertFileContent("public/p3/index.html", "_

    foo

    \n

    bar

    \n_") + b.AssertFileContent("public/p4/index.html", "_

    foo

    \n_") +} + +func TestXMLEscape(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +disableKinds = ['section','sitemap','taxonomy','term'] +-- content/p1.md -- +--- +title: p1 +--- +a **b** ` + "\v" + ` c + + ` + b := hugolib.Test(t, files) + + b.AssertFileContent("public/index.xml", ` + <p>a <strong>b</strong> c</p> + `) +} + +// Issue #9642 +func TestHighlightError(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +-- layouts/index.html -- +{{ highlight "a" "b" 0 }} + ` + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ) + + _, err := b.BuildE() + b.Assert(err.Error(), qt.Contains, "error calling highlight: invalid Highlight option: 0") +} + +// Issue #11884 +func TestUnmarshalCSVLazyDecoding(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +-- assets/pets.csv -- +name,description,age +Spot,a nice dog,3 +Rover,"a big dog",5 +Felix,a "malicious" cat,7 +Bella,"an "evil" cat",9 +Scar,"a "dead cat",11 +-- layouts/index.html -- +{{ $opts := dict "lazyQuotes" true }} +{{ $data := resources.Get "pets.csv" | transform.Unmarshal $opts }} +{{ printf "%v" $data | safeHTML }} + ` + b := hugolib.Test(t, files) + + b.AssertFileContent("public/index.html", ` +[[name description age] [Spot a nice dog 3] [Rover a big dog 5] [Felix a "malicious" cat 7] [Bella an "evil" cat 9] [Scar a "dead cat 11]] + `) +}