package hugolib

import (
	"fmt"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/bep/logg"
	"github.com/fortytw2/leaktest"
	qt "github.com/frankban/quicktest"
	"github.com/gohugoio/hugo/common/types"
	"github.com/gohugoio/hugo/htesting"
	"github.com/gohugoio/hugo/markup/asciidocext"
	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
)

const rebuildFilesSimple = `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
disableLiveReload = true
[outputFormats]
  [outputFormats.rss]
	weight = 10
  [outputFormats.html]
	weight = 20
[outputs]
home = ["rss", "html"]
section = ["html"]
page = ["html"]
-- content/mysection/_index.md --
---
title: "My Section"
---
-- content/mysection/mysectionbundle/index.md --
---
title: "My Section Bundle"
---
My Section Bundle Content.
-- content/mysection/mysectionbundle/mysectionbundletext.txt --
My Section Bundle Text 2 Content.
-- content/mysection/mysectionbundle/mysectionbundlecontent.md --
---
title: "My Section Bundle Content"
---
My Section Bundle Content Content.
-- content/mysection/_index.md --
---
title: "My Section"
---
-- content/mysection/mysectiontext.txt --
Content.
-- content/_index.md --
---
title: "Home"
---
Home Content.
-- content/hometext.txt --
Home Text Content.
-- content/myothersection/myothersectionpage.md --
---
title: "myothersectionpage"
---
myothersectionpage Content.
-- content/mythirdsection/mythirdsectionpage.md --
---
title: "mythirdsectionpage"
---
mythirdsectionpage Content.
{{< myshortcodetext >}}
§§§ myothertext
foo
§§§
-- assets/mytext.txt --
Assets My Text.
-- assets/myshortcodetext.txt --
Assets My Shortcode Text.
-- assets/myothertext.txt --
Assets My Other Text.
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}$
Resources: {{ range $i, $e := .Resources }}{{ $i }}:{{ .RelPermalink }}|{{ .Content }}|{{ end }}$
Len Resources: {{ len .Resources }}|
-- layouts/list.html --
List: {{ .Title }}|{{ .Content }}$
Len Resources: {{ len .Resources }}|
Resources: {{ range $i, $e := .Resources }}{{ $i }}:{{ .RelPermalink }}|{{ .Content }}|{{ end }}$
-- layouts/_shortcodes/foo.html --
Foo.
-- layouts/_shortcodes/myshortcodetext.html --
{{ warnf "mytext %s" now}}
{{ $r := resources.Get "myshortcodetext.txt" }}
My Shortcode Text: {{ $r.Content }}|{{ $r.Permalink }}|
-- layouts/_markup/render-codeblock-myothertext.html --
{{ $r := resources.Get "myothertext.txt" }}
My Other Text: {{ $r.Content }}|{{ $r.Permalink }}|

`

func TestRebuildEditLeafBundleHeaderOnly(t *testing.T) {
	t.Parallel()
	for range 3 {
		b := TestRunning(t, rebuildFilesSimple)
		b.AssertFileContent("public/mysection/mysectionbundle/index.html",
			"My Section Bundle Content Content.")
		b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "My Section Bundle Content.", "My Section Bundle Content Edited.").Build()
		b.AssertFileContent("public/mysection/mysectionbundle/index.html",
			"My Section Bundle Content Edited.")
		b.AssertRenderCountPage(2) // home (rss) + bundle.
		b.AssertRenderCountContent(1)
	}
}

func TestRebuildEditTextFileInLeafBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/mysection/mysectionbundle/index.html",
		"Resources: 0:/mysection/mysectionbundle/mysectionbundletext.txt|My Section Bundle Text 2 Content.|1:|<p>My Section Bundle Content Content.</p>\n|$")

	b.EditFileReplaceAll("content/mysection/mysectionbundle/mysectionbundletext.txt", "Content.", "Content Edited.").Build()
	b.AssertFileContent("public/mysection/mysectionbundle/index.html",
		"Text 2 Content Edited")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(0)
}

func TestRebuildAddingALeaffBundleIssue13925(t *testing.T) {
	t.Parallel()

	b := TestRunning(t, rebuildFilesSimple)

	b.AddFiles(
		"content/mysection/mysectionbundle2/index.md", "",
		"content/mysection/mysectionbundle2/mysectionbundletext.txt", "mysectionbundletext.txt").Build()

	b.AssertFileContent("public/mysection/mysectionbundle2/index.html", "Len Resources: 1|")

	b.AddFiles(
		"content/mynewsection/_index.md", "",
		"content/mynewsection/mynewsectiontext.txt", "foo").Build()

	b.AssertFileContent("public/mynewsection/index.html", "Len Resources: 1|")
}

func TestRebuildEditTextFileInShortcode(t *testing.T) {
	t.Parallel()
	for range 3 {
		b := TestRunning(t, rebuildFilesSimple)
		b.AssertFileContent("public/mythirdsection/mythirdsectionpage/index.html",
			"Text: Assets My Shortcode Text.")
		b.EditFileReplaceAll("assets/myshortcodetext.txt", "My Shortcode Text", "My Shortcode Text Edited").Build()
		fmt.Println(b.LogString())
		b.AssertFileContent("public/mythirdsection/mythirdsectionpage/index.html",
			"Text: Assets My Shortcode Text Edited.")

	}
}

func TestRebuildEditTextFileInHook(t *testing.T) {
	t.Parallel()
	for range 3 {
		b := TestRunning(t, rebuildFilesSimple)
		b.AssertFileContent("public/mythirdsection/mythirdsectionpage/index.html",
			"Text: Assets My Other Text.")
		b.AssertFileContent("public/myothertext.txt", "Assets My Other Text.")
		b.EditFileReplaceAll("assets/myothertext.txt", "My Other Text", "My Other Text Edited").Build()
		b.AssertFileContent("public/mythirdsection/mythirdsectionpage/index.html",
			"Text: Assets My Other Text Edited.")

	}
}

func TestRebuiEditUnmarshaledYamlFileInLeafBundle(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
-- content/mybundle/index.md --
-- content/mybundle/mydata.yml --
foo: bar
-- layouts/single.html --
MyData: {{ .Resources.Get "mydata.yml" | transform.Unmarshal }}|
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/mybundle/index.html", "MyData: map[foo:bar]")

	b.EditFileReplaceAll("content/mybundle/mydata.yml", "bar", "bar edited").Build()

	b.AssertFileContent("public/mybundle/index.html", "MyData: map[foo:bar edited]")
}

func TestRebuildEditTextFileInHomeBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/index.html", "Home Content.")
	b.AssertFileContent("public/index.html", "Home Text Content.")

	b.EditFileReplaceAll("content/hometext.txt", "Content.", "Content Edited.").Build()
	b.AssertFileContent("public/index.html", "Home Content.")
	b.AssertFileContent("public/index.html", "Home Text Content Edited.")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(0)
}

func TestRebuildEditTextFileInBranchBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/mysection/index.html", "My Section", "0:/mysection/mysectiontext.txt|Content.|")

	b.EditFileReplaceAll("content/mysection/mysectiontext.txt", "Content.", "Content Edited.").Build()
	b.AssertFileContent("public/mysection/index.html", "My Section", "0:/mysection/mysectiontext.txt|Content Edited.|")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(0)
}

func testRebuildBothWatchingAndRunning(t *testing.T, files string, withB func(b *IntegrationTestBuilder)) {
	t.Helper()
	for _, opt := range []TestOpt{TestOptWatching(), TestOptRunning()} {
		b := Test(t, files, opt)
		withB(b)
	}
}

func TestRebuildRenameTextFileInLeafBundle(t *testing.T) {
	testRebuildBothWatchingAndRunning(t, rebuildFilesSimple, func(b *IntegrationTestBuilder) {
		b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")

		b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
		b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
		b.AssertRenderCountPage(8)
		b.AssertRenderCountContent(9)
	})
}

func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content.")
	b.EditFileReplaceAll("content/mysection/mysectionbundle/mysectionbundlecontent.md", "Content Content.", "Content Content Edited.").Build()
	b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
}

func TestRebuilEditContentFileThenAnother(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.EditFileReplaceAll("content/mysection/mysectionbundle/mysectionbundlecontent.md", "Content Content.", "Content Content Edited.").Build()
	b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(2)

	b.EditFileReplaceAll("content/myothersection/myothersectionpage.md", "myothersectionpage Content.", "myothersectionpage Content Edited.").Build()
	b.AssertFileContent("public/myothersection/myothersectionpage/index.html", "myothersectionpage Content Edited")
	b.AssertRenderCountPage(2)
	b.AssertRenderCountContent(2)
}

func TestRebuildRenameTextFileInBranchBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/mysection/index.html", "My Section")

	b.RenameFile("content/mysection/mysectiontext.txt", "content/mysection/mysectiontext2.txt").Build()
	b.AssertFileContent("public/mysection/index.html", "mysectiontext2", "My Section")
	b.AssertRenderCountPage(3)
	b.AssertRenderCountContent(2)
}

func TestRebuildRenameTextFileInHomeBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.AssertFileContent("public/index.html", "Home Text Content.")

	b.RenameFile("content/hometext.txt", "content/hometext2.txt").Build()
	b.AssertFileContent("public/index.html", "hometext2", "Home Text Content.")
	b.AssertRenderCountPage(5)
}

func TestRebuildRenameDirectoryWithLeafBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.RenameDir("content/mysection/mysectionbundle", "content/mysection/mysectionbundlerenamed").Build()
	b.AssertFileContent("public/mysection/mysectionbundlerenamed/index.html", "My Section Bundle")
	b.AssertRenderCountPage(2)
}

func TestRebuildRenameDirectoryWithBranchBundle(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	b.RenameDir("content/mysection", "content/mysectionrenamed").Build()
	b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
	b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
	b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
	b.AssertRenderCountPage(5)
}

func TestRebuildRenameDirectoryWithRegularPageUsedInHome(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/foo/p1.md --
---
title: "P1"
---
-- layouts/home.html --
Pages: {{ range .Site.RegularPages }}{{ .RelPermalink }}|{{ end }}$
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "Pages: /foo/p1/|$")

	b.RenameDir("content/foo", "content/bar").Build()

	b.AssertFileContent("public/index.html", "Pages: /bar/p1/|$")
}

func TestRebuildAddRegularFileRegularPageUsedInHomeMultilingual(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[languages.fr]
weight = 3
[languages.a]
weight = 4
[languages.b]
weight = 5
[languages.c]
weight = 6
[languages.d]
weight = 7
[languages.e]
weight = 8
[languages.f]
weight = 9
[languages.g]
weight = 10
[languages.h]
weight = 11
[languages.i]
weight = 12
[languages.j]
weight = 13
-- content/foo/_index.md --
-- content/foo/data.txt --
-- content/foo/p1.md --
-- content/foo/p1.nn.md --
-- content/foo/p1.fr.md --
-- content/foo/p1.a.md --
-- content/foo/p1.b.md --
-- content/foo/p1.c.md --
-- content/foo/p1.d.md --
-- content/foo/p1.e.md --
-- content/foo/p1.f.md --
-- content/foo/p1.g.md --
-- content/foo/p1.h.md --
-- content/foo/p1.i.md --
-- content/foo/p1.j.md --
-- layouts/home.html --
RegularPages: {{ range .Site.RegularPages }}{{ .RelPermalink }}|{{ end }}$
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "RegularPages: /foo/p1/|$")
	b.AssertFileContent("public/nn/index.html", "RegularPages: /nn/foo/p1/|$")
	b.AssertFileContent("public/i/index.html", "RegularPages: /i/foo/p1/|$")

	b.AddFiles("content/foo/p2.md", ``).Build()

	b.AssertFileContent("public/index.html", "RegularPages: /foo/p1/|/foo/p2/|$")
	b.AssertFileContent("public/fr/index.html", "RegularPages: /fr/foo/p1/|$")

	b.AddFiles("content/foo/p2.fr.md", ``).Build()
	b.AssertFileContent("public/fr/index.html", "RegularPages: /fr/foo/p1/|/fr/foo/p2/|$")

	b.AddFiles("content/foo/p2.i.md", ``).Build()
	b.AssertFileContent("public/i/index.html", "RegularPages: /i/foo/p1/|/i/foo/p2/|$")
}

func TestRebuildRenameDirectoryWithBranchBundleFastRender(t *testing.T) {
	recentlyVisited := types.NewEvictingQueue[string](10).Add("/a/b/c/")
	b := TestRunning(t, rebuildFilesSimple, func(cfg *IntegrationTestConfig) { cfg.BuildCfg = BuildCfg{RecentlyTouched: recentlyVisited} })
	b.RenameDir("content/mysection", "content/mysectionrenamed").Build()
	b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
	b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
	b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
	b.AssertRenderCountPage(5)
}

func TestRebuilErrorRecovery(t *testing.T) {
	b := TestRunning(t, rebuildFilesSimple)
	_, err := b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "My Section Bundle Content.", "My Section Bundle Content\n\n\n\n{{< foo }}.").BuildE()

	b.Assert(err, qt.Not(qt.IsNil))
	b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"/content/mysection/mysectionbundle/index.md:8:9": unrecognized character`))

	// Fix the error
	b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "{{< foo }}", "{{< foo >}}").Build()
}

func TestRebuildAddPageListPagesInHome(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/asection/s1.md --
-- content/p1.md --
---
title: "P1"
weight: 1
---
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
-- layouts/home.html --
Pages: {{ range .RegularPages }}{{ .RelPermalink }}|{{ end }}$
`

	b := TestRunning(t, files)
	b.AssertFileContent("public/index.html", "Pages: /p1/|$")
	b.AddFiles("content/p2.md", ``).Build()
	b.AssertFileContent("public/index.html", "Pages: /p1/|/p2/|$")
}

func TestRebuildAddPageWithSpaceListPagesInHome(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/asection/s1.md --
-- content/p1.md --
---
title: "P1"
weight: 1
---
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
-- layouts/home.html --
Pages: {{ range .RegularPages }}{{ .RelPermalink }}|{{ end }}$
`

	b := TestRunning(t, files)
	b.AssertFileContent("public/index.html", "Pages: /p1/|$")
	b.AddFiles("content/test test/index.md", ``).Build()
	b.AssertFileContent("public/index.html", "Pages: /p1/|/test-test/|$")
}

func TestRebuildScopedToOutputFormat(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
outputs: ["html", "json"]
---
P1 Content.

{{< myshort >}}
-- layouts/single.html --
Single HTML: {{ .Title }}|{{ .Content }}|
-- layouts/single.json --
Single JSON: {{ .Title }}|{{ .Content }}|
-- layouts/_shortcodes/myshort.html --
My short.
`
	b := Test(t, files, TestOptRunning())
	b.AssertRenderCountPage(3)
	b.AssertRenderCountContent(1)
	b.AssertFileContent("public/p1/index.html", "Single HTML: P1|<p>P1 Content.</p>\n")
	b.AssertFileContent("public/p1/index.json", "Single JSON: P1|<p>P1 Content.</p>\n")
	b.EditFileReplaceAll("layouts/single.html", "Single HTML", "Single HTML Edited").Build()
	b.AssertFileContent("public/p1/index.html", "Single HTML Edited: P1|<p>P1 Content.</p>\n")
	b.AssertRenderCountPage(1)

	// Edit shortcode. Note that this is reused across all output formats.
	b.EditFileReplaceAll("layouts/_shortcodes/myshort.html", "My short", "My short edited").Build()
	b.AssertFileContent("public/p1/index.html", "My short edited")
	b.AssertFileContent("public/p1/index.json", "My short edited")
	b.AssertRenderCountPage(3) // rss (uses .Content) + 2 single pages.
}

func TestRebuildBaseof(t *testing.T) {
	files := `
-- hugo.toml --
title = "Hugo Project"
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- layouts/baseof.html --
Baseof: {{ .Title }}|
{{ block "main" . }}default{{ end }}
-- layouts/home.html --
{{ define "main" }}
Home: {{ .Title }}|{{ .Content }}|
{{ end }}
`
	testRebuildBothWatchingAndRunning(t, files, func(b *IntegrationTestBuilder) {
		b.AssertFileContent("public/index.html", "Baseof: Hugo Project|", "Home: Hugo Project||")
		b.EditFileReplaceFunc("layouts/baseof.html", func(s string) string {
			return strings.Replace(s, "Baseof", "Baseof Edited", 1)
		}).Build()
		b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Project|", "Home: Hugo Project||")
	})
}

func TestRebuildSingle(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
title = "Hugo Project"
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
---
P1 Content.
-- layouts/home.html --
Home.
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
{{ with (templates.Defer (dict "key" "global")) }}
Defer.
{{ end }}
`
	b := Test(t, files, TestOptRunning())
	b.AssertFileContent("public/p1/index.html", "Single: P1|", "Defer.")
	b.AssertRenderCountPage(3)
	b.AssertRenderCountContent(1)
	b.EditFileReplaceFunc("layouts/single.html", func(s string) string {
		s = strings.Replace(s, "Single", "Single Edited", 1)
		s = strings.Replace(s, "Defer.", "Defer Edited.", 1)
		return s
	}).Build()
	b.AssertFileContent("public/p1/index.html", "Single Edited: P1|", "Defer Edited.")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(0)
}

func TestRebuildSingleWithBaseofEditSingle(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
title = "Hugo Project"
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
---
P1 Content.
[foo](/foo)
-- layouts/baseof.html --
Baseof: {{ .Title }}|
{{ block "main" . }}default{{ end }}
{{ with (templates.Defer (dict "foo" "bar")) }}
Defer.
{{ end }}
-- layouts/home.html --
Home.
-- layouts/single.html --
{{ define "main" }}
Single: {{ .Title }}|{{ .Content }}|
{{ end }}
`
	b := Test(t, files, TestOptRunning())
	b.AssertFileContent("public/p1/index.html", "Single: P1|")
	b.EditFileReplaceFunc("layouts/single.html", func(s string) string {
		return strings.Replace(s, "Single", "Single Edited", 1)
	}).Build()
	b.AssertFileContent("public/p1/index.html", "Single Edited")
}

func TestRebuildSingleWithBaseofEditBaseof(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
title = "Hugo Project"
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
---
P1 Content.
[foo](/foo)
-- layouts/baseof.html --
Baseof: {{ .Title }}|
{{ block "main" . }}default{{ end }}
{{ with (templates.Defer (dict "foo" "bar")) }}
Defer.
{{ end }}
-- layouts/home.html --
Home.
-- layouts/single.html --
{{ define "main" }}
Single: {{ .Title }}|{{ .Content }}|
{{ end }}
`
	b := Test(t, files, TestOptRunning())
	b.AssertFileContent("public/p1/index.html", "Single: P1|")
	fmt.Println("===============")
	b.EditFileReplaceAll("layouts/baseof.html", "Baseof", "Baseof Edited").Build()
	b.AssertFileContent("public/p1/index.html", "Baseof Edited")
}

func TestRebuildWithDeferEditRenderHook(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
title = "Hugo Project"
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
---
P1 Content.
[foo](/foo)
-- layouts/baseof.html --
Baseof: {{ .Title }}|
{{ block "main" . }}default{{ end }}
 {{ with (templates.Defer (dict "foo" "bar")) }}
Defer.
{{ end }}
-- layouts/single.html --
{{ define "main" }}
Single: {{ .Title }}|{{ .Content }}|
{{ end }}
-- layouts/_markup/render-link.html --
Render Link.
`
	b := Test(t, files, TestOptRunning())
	// Edit render hook.
	b.EditFileReplaceAll("layouts/_markup/render-link.html", "Render Link", "Render Link Edited").Build()

	b.AssertFileContent("public/p1/index.html", "Render Link Edited")
}

func TestRebuildFromString(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
disableLiveReload = true
-- content/p1.md --
---
title: "P1"
layout: "l1"
---
P1 Content.
-- content/p2.md --
---
title: "P2"
layout: "l2"
---
P2 Content.
-- assets/mytext.txt --
My Text
-- layouts/l1.html --
{{ $r := partial "get-resource.html" . }}
L1: {{ .Title }}|{{ .Content }}|R: {{ $r.Content }}|
-- layouts/l2.html --
L2.
-- layouts/_partials/get-resource.html --
{{ $mytext := resources.Get "mytext.txt" }}
{{ $txt := printf "Text: %s" $mytext.Content }}
{{ $r := resources.FromString "r.txt"  $txt }}
{{ return $r }}

`
	b := TestRunning(t, files)

	b.AssertFileContent("public/p1/index.html", "L1: P1|<p>P1 Content.</p>\n|R: Text: My Text|")

	b.EditFileReplaceAll("assets/mytext.txt", "My Text", "My Text Edited").Build()

	b.AssertFileContent("public/p1/index.html", "L1: P1|<p>P1 Content.</p>\n|R: Text: My Text Edited|")

	b.AssertRenderCountPage(1)
}

func TestRebuildDeeplyNestedLink(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com/"
disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
disableLiveReload = true
-- content/s/p1.md --
---
title: "P1"
---
-- content/s/p2.md --
---
title: "P2"
---
-- content/s/p3.md --
---
title: "P3"
---
-- content/s/p4.md --
---
title: "P4"
---
-- content/s/p5.md --
---
title: "P5"
---
-- content/s/p6.md --
---
title: "P6"
---
-- content/s/p7.md --
---
title: "P7"
---
-- layouts/list.html --
List.
-- layouts/single.html --
Single.
-- layouts/single.html --
Next: {{ with  .PrevInSection }}{{ .Title }}{{ end }}|
Prev: {{ with  .NextInSection }}{{ .Title }}{{ end }}|


`

	b := TestRunning(t, files)

	b.AssertFileContent("public/s/p1/index.html", "Next: P2|")
	b.EditFileReplaceAll("content/s/p7.md", "P7", "P7 Edited").Build()
	b.AssertFileContent("public/s/p6/index.html", "Next: P7 Edited|")
}

const rebuildBasicFiles = `
-- hugo.toml --
baseURL = "https://example.com/"
disableLiveReload = true
disableKinds = ["sitemap", "robotstxt", "404", "rss"]
-- content/_index.md --
---
title: "Home"
cascade:
  target:
    kind: "page"
  date: 2019-05-06
  params:
    myparam: "myvalue"
---
-- content/mysection/_index.md --
---
title: "My Section"
date: 2024-02-01
---
-- content/mysection/p1.md --
---
title: "P1"
date: 2020-01-01
---
-- content/mysection/p2.md --
---
title: "P2"
date: 2021-02-01
---
-- content/mysection/p3.md --
---
title: "P3"
---
-- layouts/all.html --
all. {{ .Title }}|Param: {{ .Params.myparam }}|Lastmod: {{ .Lastmod.Format "2006-01-02" }}|
`

func TestRebuildEditPageTitle(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertFileContent("public/index.html", "all. Home|Param: |Lastmod: 2024-02-01|")
	b.AssertFileContent("public/mysection/p1/index.html", "P1|Param: myvalue|", "Lastmod: 2020-01-01|")
	b.EditFileReplaceAll("content/mysection/p1.md", "P1", "P1edit").Build()
	b.AssertRenderCountPage(2)
	b.AssertFileContent("public/mysection/p1/index.html", "all. P1edit|Param: myvalue|")
}

func TestRebuildEditPageDate(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertFileContent("public/index.html", "all. Home|Param: |Lastmod: 2024-02-01|")
	b.EditFileReplaceAll("content/mysection/p2.md", "2021-02-01", "2025-04-02").Build()
	b.AssertRenderCountPage(2)
	b.AssertFileContent("public/index.html", "all. Home|Param: |Lastmod: 2025-04-02|")
	b.AssertFileContent("public/mysection/p2/index.html", "all. P2|Param: myvalue|Lastmod: 2025-04-02|")
}

func TestRebuildEditHomeCascadeEditParam(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertRenderCountPage(7)
	b.EditFileReplaceAll("content/_index.md", "myvalue", "myvalue-edit").Build()
	b.AssertRenderCountPage(7)
	b.AssertFileContent("public/mysection/p1/index.html", "all. P1|Param: myvalue-edit|")
}

func TestRebuildEditHomeCascadeRemoveParam(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertRenderCountPage(7)
	b.EditFileReplaceAll("content/_index.md", `myparam: "myvalue"`, "").Build()
	b.AssertRenderCountPage(7)
	b.AssertFileContent("public/mysection/p1/index.html", "all. P1|Param: |")
}

func TestRebuildEditHomeCascadeEditDate(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertRenderCountPage(7)
	b.AssertFileContent("public/mysection/p3/index.html", "all. P3|Param: myvalue|Lastmod: 2019-05-06")
	b.EditFileReplaceAll("content/_index.md", "2019-05-06", "2025-05-06").Build()
	b.AssertRenderCountPage(7)
	b.AssertFileContent("public/index.html", "all. Home|Param: |Lastmod: 2025-05-06|")
	b.AssertFileContent("public/mysection/index.html", "all. My Section|Param: |Lastmod: 2024-02-01|")
	b.AssertFileContent("public/mysection/p3/index.html", "all. P3|Param: myvalue|Lastmod: 2025-05-06")
}

func TestRebuildEditSectionRemoveDate(t *testing.T) {
	t.Parallel()
	b := TestRunning(t, rebuildBasicFiles)
	b.AssertFileContent("public/mysection/index.html", "all. My Section|Param: |Lastmod: 2024-02-01|")
	b.EditFileReplaceAll("content/mysection/_index.md", "date: 2024-02-01", "").Build()
	b.AssertRenderCountPage(5)
	b.AssertFileContent("public/mysection/index.html", "all. My Section|Param: |Lastmod: 2021-02-01|")
}

func TestRebuildVariations(t *testing.T) {
	// t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4
	// This leaktest seems to be a little bit shaky on Travis.
	if !htesting.IsRealCI() {
		defer leaktest.CheckTimeout(t, 10*time.Second)()
	}

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
defaultContentLanguage = "nn"
[pagination]
pagerSize = 20
[security]
enableInlineShortcodes = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/mysect/p1/index.md --
---
title: "P1"
---
P1 Content.
{{< include "mysect/p2" >}}
§§§go { page="mysect/p3" }
hello
§§§

{{< foo.inline >}}Foo{{< /foo.inline >}}
-- content/mysect/p2/index.md --
---
title: "P2"
---
P2 Content.
-- content/mysect/p3/index.md --
---
title: "P3"
---
P3 Content.
-- content/mysect/sub/_index.md --
-- content/mysect/sub/p4/index.md --
---
title: "P4"
---
P4 Content.
-- content/mysect/sub/p5/index.md --
---
title: "P5"
lastMod: 2019-03-02
---
P5 Content.
-- content/myothersect/_index.md --
---
cascade:
- _target:
  cascadeparam: "cascadevalue"
---
-- content/myothersect/sub/_index.md --
-- content/myothersect/sub/p6/index.md --
---
title: "P6"
---
P6 Content.
-- content/translations/p7.en.md --
---
title: "P7 EN"
---
P7 EN Content.
-- content/translations/p7.nn.md --
---
title: "P7 NN"
---
P7 NN Content.
-- layouts/home.html --
Home: {{ .Title }}|{{ .Content }}|
RegularPages: {{ range .RegularPages }}{{ .RelPermalink }}|{{ end }}$
Len RegularPagesRecursive: {{ len .RegularPagesRecursive }}
Site.Lastmod: {{ .Site.Lastmod.Format "2006-01-02" }}|
Paginate: {{ range (.Paginate .Site.RegularPages).Pages }}{{ .RelPermalink }}|{{ .Title }}|{{ end }}$
-- layouts/single.html --
Single: .Site: {{ .Site }}
Single: {{ .Title }}|{{ .Content }}|
Single Partial Cached: {{ partialCached "pcached" . }}|
Page.Lastmod: {{ .Lastmod.Format "2006-01-02" }}|
Cascade param: {{ .Params.cascadeparam }}|
-- layouts/list.html --
List: {{ .Title }}|{{ .Content }}|
RegularPages: {{ range .RegularPages }}{{ .Title }}|{{ end }}$
Len RegularPagesRecursive: {{ len .RegularPagesRecursive }}
RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .RelPermalink }}|{{ end }}$
List Partial P1: {{ partial "p1" . }}|
Page.Lastmod: {{ .Lastmod.Format "2006-01-02" }}|
Cascade param: {{ .Params.cascadeparam }}|
-- layouts/_partials/p1.html --
Partial P1.
-- layouts/_partials/pcached.html --
Partial Pcached.
-- layouts/_shortcodes/include.html --
{{ $p := site.GetPage (.Get 0)}}
.Page.Site: {{ .Page.Site }} :: site: {{ site }}
{{ with $p }}
Shortcode Include: {{ .Title }}|
{{ end }}
Shortcode .Page.Title: {{ .Page.Title }}|
Shortcode Partial P1: {{ partial "p1" . }}|
-- layouts/_markup/render-codeblock.html --
{{ $p := site.GetPage (.Attributes.page)}}
{{ with $p }}
Codeblock Include: {{ .Title }}|
{{ end }}

`

	b := NewIntegrationTestBuilder(
		IntegrationTestConfig{
			T:           t,
			TxtarString: files,
			Running:     true,
			Verbose:     false,
			BuildCfg: BuildCfg{
				testCounters: &buildCounters{},
			},
			LogLevel: logg.LevelWarn,
		},
	).Build()

	// When running the server, this is done on shutdown.
	// Do this here to satisfy the leak detector above.
	defer func() {
		b.Assert(b.H.Close(), qt.IsNil)
	}()

	contentRenderCount := b.counters.contentRenderCounter.Load()
	pageRenderCount := b.counters.pageRenderCounter.Load()

	b.Assert(contentRenderCount > 0, qt.IsTrue)
	b.Assert(pageRenderCount > 0, qt.IsTrue)

	// Test cases:
	// - Edit content file direct
	// - Edit content file transitive shortcode
	// - Edit content file transitive render hook
	// - Rename one language version of a content file
	// - Delete content file, check site.RegularPages and section.RegularPagesRecursive (length)
	// - Add content file (see above).
	// - Edit shortcode
	// - Edit inline shortcode
	// - Edit render hook
	// - Edit partial used in template
	// - Edit partial used in shortcode
	// - Edit partial cached.
	// - Edit lastMod date in content file, check site.Lastmod.
	editFile := func(filename string, replacementFunc func(s string) string) {
		b.EditFileReplaceFunc(filename, replacementFunc).Build()
		b.Assert(b.counters.contentRenderCounter.Load() < contentRenderCount, qt.IsTrue, qt.Commentf("count %d < %d", b.counters.contentRenderCounter.Load(), contentRenderCount))
		b.Assert(b.counters.pageRenderCounter.Load() < pageRenderCount, qt.IsTrue, qt.Commentf("count %d < %d", b.counters.pageRenderCounter.Load(), pageRenderCount))
	}

	b.AssertFileContent("public/index.html", "RegularPages: $", "Len RegularPagesRecursive: 7", "Site.Lastmod: 2019-03-02")

	b.AssertFileContent("public/mysect/p1/index.html",
		"Single: P1|<p>P1 Content.",
		"Shortcode Include: P2|",
		"Codeblock Include: P3|")

	editFile("content/mysect/p1/index.md", func(s string) string {
		return strings.ReplaceAll(s, "P1", "P1 Edited")
	})

	b.AssertFileContent("public/mysect/p1/index.html", "Single: P1 Edited|<p>P1 Edited Content.")
	b.AssertFileContent("public/index.html", "RegularPages: $", "Len RegularPagesRecursive: 7", "Paginate: /mysect/sub/p5/|P5|/mysect/p1/|P1 Edited")
	b.AssertFileContent("public/mysect/index.html", "RegularPages: P1 Edited|P2|P3|$", "Len RegularPagesRecursive: 5")

	// p2 is included in p1 via shortcode.
	editFile("content/mysect/p2/index.md", func(s string) string {
		return strings.ReplaceAll(s, "P2", "P2 Edited")
	})

	b.AssertFileContent("public/mysect/p1/index.html", "Shortcode Include: P2 Edited|")

	// p3 is included in p1 via codeblock hook.
	editFile("content/mysect/p3/index.md", func(s string) string {
		return strings.ReplaceAll(s, "P3", "P3 Edited")
	})

	b.AssertFileContent("public/mysect/p1/index.html", "Codeblock Include: P3 Edited|")

	// Remove a content file in a nested section.
	b.RemoveFiles("content/mysect/sub/p4/index.md").Build()
	b.AssertFileContent("public/mysect/index.html", "RegularPages: P1 Edited|P2 Edited|P3 Edited", "Len RegularPagesRecursive: 4")
	b.AssertFileContent("public/mysect/sub/index.html", "RegularPages: P5|$", "RegularPagesRecursive: 1")

	// Rename one of the translations.
	b.AssertFileContent("public/translations/index.html", "RegularPagesRecursive: /translations/p7/")
	b.AssertFileContent("public/en/translations/index.html", "RegularPagesRecursive: /en/translations/p7/")
	b.RenameFile("content/translations/p7.nn.md", "content/translations/p7rename.nn.md").Build()
	b.AssertFileContent("public/translations/index.html", "RegularPagesRecursive: /translations/p7rename/")
	b.AssertFileContent("public/en/translations/index.html", "RegularPagesRecursive: /en/translations/p7/")

	// Edit shortcode
	editFile("layouts/_shortcodes/include.html", func(s string) string {
		return s + "\nShortcode Include Edited."
	})
	b.AssertFileContent("public/mysect/p1/index.html", "Shortcode Include Edited.")

	// Edit render hook
	editFile("layouts/_markup/render-codeblock.html", func(s string) string {
		return s + "\nCodeblock Include Edited."
	})
	b.AssertFileContent("public/mysect/p1/index.html", "Codeblock Include Edited.")

	// Edit partial p1
	editFile("layouts/_partials/p1.html", func(s string) string {
		return strings.Replace(s, "Partial P1", "Partial P1 Edited", 1)
	})
	b.AssertFileContent("public/mysect/index.html", "List Partial P1: Partial P1 Edited.")
	b.AssertFileContent("public/mysect/p1/index.html", "Shortcode Partial P1: Partial P1 Edited.")

	// Edit partial cached.
	editFile("layouts/_partials/pcached.html", func(s string) string {
		return strings.Replace(s, "Partial Pcached", "Partial Pcached Edited", 1)
	})
	b.AssertFileContent("public/mysect/p1/index.html", "Pcached Edited.")

	// Edit lastMod date in content file, check site.Lastmod.
	editFile("content/mysect/sub/p5/index.md", func(s string) string {
		return strings.Replace(s, "2019-03-02", "2020-03-10", 1)
	})
	b.AssertFileContent("public/index.html", "Site.Lastmod: 2020-03-10|")
	b.AssertFileContent("public/mysect/index.html", "Page.Lastmod: 2020-03-10|")

	// Adjust the date back a few days.
	editFile("content/mysect/sub/p5/index.md", func(s string) string {
		return strings.Replace(s, "2020-03-10", "2019-03-08", 1)
	})
	b.AssertFileContent("public/mysect/index.html", "Page.Lastmod: 2019-03-08|")
	b.AssertFileContent("public/index.html", "Site.Lastmod: 2019-03-08|")

	// Check cascade mods.
	b.AssertFileContent("public/myothersect/index.html", "Cascade param: cascadevalue|")
	b.AssertFileContent("public/myothersect/sub/index.html", "Cascade param: cascadevalue|")
	b.AssertFileContent("public/myothersect/sub/p6/index.html", "Cascade param: cascadevalue|")

	editFile("content/myothersect/_index.md", func(s string) string {
		return strings.Replace(s, "cascadevalue", "cascadevalue edited", 1)
	})
	b.AssertFileContent("public/myothersect/index.html", "Cascade param: cascadevalue edited|")
	b.AssertFileContent("public/myothersect/sub/p6/index.html", "Cascade param: cascadevalue edited|")

	// Repurpose the cascadeparam to set the title.
	editFile("content/myothersect/_index.md", func(s string) string {
		return strings.Replace(s, "cascadeparam:", "title:", 1)
	})
	b.AssertFileContent("public/myothersect/sub/index.html", "Cascade param: |", "List: cascadevalue edited|")

	// Revert it.
	editFile("content/myothersect/_index.md", func(s string) string {
		return strings.Replace(s, "title:", "cascadeparam:", 1)
	})
	b.AssertFileContent("public/myothersect/sub/index.html", "Cascade param: cascadevalue edited|", "List: |")
}

func TestRebuildVariationsJSNoneFingerprinted(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com/"
disableKinds = ["term", "taxonomy", "sitemap", "robotsTXT", "404", "rss"]
disableLiveReload = true
-- content/p1/index.md --
---
title: "P1"
---
P1.
-- content/p2/index.md --
---
title: "P2"
---
P2.
-- content/p3/index.md --
---
title: "P3"
---
P3.
-- content/p4/index.md --
---
title: "P4"
---
P4.
-- assets/main.css --
body {
	background: red;
}
-- layouts/default/list.html --
List.
-- layouts/single.html --
Single.
{{ $css := resources.Get "main.css" | minify }}
RelPermalink: {{ $css.RelPermalink }}|

`

	b := TestRunning(t, files)

	b.AssertFileContent("public/p1/index.html", "RelPermalink: /main.min.css|")
	b.AssertFileContent("public/main.min.css", "body{background:red}")

	b.EditFileReplaceAll("assets/main.css", "red", "blue")
	b.RemoveFiles("content/p2/index.md")
	b.RemoveFiles("content/p3/index.md")
	b.Build()

	b.AssertFileContent("public/main.min.css", "body{background:blue}")
}

func TestRebuildVariationsJSInNestedCachedPartialFingerprinted(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com/"
disableKinds = ["term", "taxonomy", "sitemap", "robotsTXT", "404", "rss"]
disableLiveReload = true
-- content/p1/index.md --
---
title: "P1"
---
P1.
-- content/p2/index.md --
---
title: "P2"
---
P2.
-- content/p3/index.md --
---
title: "P3"
---
P3.
-- content/p4/index.md --
---
title: "P4"
---
P4.
-- assets/js/main.js --
console.log("Hello");
-- layouts/list.html --
List. {{ partial "head.html" . }}$
-- layouts/single.html --
Single. {{ partial "head.html" . }}$
-- layouts/_partials/head.html --
{{ partialCached "js.html" . }}$
-- layouts/_partials/js.html --
{{ $js := resources.Get "js/main.js" | js.Build | fingerprint }}
RelPermalink: {{ $js.RelPermalink }}|
`

	b := TestRunning(t, files, TestOptOsFs())

	b.AssertFileContent("public/p1/index.html", "/js/main.712a50b59d0f0dedb4e3606eaa3860b1f1a5305f6c42da30a2985e473ba314eb.js")
	b.AssertFileContent("public/index.html", "/js/main.712a50b59d0f0dedb4e3606eaa3860b1f1a5305f6c42da30a2985e473ba314eb.js")

	b.EditFileReplaceAll("assets/js/main.js", "Hello", "Hello is Edited").Build()

	for i := 1; i < 5; i++ {
		b.AssertFileContent(fmt.Sprintf("public/p%d/index.html", i), "/js/main.6535698cec9a21875f40ae03e96f30c4bee41a01e979224761e270b9034b2424.js")
	}

	b.AssertFileContent("public/index.html", "/js/main.6535698cec9a21875f40ae03e96f30c4bee41a01e979224761e270b9034b2424.js")
}

func TestRebuildVariationsJSInNestedPartialFingerprintedInBase(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com/"
disableKinds = ["term", "taxonomy", "sitemap", "robotsTXT", "404", "rss"]
disableLiveReload = true
-- assets/js/main.js --
console.log("Hello");
-- layouts/baseof.html --
Base. {{ partial "common/head.html" . }}$
{{ block "main" . }}default{{ end }}
-- layouts/list.html --
{{ define "main" }}main{{ end }}
-- layouts/_partials/common/head.html --
{{ partial "myfiles/js.html" . }}$
-- layouts/_partials/myfiles/js.html --
{{ $js := resources.Get "js/main.js" | js.Build | fingerprint }}
RelPermalink: {{ $js.RelPermalink }}|
`

	b := TestRunning(t, files, TestOptOsFs())

	b.AssertFileContent("public/index.html", "/js/main.712a50b59d0f0dedb4e3606eaa3860b1f1a5305f6c42da30a2985e473ba314eb.js")

	b.EditFileReplaceAll("assets/js/main.js", "Hello", "Hello is Edited").Build()

	b.AssertFileContent("public/index.html", "/js/main.6535698cec9a21875f40ae03e96f30c4bee41a01e979224761e270b9034b2424.js")
}

func TestRebuildVariationsJSBundled(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "robotsTXT", "404", "rss"]
disableLiveReload = true
-- content/_index.md --
---
title: "Home"
---
-- content/p1.md --
---
title: "P1"
layout: "main"
---
-- content/p2.md --
---
title: "P2"
---
{{< jsfingerprinted >}}
-- content/p3.md --
---
title: "P3"
layout: "plain"
---
{{< jsfingerprinted >}}
-- content/main.js --
console.log("Hello");
-- content/foo.js --
console.log("Foo");
-- layouts/home.html --
Home.
{{ $js := site.Home.Resources.Get "main.js"  }}
{{ with $js }}
<script src="{{ .RelPermalink }}"></script>
{{ end }}
-- layouts/single.html --
Single. Deliberately no .Content in here.
-- layouts/plain.html --
Content: {{ .Content }}|
-- layouts/main.html --
{{ $js := site.Home.Resources.Get "main.js"  }}
{{ with $js }}
<script>
{{ .Content }}
</script>
{{ end }}
-- layouts/_shortcodes/jsfingerprinted.html --
{{ $js := site.Home.Resources.Get "foo.js" | fingerprint  }}
<script src="{{ $js.RelPermalink }}"></script>
`

	testCounters := &buildCounters{}

	b := NewIntegrationTestBuilder(
		IntegrationTestConfig{
			T:           t,
			TxtarString: files,
			Running:     true,
			// LogLevel:    logg.LevelTrace,
			// Verbose:     true,
			BuildCfg: BuildCfg{
				testCounters: testCounters,
			},
		},
	).Build()

	b.AssertFileContent("public/index.html", `<script src="/main.js"></script>`)
	b.AssertFileContent("public/p1/index.html", "<script>\n\"console.log(\\\"Hello\\\");\"\n</script>")
	b.AssertFileContent("public/p2/index.html", "Single. Deliberately no .Content in here.")
	b.AssertFileContent("public/p3/index.html", "foo.57b4465b908531b43d4e4680ab1063d856b475cb1ae81ad43e0064ecf607bec1.js")
	b.AssertRenderCountPage(4)

	// Edit JS file.
	b.EditFileReplaceFunc("content/main.js", func(s string) string {
		return strings.Replace(s, "Hello", "Hello is Edited", 1)
	}).Build()

	b.AssertFileContent("public/p1/index.html", "<script>\n\"console.log(\\\"Hello is Edited\\\");\"\n</script>")
	// The p1 (the one inlining the JS) should be rebuilt.
	b.AssertRenderCountPage(2)
	// But not the content file.
	b.AssertRenderCountContent(0)

	// This is included with RelPermalink in a shortcode used in p3, but it's fingerprinted
	// so we need to rebuild on change.
	b.EditFileReplaceFunc("content/foo.js", func(s string) string {
		return strings.Replace(s, "Foo", "Foo Edited", 1)
	}).Build()

	// Verify that the hash has changed.
	b.AssertFileContent("public/p3/index.html", "foo.3a332a088521231e5fc9bd22f15e0ccf507faa7b373fbff22959005b9a80481c.js")

	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(1)
}

func TestRebuildEditData(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableLiveReload = true
[security]
enableInlineShortcodes=true
-- data/mydata.yaml --
foo: bar
-- content/_index.md --
---
title: "Home"
---
{{< data "mydata.foo" >}}}
-- content/p1.md --
---
title: "P1"
---

Foo inline: {{< foo.inline >}}{{ site.Data.mydata.foo }}|{{< /foo.inline >}}
-- layouts/_shortcodes/data.html --
{{ $path := split (.Get 0) "." }}
{{ $data := index site.Data $path }}
Foo: {{ $data }}|
-- layouts/home.html --
Content: {{ .Content }}|
-- layouts/single.html --
Single: {{ .Content }}|
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "Foo: bar|")
	b.AssertFileContent("public/p1/index.html", "Foo inline: bar|")
	b.EditFileReplaceFunc("data/mydata.yaml", func(s string) string {
		return strings.Replace(s, "bar", "bar edited", 1)
	}).Build()
	b.AssertFileContent("public/index.html", "Foo: bar edited|")
	b.AssertFileContent("public/p1/index.html", "Foo inline: bar edited|")
}

func TestRebuildEditHomeContent(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/_index.md --
---
title: "Home"
---
Home.
-- layouts/home.html --
Content: {{ .Content }}
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "Content: <p>Home.</p>")
	b.EditFileReplaceAll("content/_index.md", "Home.", "Home").Build()
	b.AssertFileContent("public/index.html", "Content: <p>Home</p>")
}

// Issue #13014.
func TestRebuildEditNotPermalinkableCustomOutputFormatTemplateInFastRenderMode(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com/docs/"
disableLiveReload = true
[internal]
fastRenderMode = true
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404"]
[outputFormats]
  [outputFormats.SearchIndex]
    baseName = 'Search'
    isPlainText = true
    mediaType = 'text/plain'
    noAlternative = true
	permalinkable = false

[outputs]
  home = ['HTML', 'SearchIndex']
-- content/_index.md --
---
title: "Home"
---
Home.
-- layouts/home.html --
Home.
-- layouts/index.searchindex.txt --
Text. {{ .Title }}|{{ .RelPermalink }}|

`
	b := TestRunning(t, files, TestOptInfo())

	b.AssertFileContent("public/search.txt", "Text.")

	b.EditFileReplaceAll("layouts/index.searchindex.txt", "Text.", "Text Edited.").Build()

	b.BuildPartial("/docs/search.txt")

	b.AssertFileContent("public/search.txt", "Text Edited.")
}

func TestRebuildVariationsAssetsJSImport(t *testing.T) {
	t.Parallel()
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- layouts/home.html --
Home. {{ now }}
{{ with (resources.Get "js/main.js" | js.Build | fingerprint) }}
<script>{{ .Content | safeJS }}</script>
{{ end }}
-- assets/js/lib/foo.js --
export function foo() {
	console.log("Foo");
}
-- assets/js/main.js --
import { foo } from "./lib/foo.js";
console.log("Hello");
foo();
`

	b := NewIntegrationTestBuilder(
		IntegrationTestConfig{
			T:           t,
			TxtarString: files,
			Running:     true,
			// LogLevel:    logg.LevelTrace,
			NeedsOsFS: true,
		},
	).Build()

	b.AssertFileContent("public/index.html", "Home.", "Hello", "Foo")
	// Edit the imported file.
	b.EditFileReplaceAll("assets/js/lib/foo.js", "Foo", "Foo Edited").Build()
	b.AssertFileContent("public/index.html", "Home.", "Hello", "Foo Edited")
}

func TestRebuildVariationsAssetsPostCSSImport(t *testing.T) {
	if !htesting.IsCI() {
		t.Skip("skip CI only")
	}

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy", "sitemap", "rss"]
disableLiveReload = true
-- assets/css/lib/foo.css --
body {
	background: red;
}
-- assets/css/main.css --
@import "lib/foo.css";
-- package.json --
{
	"devDependencies": {
		"postcss-cli": "^9.0.1"
	}
}
-- content/p1.md --
---
title: "P1"
---
-- content/p2.md --
---
title: "P2"
layout: "foo"
---
{{< fingerprinted >}}
-- content/p3.md --
---
title: "P3"
layout: "foo"
---
{{< notfingerprinted >}}
-- layouts/_shortcodes/fingerprinted.html --
Fingerprinted.
{{ $opts := dict "inlineImports" true "noMap" true }}
{{ with (resources.Get "css/main.css" | postCSS $opts | fingerprint) }}
<style src="{{ .RelPermalink }}"></style>
{{ end }}
-- layouts/_shortcodes/notfingerprinted.html --
Fingerprinted.
{{ $opts := dict "inlineImports" true "noMap" true }}
{{ with (resources.Get "css/main.css" | postCSS $opts) }}
<style src="{{ .RelPermalink }}"></style>
{{ end }}
-- layouts/home.html --
Home.
{{ $opts := dict "inlineImports" true "noMap" true }}
{{ with (resources.Get "css/main.css" | postCSS $opts) }}
<style>{{ .Content | safeCSS }}</style>
{{ end }}
-- layouts/foo.html --
Foo.
{{ .Title }}|{{ .Content }}|
-- layouts/single.html --
Single.
{{ $opts := dict "inlineImports" true "noMap" true }}
{{ with (resources.Get "css/main.css" | postCSS $opts) }}
<style src="{{ .RelPermalink }}"></style>
{{ end }}
`

	b := NewIntegrationTestBuilder(
		IntegrationTestConfig{
			T:               t,
			TxtarString:     files,
			Running:         true,
			NeedsOsFS:       true,
			NeedsNpmInstall: true,
			// LogLevel:        logg.LevelDebug,
		},
	).Build()

	b.AssertFileContent("public/index.html", "Home.", "<style>body {\n\tbackground: red;\n}</style>")
	b.AssertFileContent("public/p1/index.html", "Single.", "/css/main.css")
	b.AssertRenderCountPage(4)

	// Edit the imported file.
	b.EditFileReplaceFunc("assets/css/lib/foo.css", func(s string) string {
		return strings.Replace(s, "red", "blue", 1)
	}).Build()

	b.AssertRenderCountPage(4)

	b.AssertFileContent("public/index.html", "Home.", "<style>body {\n\tbackground: blue;\n}</style>")
}

func TestRebuildI18n(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- i18n/en.toml --
hello = "Hello"
-- layouts/home.html --
Hello: {{ i18n "hello" }}
`

	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "Hello: Hello")

	b.EditFileReplaceAll("i18n/en.toml", "Hello", "Hugo").Build()

	b.AssertFileContent("public/index.html", "Hello: Hugo")
}

func TestRebuildEditContentNonDefaultLanguage(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/p1/index.en.md --
---
title: "P1 en"
---
P1 en.
-- content/p1/b.en.md --
---
title: "B en"
---
B en.
-- content/p1/f1.en.txt --
F1 en
-- content/p1/index.nn.md --
---
title: "P1 nn"
---
P1 nn.
-- content/p1/b.nn.md --
---
title: "B nn"
---
B nn.
-- content/p1/f1.nn.txt --
F1 nn
-- layouts/all.html --
All: {{ .Title }}|{{ .Kind }}|{{ .Content }}|Bundled File: {{ with .Resources.GetMatch "f1.*" }}{{ .Content }}{{ end }}|Bundled Page: {{ with .Resources.GetMatch "b.*" }}{{ .Content }}{{ end }}|
`

	b := TestRunning(t, files)

	b.AssertFileContent("public/nn/p1/index.html", "All: P1 nn|page|<p>P1 nn.</p>", "F1 nn|")
	b.EditFileReplaceAll("content/p1/index.nn.md", "P1 nn.", "P1 nn edit.").Build()
	b.AssertFileContent("public/nn/p1/index.html", "All: P1 nn|page|<p>P1 nn edit.</p>")
	b.EditFileReplaceAll("content/p1/f1.nn.txt", "F1 nn", "F1 nn edit.").Build()
	b.AssertFileContent("public/nn/p1/index.html", "All: P1 nn|page|<p>P1 nn edit.</p>\n|Bundled File: F1 nn edit.|")
	b.EditFileReplaceAll("content/p1/b.nn.md", "B nn.", "B nn edit.").Build()
	b.AssertFileContent("public/nn/p1/index.html", "B nn edit.")
}

func TestRebuildEditContentNonDefaultLanguageDifferentBundles(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
contentDir = "content/en"
[languages.nn]
weight = 2
contentDir = "content/nn"
-- content/en/p1en/index.md --
---
title: "P1 en"
---
-- content/nn/p1nn/index.md --
---
title: "P1 nn"
---
P1 nn.
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
`

	b := TestRunning(t, files)
	b.AssertFileContent("public/nn/p1nn/index.html", "Single: P1 nn|<p>P1 nn.</p>")
	b.EditFileReplaceAll("content/nn/p1nn/index.md", "P1 nn.", "P1 nn edit.").Build()
	b.AssertFileContent("public/nn/p1nn/index.html", "Single: P1 nn|<p>P1 nn edit.</p>\n|")
	b.AssertFileContent("public/nn/p1nn/index.html", "P1 nn edit.")
}

func TestRebuildVariationsAssetsSassImport(t *testing.T) {
	if !htesting.IsCI() {
		t.Skip("skip CI only")
	}

	filesTemplate := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- assets/css/lib/foo.scss --
body {
	background: red;
}
-- assets/css/main.scss --
@import "lib/foo";
-- layouts/home.html --
Home.
{{ $opts := dict "transpiler" "TRANSPILER" }}
{{ with (resources.Get "css/main.scss" | toCSS $opts) }}
<style>{{ .Content | safeCSS }}</style>
{{ end }}
`

	runTest := func(transpiler string) {
		t.Run(transpiler, func(t *testing.T) {
			files := strings.Replace(filesTemplate, "TRANSPILER", transpiler, 1)
			b := NewIntegrationTestBuilder(
				IntegrationTestConfig{
					T:           t,
					TxtarString: files,
					Running:     true,
					NeedsOsFS:   true,
				},
			).Build()

			b.AssertFileContent("public/index.html", "Home.", "background: red")

			// Edit the imported file.
			b.EditFileReplaceFunc("assets/css/lib/foo.scss", func(s string) string {
				return strings.Replace(s, "red", "blue", 1)
			}).Build()

			b.AssertFileContent("public/index.html", "Home.", "background: blue")
		})
	}

	if scss.Supports() {
		runTest("libsass")
	}

	if dartsass.Supports() {
		runTest("dartsass")
	}
}

func benchmarkFilesEdit(count int) string {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["term", "taxonomy"]
disableLiveReload = true
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
-- layouts/list.html --
List: {{ .Title }}|{{ .Content }}|
-- content/mysect/_index.md --
---
title: "My Sect"
---
	`

	contentTemplate := `
---
title: "P%d"
---
P%d Content.
`

	for i := range count {
		files += fmt.Sprintf("-- content/mysect/p%d/index.md --\n%s", i, fmt.Sprintf(contentTemplate, i, i))
	}

	return files
}

func BenchmarkRebuildContentFileChange(b *testing.B) {
	files := benchmarkFilesEdit(500)

	cfg := IntegrationTestConfig{
		T:           b,
		TxtarString: files,
		Running:     true,
	}

	for b.Loop() {
		b.StopTimer()
		bb := NewIntegrationTestBuilder(cfg).Build()
		b.StartTimer()
		bb.EditFileReplaceFunc("content/mysect/p123/index.md", func(s string) string {
			return s + "... Edited"
		}).Build()
	}
}

func TestRebuildConcat(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
-- assets/a.css --
a
-- assets/b.css --
b
-- assets/c.css --
c
-- assets/common/c1.css --
c1
-- assets/common/c2.css --
c2
-- layouts/home.html --
{{ $a := resources.Get "a.css" }}
{{ $b := resources.Get "b.css" }}
{{ $common := resources.Match "common/*.css" | resources.Concat "common.css" | minify }}
{{ $ab := slice $a $b $common | resources.Concat "ab.css" }}
all: {{ $ab.RelPermalink }}
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/ab.css", "abc1c2")
	b.EditFileReplaceAll("assets/common/c2.css", "c2", "c2 edited").Build()
	b.AssertFileContent("public/ab.css", "abc1c2 edited")
	b.AddFiles("assets/common/c3.css", "c3").Build()
	b.AssertFileContent("public/ab.css", "abc1c2 editedc3")
}

func TestRebuildEditArchetypeFile(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- archetypes/default.md --
---
title: "Default"
---
`

	b := TestRunning(t, files)
	// Just make sure that it doesn't panic.
	b.EditFileReplaceAll("archetypes/default.md", "Default", "Default Edited").Build()
}

func TestRebuildEditMixedCaseTemplateFileIssue12165(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- layouts/_partials/MyTemplate.html --
MyTemplate
-- layouts/home.html --
MyTemplate: {{ partial "MyTemplate.html" . }}|


`

	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate")

	b.EditFileReplaceAll("layouts/_partials/MyTemplate.html", "MyTemplate", "MyTemplate Edited").Build()

	b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate Edited")
}

func TestRebuildEditInlinePartial13723(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
title = "Foo"
-- layouts/baseof.html --
{{ block "main" . }}Main.{{ end }}
{{ partial "myinlinepartialinbaseof.html" . }}|
 {{- define "_partials/myinlinepartialinbaseof.html" }}
 My inline partial in baseof.
 {{ end }}
-- layouts/_partials/mypartial.html --
Mypartial.
{{ partial "myinlinepartial.html" . }}|
{{- define "_partials/myinlinepartial.html" }}
Mypartial Inline.|{{ .Title }}|
{{ end }}
-- layouts/_partials/myotherpartial.html --
Myotherpartial.
{{ partial "myotherinlinepartial.html" . }}|
{{- define "_partials/myotherinlinepartial.html" }}
Myotherpartial Inline.|{{ .Title }}|
{{ return "myotherinlinepartial" }}
{{ end }}
-- layouts/all.html --
{{ define "main" }}
{{ partial "mypartial.html" . }}|
{{ partial "myotherpartial.html" . }}|
  {{ partial "myinlinepartialinall.html" . }}|
{{ end }}
 {{- define "_partials/myinlinepartialinall.html" }}
 My inline partial in all.
 {{ end }}

`
	b := TestRunning(t, files)
	b.AssertFileContent("public/index.html", "Mypartial.", "Mypartial Inline.|Foo")

	// Edit inline partial in partial.
	b.EditFileReplaceAll("layouts/_partials/mypartial.html", "Mypartial Inline.", "Mypartial Inline Edited.").Build()
	b.AssertFileContent("public/index.html", "Mypartial Inline Edited.|Foo")

	// Edit inline partial in baseof.
	b.EditFileReplaceAll("layouts/baseof.html", "My inline partial in baseof.", "My inline partial in baseof Edited.").Build()
	b.AssertFileContent("public/index.html", "My inline partial in baseof Edited.")

	// Edit inline partial in all.
	b.EditFileReplaceAll("layouts/all.html", "My inline partial in all.", "My inline partial in all Edited.").Build()
	b.AssertFileContent("public/index.html", "My inline partial in all Edited.")
}

func TestRebuildEditAsciiDocContentFile(t *testing.T) {
	if ok, err := asciidocext.Supports(); !ok {
		t.Skip(err)
	}
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss", "home", "section"]
[security]
[security.exec]
allow = ['^python$', '^rst2html.*', '^asciidoctor$']
-- content/posts/p1.adoc --
---
title: "P1"
---
P1 Content.
-- content/posts/p2.adoc --
---
title: "P2"
---
P2 Content.
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
`
	b := TestRunning(t, files)
	b.AssertFileContent("public/posts/p1/index.html",
		"Single: P1|<div class=\"paragraph\">\n<p>P1 Content.</p>\n</div>\n|")
	b.AssertRenderCountPage(2)
	b.AssertRenderCountContent(2)

	b.EditFileReplaceAll("content/posts/p1.adoc", "P1 Content.", "P1 Content Edited.").Build()

	b.AssertFileContent("public/posts/p1/index.html", "Single: P1|<div class=\"paragraph\">\n<p>P1 Content Edited.</p>\n</div>\n|")
	b.AssertRenderCountPage(1)
	b.AssertRenderCountContent(1)
}

func TestRebuildEditSingleListChangeUbuntuIssue12362(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['rss','section','sitemap','taxonomy','term']
disableLiveReload = true
-- layouts/list.html --
{{ range .Pages }}{{ .Title }}|{{ end }}
-- layouts/single.html --
{{ .Title }}
-- content/p1.md --
---
title: p1
---
`

	b := TestRunning(t, files)
	b.AssertFileContent("public/index.html", "p1|")

	b.AddFiles("content/p2.md", "---\ntitle: p2\n---").Build()
	b.AssertFileContent("public/index.html", "p1|p2|") // this test passes, which doesn't match reality
}

func TestRebuildHomeThenPageIssue12436(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ['sitemap','taxonomy','term']
disableLiveReload = true
-- layouts/list.html --
{{ .Content }}
-- layouts/single.html --
{{ .Content }}
-- content/_index.md --
---
title: home
---
home-content|
-- content/p1/index.md --
---
title: p1
---
p1-content|
`

	b := TestRunning(t, files)

	b.AssertFileContent("public/index.html", "home-content|")
	b.AssertFileContent("public/p1/index.html", "p1-content|")
	b.AssertRenderCountPage(3)

	b.EditFileReplaceAll("content/_index.md", "home-content", "home-content-foo").Build()
	b.AssertFileContent("public/index.html", "home-content-foo")
	b.AssertRenderCountPage(2) // Home page rss + html

	b.EditFileReplaceAll("content/p1/index.md", "p1-content", "p1-content-foo").Build()
	b.AssertFileContent("public/p1/index.html", "p1-content-foo")
}

func TestRebuildEditTagIssue13648(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- layouts/all.html --
All. {{ range .Pages }}{{ .Title }}|{{ end }}$
-- content/p1.md --
---
title: "P1"
tags: ["tag1"]
---

`
	b := TestRunning(t, files)

	b.AssertFileContent("public/tags/index.html", "All. Tag1|")
	b.EditFileReplaceAll("content/p1.md", "tag1", "tag2").Build()

	b.AssertFileContent("public/tags/index.html", "All. Tag1|$")
}

func TestRebuildPageWithChineseTag14240(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- layouts/all.html --
All. {{ range .Pages }}{{ .RelPermalink }}: {{ .Title }}|{{ end }}$
-- content/p1.md --
---
title: "P1"
tags: ["欢迎"]
---

`
	b := TestRunning(t, files)

	b.AssertFileContent("public/tags/欢迎/index.html", "All. /p1/: P1|$")
	b.EditFileReplaceAll("content/p1.md", "P1", "P1 edit").Build()
	b.AssertFileContent("public/tags/欢迎/index.html", "All. /p1/: P1 edit|$")
}

func TestRebuildEditNonReferencedResourceIssue13748(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/mybundle/index.md --
-- content/mybundle/resource.txt --
This is a resource file.
-- layouts/all.html --
All.
`
	b := TestRunning(t, files)

	b.AssertFileContent("public/mybundle/resource.txt", "This is a resource file.")
	b.EditFileReplaceAll("content/mybundle/resource.txt", "This is a resource file.", "This is an edited resource file.").Build()
	b.AssertFileContent("public/mybundle/resource.txt", "This is an edited resource file.")
}

// Issue #14207
func TestRebuildAddShortcodeThenEditContentUsingIt(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
-- content/p1.md --
---
title: "P1"
---
Content before shortcode.
`
	b := TestRunning(t, files)
	b.AssertFileContent("public/p1/index.html", "Single: P1|", "Content before shortcode.")

	// Add a new shortcode file
	b.AddFiles(
		"layouts/_shortcodes/year.html", `{{- now.Format "2006" -}}`,
	).Build()

	// Verify shortcode was registered
	ts := b.H.Sites[0].Deps.TemplateStore
	if ti := ts.LookupShortcodeByName("year"); ti == nil {
		t.Fatal("shortcode 'year' not found in cache after adding")
	}

	// Now edit content to use the shortcode - this should find the shortcode
	currentYear := time.Now().Format("2006")
	b.EditFileReplaceAll("content/p1.md", "Content before shortcode.", "This is {{< year >}} wow.").Build()
	b.AssertFileContent("public/p1/index.html", "Single: P1|", fmt.Sprintf("This is %s wow.", currentYear))
}
