hugo_sites_build_test.go (39770B)
1 package hugolib
2
3 import (
4 "fmt"
5 "path/filepath"
6 "strings"
7 "testing"
8 "time"
9
10 qt "github.com/frankban/quicktest"
11 "github.com/gohugoio/hugo/htesting"
12 "github.com/gohugoio/hugo/resources/page"
13
14 "github.com/fortytw2/leaktest"
15 "github.com/fsnotify/fsnotify"
16 "github.com/gohugoio/hugo/helpers"
17 "github.com/gohugoio/hugo/hugofs"
18 "github.com/spf13/afero"
19 )
20
21 func TestMultiSitesMainLangInRoot(t *testing.T) {
22 t.Parallel()
23 for _, b := range []bool{false} {
24 doTestMultiSitesMainLangInRoot(t, b)
25 }
26 }
27
28 func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
29 c := qt.New(t)
30
31 siteConfig := map[string]any{
32 "DefaultContentLanguage": "fr",
33 "DefaultContentLanguageInSubdir": defaultInSubDir,
34 }
35
36 b := newMultiSiteTestBuilder(t, "toml", multiSiteTOMLConfigTemplate, siteConfig)
37
38 pathMod := func(s string) string {
39 return s
40 }
41
42 if !defaultInSubDir {
43 pathMod = func(s string) string {
44 return strings.Replace(s, "/fr/", "/", -1)
45 }
46 }
47
48 b.CreateSites()
49 b.Build(BuildCfg{})
50
51 sites := b.H.Sites
52 c.Assert(len(sites), qt.Equals, 4)
53
54 enSite := sites[0]
55 frSite := sites[1]
56
57 c.Assert(enSite.Info.LanguagePrefix, qt.Equals, "/en")
58
59 if defaultInSubDir {
60 c.Assert(frSite.Info.LanguagePrefix, qt.Equals, "/fr")
61 } else {
62 c.Assert(frSite.Info.LanguagePrefix, qt.Equals, "")
63 }
64
65 c.Assert(enSite.PathSpec.RelURL("foo", true), qt.Equals, "/blog/en/foo")
66
67 doc1en := enSite.RegularPages()[0]
68 doc1fr := frSite.RegularPages()[0]
69
70 enPerm := doc1en.Permalink()
71 enRelPerm := doc1en.RelPermalink()
72 c.Assert(enPerm, qt.Equals, "http://example.com/blog/en/sect/doc1-slug/")
73 c.Assert(enRelPerm, qt.Equals, "/blog/en/sect/doc1-slug/")
74
75 frPerm := doc1fr.Permalink()
76 frRelPerm := doc1fr.RelPermalink()
77
78 b.AssertFileContent(pathMod("public/fr/sect/doc1/index.html"), "Single", "Bonjour")
79 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Hello")
80
81 if defaultInSubDir {
82 c.Assert(frPerm, qt.Equals, "http://example.com/blog/fr/sect/doc1/")
83 c.Assert(frRelPerm, qt.Equals, "/blog/fr/sect/doc1/")
84
85 // should have a redirect on top level.
86 b.AssertFileContent("public/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr">`)
87 } else {
88 // Main language in root
89 c.Assert(frPerm, qt.Equals, "http://example.com/blog/sect/doc1/")
90 c.Assert(frRelPerm, qt.Equals, "/blog/sect/doc1/")
91
92 // should have redirect back to root
93 b.AssertFileContent("public/fr/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog">`)
94 }
95 b.AssertFileContent(pathMod("public/fr/index.html"), "Home", "Bonjour")
96 b.AssertFileContent("public/en/index.html", "Home", "Hello")
97
98 // Check list pages
99 b.AssertFileContent(pathMod("public/fr/sect/index.html"), "List", "Bonjour")
100 b.AssertFileContent("public/en/sect/index.html", "List", "Hello")
101 b.AssertFileContent(pathMod("public/fr/plaques/FRtag1/index.html"), "Taxonomy List", "Bonjour")
102 b.AssertFileContent("public/en/tags/tag1/index.html", "Taxonomy List", "Hello")
103
104 // Check sitemaps
105 // Sitemaps behaves different: In a multilanguage setup there will always be a index file and
106 // one sitemap in each lang folder.
107 b.AssertFileContent("public/sitemap.xml",
108 "<loc>http://example.com/blog/en/sitemap.xml</loc>",
109 "<loc>http://example.com/blog/fr/sitemap.xml</loc>")
110
111 if defaultInSubDir {
112 b.AssertFileContent("public/fr/sitemap.xml", "<loc>http://example.com/blog/fr/</loc>")
113 } else {
114 b.AssertFileContent("public/fr/sitemap.xml", "<loc>http://example.com/blog/</loc>")
115 }
116 b.AssertFileContent("public/en/sitemap.xml", "<loc>http://example.com/blog/en/</loc>")
117
118 // Check rss
119 b.AssertFileContent(pathMod("public/fr/index.xml"), pathMod(`<atom:link href="http://example.com/blog/fr/index.xml"`),
120 `rel="self" type="application/rss+xml"`)
121 b.AssertFileContent("public/en/index.xml", `<atom:link href="http://example.com/blog/en/index.xml"`)
122 b.AssertFileContent(
123 pathMod("public/fr/sect/index.xml"),
124 pathMod(`<atom:link href="http://example.com/blog/fr/sect/index.xml"`))
125 b.AssertFileContent("public/en/sect/index.xml", `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
126 b.AssertFileContent(
127 pathMod("public/fr/plaques/FRtag1/index.xml"),
128 pathMod(`<atom:link href="http://example.com/blog/fr/plaques/FRtag1/index.xml"`))
129 b.AssertFileContent("public/en/tags/tag1/index.xml", `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
130
131 // Check paginators
132 b.AssertFileContent(pathMod("public/fr/page/1/index.html"), pathMod(`refresh" content="0; url=http://example.com/blog/fr/"`))
133 b.AssertFileContent("public/en/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/"`)
134 b.AssertFileContent(pathMod("public/fr/page/2/index.html"), "Home Page 2", "Bonjour", pathMod("http://example.com/blog/fr/"))
135 b.AssertFileContent("public/en/page/2/index.html", "Home Page 2", "Hello", "http://example.com/blog/en/")
136 b.AssertFileContent(pathMod("public/fr/sect/page/1/index.html"), pathMod(`refresh" content="0; url=http://example.com/blog/fr/sect/"`))
137 b.AssertFileContent("public/en/sect/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/sect/"`)
138 b.AssertFileContent(pathMod("public/fr/sect/page/2/index.html"), "List Page 2", "Bonjour", pathMod("http://example.com/blog/fr/sect/"))
139 b.AssertFileContent("public/en/sect/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/sect/")
140 b.AssertFileContent(
141 pathMod("public/fr/plaques/FRtag1/page/1/index.html"),
142 pathMod(`refresh" content="0; url=http://example.com/blog/fr/plaques/FRtag1/"`))
143 b.AssertFileContent("public/en/tags/tag1/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
144 b.AssertFileContent(
145 pathMod("public/fr/plaques/FRtag1/page/2/index.html"), "List Page 2", "Bonjour",
146 pathMod("http://example.com/blog/fr/plaques/FRtag1/"))
147 b.AssertFileContent("public/en/tags/tag1/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
148 // nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)
149 b.AssertFileContent("public/nn/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nn/"`)
150 b.AssertFileContent("public/nb/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nb/"`)
151 }
152
153 func TestMultiSitesWithTwoLanguages(t *testing.T) {
154 t.Parallel()
155
156 c := qt.New(t)
157 b := newTestSitesBuilder(t).WithConfigFile("toml", `
158
159 defaultContentLanguage = "nn"
160
161 [languages]
162 [languages.nn]
163 languageName = "Nynorsk"
164 weight = 1
165 title = "Tittel på Nynorsk"
166 [languages.nn.params]
167 p1 = "p1nn"
168
169 [languages.en]
170 title = "Title in English"
171 languageName = "English"
172 weight = 2
173 [languages.en.params]
174 p1 = "p1en"
175 `)
176
177 b.CreateSites()
178 b.Build(BuildCfg{SkipRender: true})
179 sites := b.H.Sites
180
181 c.Assert(len(sites), qt.Equals, 2)
182
183 nnSite := sites[0]
184 nnHome := nnSite.getPage(page.KindHome)
185 c.Assert(len(nnHome.AllTranslations()), qt.Equals, 2)
186 c.Assert(len(nnHome.Translations()), qt.Equals, 1)
187 c.Assert(nnHome.IsTranslated(), qt.Equals, true)
188
189 enHome := sites[1].getPage(page.KindHome)
190
191 p1, err := enHome.Param("p1")
192 c.Assert(err, qt.IsNil)
193 c.Assert(p1, qt.Equals, "p1en")
194
195 p1, err = nnHome.Param("p1")
196 c.Assert(err, qt.IsNil)
197 c.Assert(p1, qt.Equals, "p1nn")
198 }
199
200 func TestMultiSitesBuild(t *testing.T) {
201 for _, config := range []struct {
202 content string
203 suffix string
204 }{
205 {multiSiteTOMLConfigTemplate, "toml"},
206 {multiSiteYAMLConfigTemplate, "yml"},
207 {multiSiteJSONConfigTemplate, "json"},
208 } {
209 t.Run(config.suffix, func(t *testing.T) {
210 t.Parallel()
211 doTestMultiSitesBuild(t, config.content, config.suffix)
212 })
213 }
214 }
215
216 func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
217 c := qt.New(t)
218
219 b := newMultiSiteTestBuilder(t, configSuffix, configTemplate, nil)
220 b.CreateSites()
221
222 sites := b.H.Sites
223 c.Assert(len(sites), qt.Equals, 4)
224
225 b.Build(BuildCfg{})
226
227 // Check site config
228 for _, s := range sites {
229 c.Assert(s.Info.defaultContentLanguageInSubdir, qt.Equals, true)
230 c.Assert(s.disabledKinds, qt.Not(qt.IsNil))
231 }
232
233 gp1 := b.H.GetContentPage(filepath.FromSlash("content/sect/doc1.en.md"))
234 c.Assert(gp1, qt.Not(qt.IsNil))
235 c.Assert(gp1.Title(), qt.Equals, "doc1")
236 gp2 := b.H.GetContentPage(filepath.FromSlash("content/dummysect/notfound.md"))
237 c.Assert(gp2, qt.IsNil)
238
239 enSite := sites[0]
240 enSiteHome := enSite.getPage(page.KindHome)
241 c.Assert(enSiteHome.IsTranslated(), qt.Equals, true)
242
243 c.Assert(enSite.language.Lang, qt.Equals, "en")
244
245 // dumpPages(enSite.RegularPages()...)
246
247 c.Assert(len(enSite.RegularPages()), qt.Equals, 5)
248 c.Assert(len(enSite.AllPages()), qt.Equals, 32)
249
250 // Check 404s
251 b.AssertFileContent("public/en/404.html", "404|en|404 Page not found")
252 b.AssertFileContent("public/fr/404.html", "404|fr|404 Page not found")
253
254 // Check robots.txt
255 // the domain root is the public directory, so the robots.txt has to be created there and not in the language directories
256 b.AssertFileContent("public/robots.txt", "robots")
257 b.AssertFileDoesNotExist("public/en/robots.txt")
258 b.AssertFileDoesNotExist("public/nn/robots.txt")
259
260 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Permalink: http://example.com/blog/en/sect/doc1-slug/")
261 b.AssertFileContent("public/en/sect/doc2/index.html", "Permalink: http://example.com/blog/en/sect/doc2/")
262 b.AssertFileContent("public/superbob/index.html", "Permalink: http://example.com/blog/superbob/")
263
264 doc2 := enSite.RegularPages()[1]
265 doc3 := enSite.RegularPages()[2]
266 c.Assert(doc3, qt.Equals, doc2.Prev())
267 doc1en := enSite.RegularPages()[0]
268 doc1fr := doc1en.Translations()[0]
269 b.AssertFileContent("public/fr/sect/doc1/index.html", "Permalink: http://example.com/blog/fr/sect/doc1/")
270
271 c.Assert(doc1fr, qt.Equals, doc1en.Translations()[0])
272 c.Assert(doc1en, qt.Equals, doc1fr.Translations()[0])
273 c.Assert(doc1fr.Language().Lang, qt.Equals, "fr")
274
275 doc4 := enSite.AllPages()[4]
276 c.Assert(len(doc4.Translations()), qt.Equals, 0)
277
278 // Taxonomies and their URLs
279 c.Assert(len(enSite.Taxonomies()), qt.Equals, 1)
280 tags := enSite.Taxonomies()["tags"]
281 c.Assert(len(tags), qt.Equals, 2)
282 c.Assert(doc1en, qt.Equals, tags["tag1"][0].Page)
283
284 frSite := sites[1]
285
286 c.Assert(frSite.language.Lang, qt.Equals, "fr")
287 c.Assert(len(frSite.RegularPages()), qt.Equals, 4)
288 c.Assert(len(frSite.AllPages()), qt.Equals, 32)
289
290 for _, frenchPage := range frSite.RegularPages() {
291 p := frenchPage
292 c.Assert(p.Language().Lang, qt.Equals, "fr")
293 }
294
295 // See https://github.com/gohugoio/hugo/issues/4285
296 // Before Hugo 0.33 you had to be explicit with the content path to get the correct Page, which
297 // isn't ideal in a multilingual setup. You want a way to get the current language version if available.
298 // Now you can do lookups with translation base name to get that behaviour.
299 // Let us test all the regular page variants:
300 getPageDoc1En := enSite.getPage(page.KindPage, filepath.ToSlash(doc1en.File().Path()))
301 getPageDoc1EnBase := enSite.getPage(page.KindPage, "sect/doc1")
302 getPageDoc1Fr := frSite.getPage(page.KindPage, filepath.ToSlash(doc1fr.File().Path()))
303 getPageDoc1FrBase := frSite.getPage(page.KindPage, "sect/doc1")
304 c.Assert(getPageDoc1En, qt.Equals, doc1en)
305 c.Assert(getPageDoc1Fr, qt.Equals, doc1fr)
306 c.Assert(getPageDoc1EnBase, qt.Equals, doc1en)
307 c.Assert(getPageDoc1FrBase, qt.Equals, doc1fr)
308
309 // Check redirect to main language, French
310 b.AssertFileContent("public/index.html", "0; url=http://example.com/blog/fr")
311
312 // check home page content (including data files rendering)
313 b.AssertFileContent("public/en/index.html", "Default Home Page 1", "Hello", "Hugo Rocks!")
314 b.AssertFileContent("public/fr/index.html", "French Home Page 1", "Bonjour", "Hugo Rocks!")
315
316 // check single page content
317 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour", "LingoFrench")
318 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault")
319
320 // Check node translations
321 homeEn := enSite.getPage(page.KindHome)
322 c.Assert(homeEn, qt.Not(qt.IsNil))
323 c.Assert(len(homeEn.Translations()), qt.Equals, 3)
324 c.Assert(homeEn.Translations()[0].Language().Lang, qt.Equals, "fr")
325 c.Assert(homeEn.Translations()[1].Language().Lang, qt.Equals, "nn")
326 c.Assert(homeEn.Translations()[1].Title(), qt.Equals, "På nynorsk")
327 c.Assert(homeEn.Translations()[2].Language().Lang, qt.Equals, "nb")
328 c.Assert(homeEn.Translations()[2].Title(), qt.Equals, "På bokmål")
329 c.Assert(homeEn.Translations()[2].Language().LanguageName, qt.Equals, "Bokmål")
330
331 sectFr := frSite.getPage(page.KindSection, "sect")
332 c.Assert(sectFr, qt.Not(qt.IsNil))
333
334 c.Assert(sectFr.Language().Lang, qt.Equals, "fr")
335 c.Assert(len(sectFr.Translations()), qt.Equals, 1)
336 c.Assert(sectFr.Translations()[0].Language().Lang, qt.Equals, "en")
337 c.Assert(sectFr.Translations()[0].Title(), qt.Equals, "Sects")
338
339 nnSite := sites[2]
340 c.Assert(nnSite.language.Lang, qt.Equals, "nn")
341 taxNn := nnSite.getPage(page.KindTaxonomy, "lag")
342 c.Assert(taxNn, qt.Not(qt.IsNil))
343 c.Assert(len(taxNn.Translations()), qt.Equals, 1)
344 c.Assert(taxNn.Translations()[0].Language().Lang, qt.Equals, "nb")
345
346 taxTermNn := nnSite.getPage(page.KindTerm, "lag", "sogndal")
347 c.Assert(taxTermNn, qt.Not(qt.IsNil))
348 c.Assert(nnSite.getPage(page.KindTerm, "LAG", "SOGNDAL"), qt.Equals, taxTermNn)
349 c.Assert(len(taxTermNn.Translations()), qt.Equals, 1)
350 c.Assert(taxTermNn.Translations()[0].Language().Lang, qt.Equals, "nb")
351
352 // Check sitemap(s)
353 b.AssertFileContent("public/sitemap.xml",
354 "<loc>http://example.com/blog/en/sitemap.xml</loc>",
355 "<loc>http://example.com/blog/fr/sitemap.xml</loc>")
356 b.AssertFileContent("public/en/sitemap.xml", "http://example.com/blog/en/sect/doc2/")
357 b.AssertFileContent("public/fr/sitemap.xml", "http://example.com/blog/fr/sect/doc1/")
358
359 // Check taxonomies
360 enTags := enSite.Taxonomies()["tags"]
361 frTags := frSite.Taxonomies()["plaques"]
362 c.Assert(len(enTags), qt.Equals, 2, qt.Commentf("Tags in en: %v", enTags))
363 c.Assert(len(frTags), qt.Equals, 2, qt.Commentf("Tags in fr: %v", frTags))
364 c.Assert(enTags["tag1"], qt.Not(qt.IsNil))
365 c.Assert(frTags["FRtag1"], qt.Not(qt.IsNil))
366 b.AssertFileContent("public/fr/plaques/FRtag1/index.html", "FRtag1|Bonjour|http://example.com/blog/fr/plaques/FRtag1/")
367
368 // en and nn have custom site menus
369 c.Assert(len(frSite.Menus()), qt.Equals, 0)
370 c.Assert(len(enSite.Menus()), qt.Equals, 1)
371 c.Assert(len(nnSite.Menus()), qt.Equals, 1)
372
373 c.Assert(enSite.Menus()["main"].ByName()[0].Name, qt.Equals, "Home")
374 c.Assert(nnSite.Menus()["main"].ByName()[0].Name, qt.Equals, "Heim")
375
376 // Issue #3108
377 prevPage := enSite.RegularPages()[0].Prev()
378 c.Assert(prevPage, qt.Not(qt.IsNil))
379 c.Assert(prevPage.Kind(), qt.Equals, page.KindPage)
380
381 for {
382 if prevPage == nil {
383 break
384 }
385 c.Assert(prevPage.Kind(), qt.Equals, page.KindPage)
386 prevPage = prevPage.Prev()
387 }
388
389 // Check bundles
390 b.AssertFileContent("public/fr/bundles/b1/index.html", "RelPermalink: /blog/fr/bundles/b1/|")
391 bundleFr := frSite.getPage(page.KindPage, "bundles/b1/index.md")
392 c.Assert(bundleFr, qt.Not(qt.IsNil))
393 c.Assert(len(bundleFr.Resources()), qt.Equals, 1)
394 logoFr := bundleFr.Resources().GetMatch("logo*")
395 logoFrGet := bundleFr.Resources().Get("logo.png")
396 c.Assert(logoFrGet, qt.Equals, logoFr)
397 c.Assert(logoFr, qt.Not(qt.IsNil))
398 b.AssertFileContent("public/fr/bundles/b1/index.html", "Resources: image/png: /blog/fr/bundles/b1/logo.png")
399 b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
400
401 bundleEn := enSite.getPage(page.KindPage, "bundles/b1/index.en.md")
402 c.Assert(bundleEn, qt.Not(qt.IsNil))
403 b.AssertFileContent("public/en/bundles/b1/index.html", "RelPermalink: /blog/en/bundles/b1/|")
404 c.Assert(len(bundleEn.Resources()), qt.Equals, 1)
405 logoEn := bundleEn.Resources().GetMatch("logo*")
406 c.Assert(logoEn, qt.Not(qt.IsNil))
407 b.AssertFileContent("public/en/bundles/b1/index.html", "Resources: image/png: /blog/en/bundles/b1/logo.png")
408 b.AssertFileContent("public/en/bundles/b1/logo.png", "PNG Data")
409 }
410
411 func TestMultiSitesRebuild(t *testing.T) {
412 // t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4
413 // This leaktest seems to be a little bit shaky on Travis.
414 if !htesting.IsCI() {
415 defer leaktest.CheckTimeout(t, 10*time.Second)()
416 }
417
418 c := qt.New(t)
419
420 b := newMultiSiteTestDefaultBuilder(t).Running().CreateSites().Build(BuildCfg{})
421
422 sites := b.H.Sites
423 fs := b.Fs
424
425 b.AssertFileContent("public/en/sect/doc2/index.html", "Single: doc2|Hello|en|", "\n\n<h1 id=\"doc2\">doc2</h1>\n\n<p><em>some content</em>")
426
427 enSite := sites[0]
428 frSite := sites[1]
429
430 c.Assert(len(enSite.RegularPages()), qt.Equals, 5)
431 c.Assert(len(frSite.RegularPages()), qt.Equals, 4)
432
433 // Verify translations
434 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Hello")
435 b.AssertFileContent("public/fr/sect/doc1/index.html", "Bonjour")
436
437 // check single page content
438 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour")
439 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello")
440
441 homeEn := enSite.getPage(page.KindHome)
442 c.Assert(homeEn, qt.Not(qt.IsNil))
443 c.Assert(len(homeEn.Translations()), qt.Equals, 3)
444
445 contentFs := b.H.Fs.Source
446
447 for i, this := range []struct {
448 preFunc func(t *testing.T)
449 events []fsnotify.Event
450 assertFunc func(t *testing.T)
451 }{
452 // * Remove doc
453 // * Add docs existing languages
454 // (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages).
455 // * Rename file
456 // * Change doc
457 // * Change a template
458 // * Change language file
459 {
460 func(t *testing.T) {
461 fs.Source.Remove("content/sect/doc2.en.md")
462 },
463 []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc2.en.md"), Op: fsnotify.Remove}},
464 func(t *testing.T) {
465 c.Assert(len(enSite.RegularPages()), qt.Equals, 4, qt.Commentf("1 en removed"))
466 },
467 },
468 {
469 func(t *testing.T) {
470 writeNewContentFile(t, contentFs, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
471 writeNewContentFile(t, contentFs, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
472 writeNewContentFile(t, contentFs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
473 },
474 []fsnotify.Event{
475 {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Create},
476 {Name: filepath.FromSlash("content/new2.en.md"), Op: fsnotify.Create},
477 {Name: filepath.FromSlash("content/new1.fr.md"), Op: fsnotify.Create},
478 },
479 func(t *testing.T) {
480 c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
481 c.Assert(len(enSite.AllPages()), qt.Equals, 34)
482 c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
483 c.Assert(frSite.RegularPages()[3].Title(), qt.Equals, "new_fr_1")
484 c.Assert(enSite.RegularPages()[0].Title(), qt.Equals, "new_en_2")
485 c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1")
486
487 rendered := readWorkingDir(t, fs, "public/en/new1/index.html")
488 c.Assert(strings.Contains(rendered, "new_en_1"), qt.Equals, true)
489 },
490 },
491 {
492 func(t *testing.T) {
493 p := "content/sect/doc1.en.md"
494 doc1 := readFileFromFs(t, contentFs, p)
495 doc1 += "CHANGED"
496 writeToFs(t, contentFs, p, doc1)
497 },
498 []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}},
499 func(t *testing.T) {
500 c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
501 doc1 := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
502 c.Assert(strings.Contains(doc1, "CHANGED"), qt.Equals, true)
503 },
504 },
505 // Rename a file
506 {
507 func(t *testing.T) {
508 if err := contentFs.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
509 t.Fatalf("Rename failed: %s", err)
510 }
511 },
512 []fsnotify.Event{
513 {Name: filepath.FromSlash("content/new1renamed.en.md"), Op: fsnotify.Rename},
514 {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Rename},
515 },
516 func(t *testing.T) {
517 c.Assert(len(enSite.RegularPages()), qt.Equals, 6, qt.Commentf("Rename"))
518 c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1")
519 rendered := readWorkingDir(t, fs, "public/en/new1renamed/index.html")
520 c.Assert(rendered, qt.Contains, "new_en_1")
521 },
522 },
523 {
524 // Change a template
525 func(t *testing.T) {
526 template := "layouts/_default/single.html"
527 templateContent := readSource(t, fs, template)
528 templateContent += "{{ print \"Template Changed\"}}"
529 writeSource(t, fs, template, templateContent)
530 },
531 []fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}},
532 func(t *testing.T) {
533 c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
534 c.Assert(len(enSite.AllPages()), qt.Equals, 34)
535 c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
536 doc1 := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
537 c.Assert(strings.Contains(doc1, "Template Changed"), qt.Equals, true)
538 },
539 },
540 {
541 // Change a language file
542 func(t *testing.T) {
543 languageFile := "i18n/fr.yaml"
544 langContent := readSource(t, fs, languageFile)
545 langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
546 writeSource(t, fs, languageFile, langContent)
547 },
548 []fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}},
549 func(t *testing.T) {
550 c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
551 c.Assert(len(enSite.AllPages()), qt.Equals, 34)
552 c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
553 docEn := readWorkingDir(t, fs, "public/en/sect/doc1-slug/index.html")
554 c.Assert(strings.Contains(docEn, "Hello"), qt.Equals, true)
555 docFr := readWorkingDir(t, fs, "public/fr/sect/doc1/index.html")
556 c.Assert(strings.Contains(docFr, "Salut"), qt.Equals, true)
557
558 homeEn := enSite.getPage(page.KindHome)
559 c.Assert(homeEn, qt.Not(qt.IsNil))
560 c.Assert(len(homeEn.Translations()), qt.Equals, 3)
561 c.Assert(homeEn.Translations()[0].Language().Lang, qt.Equals, "fr")
562 },
563 },
564 // Change a shortcode
565 {
566 func(t *testing.T) {
567 writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")
568 },
569 []fsnotify.Event{
570 {Name: filepath.FromSlash("layouts/shortcodes/shortcode.html"), Op: fsnotify.Write},
571 },
572 func(t *testing.T) {
573 c.Assert(len(enSite.RegularPages()), qt.Equals, 6)
574 c.Assert(len(enSite.AllPages()), qt.Equals, 34)
575 c.Assert(len(frSite.RegularPages()), qt.Equals, 5)
576 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut")
577 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello")
578 },
579 },
580 } {
581
582 if this.preFunc != nil {
583 this.preFunc(t)
584 }
585
586 err := b.H.Build(BuildCfg{}, this.events...)
587 if err != nil {
588 t.Fatalf("[%d] Failed to rebuild sites: %s", i, err)
589 }
590
591 this.assertFunc(t)
592 }
593 }
594
595 // https://github.com/gohugoio/hugo/issues/4706
596 func TestContentStressTest(t *testing.T) {
597 b := newTestSitesBuilder(t)
598
599 numPages := 500
600
601 contentTempl := `
602 ---
603 %s
604 title: %q
605 weight: %d
606 multioutput: %t
607 ---
608
609 # Header
610
611 CONTENT
612
613 The End.
614 `
615
616 contentTempl = strings.Replace(contentTempl, "CONTENT", strings.Repeat(`
617
618 ## Another header
619
620 Some text. Some more text.
621
622 `, 100), -1)
623
624 var content []string
625 defaultOutputs := `outputs: ["html", "json", "rss" ]`
626
627 for i := 1; i <= numPages; i++ {
628 outputs := defaultOutputs
629 multioutput := true
630 if i%3 == 0 {
631 outputs = `outputs: ["json"]`
632 multioutput = false
633 }
634 section := "s1"
635 if i%10 == 0 {
636 section = "s2"
637 }
638 content = append(content, []string{fmt.Sprintf("%s/page%d.md", section, i), fmt.Sprintf(contentTempl, outputs, fmt.Sprintf("Title %d", i), i, multioutput)}...)
639 }
640
641 content = append(content, []string{"_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("Home %d", 0), 0, true)}...)
642 content = append(content, []string{"s1/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 1), 1, true)}...)
643 content = append(content, []string{"s2/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 2), 2, true)}...)
644
645 b.WithSimpleConfigFile()
646 b.WithTemplates("layouts/_default/single.html", `Single: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`)
647 b.WithTemplates("layouts/_default/myview.html", `View: {{ len .Content }}`)
648 b.WithTemplates("layouts/_default/single.json", `Single JSON: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`)
649 b.WithTemplates("layouts/_default/list.html", `
650 Page: {{ .Paginator.PageNumber }}
651 P: {{ with .File }}{{ path.Join .Path }}{{ end }}
652 List: {{ len .Paginator.Pages }}|List Content: {{ len .Content }}
653 {{ $shuffled := where .Site.RegularPages "Params.multioutput" true | shuffle }}
654 {{ $first5 := $shuffled | first 5 }}
655 L1: {{ len .Site.RegularPages }} L2: {{ len $first5 }}
656 {{ range $i, $e := $first5 }}
657 Render {{ $i }}: {{ .Render "myview" }}
658 {{ end }}
659 END
660 `)
661
662 b.WithContent(content...)
663
664 b.CreateSites().Build(BuildCfg{})
665
666 contentMatchers := []string{"<h2 id=\"another-header\">Another header</h2>", "<h2 id=\"another-header-99\">Another header</h2>", "<p>The End.</p>"}
667
668 for i := 1; i <= numPages; i++ {
669 if i%3 != 0 {
670 section := "s1"
671 if i%10 == 0 {
672 section = "s2"
673 }
674 checkContent(b, fmt.Sprintf("public/%s/page%d/index.html", section, i), contentMatchers...)
675 }
676 }
677
678 for i := 1; i <= numPages; i++ {
679 section := "s1"
680 if i%10 == 0 {
681 section = "s2"
682 }
683 checkContent(b, fmt.Sprintf("public/%s/page%d/index.json", section, i), contentMatchers...)
684 }
685
686 checkContent(b, "public/s1/index.html", "P: s1/_index.md\nList: 10|List Content: 8132\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8132\n\nRender 1: View: 8132\n\nRender 2: View: 8132\n\nRender 3: View: 8132\n\nRender 4: View: 8132\n\nEND\n")
687 checkContent(b, "public/s2/index.html", "P: s2/_index.md\nList: 10|List Content: 8132", "Render 4: View: 8132\n\nEND")
688 checkContent(b, "public/index.html", "P: _index.md\nList: 10|List Content: 8132", "4: View: 8132\n\nEND")
689
690 // Check paginated pages
691 for i := 2; i <= 9; i++ {
692 checkContent(b, fmt.Sprintf("public/page/%d/index.html", i), fmt.Sprintf("Page: %d", i), "Content: 8132\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8132", "Render 4: View: 8132\n\nEND")
693 }
694 }
695
696 func checkContent(s *sitesBuilder, filename string, matches ...string) {
697 s.T.Helper()
698 content := readWorkingDir(s.T, s.Fs, filename)
699 for _, match := range matches {
700 if !strings.Contains(content, match) {
701 s.Fatalf("No match for\n%q\nin content for %s\n%q\nDiff:\n%s", match, filename, content, htesting.DiffStrings(content, match))
702 }
703 }
704 }
705
706 func TestTranslationsFromContentToNonContent(t *testing.T) {
707 b := newTestSitesBuilder(t)
708 b.WithConfigFile("toml", `
709
710 baseURL = "http://example.com/"
711
712 defaultContentLanguage = "en"
713
714 [languages]
715 [languages.en]
716 weight = 10
717 contentDir = "content/en"
718 [languages.nn]
719 weight = 20
720 contentDir = "content/nn"
721
722
723 `)
724
725 b.WithContent("en/mysection/_index.md", `
726 ---
727 Title: My Section
728 ---
729
730 `)
731
732 b.WithContent("en/_index.md", `
733 ---
734 Title: My Home
735 ---
736
737 `)
738
739 b.WithContent("en/categories/mycat/_index.md", `
740 ---
741 Title: My MyCat
742 ---
743
744 `)
745
746 b.WithContent("en/categories/_index.md", `
747 ---
748 Title: My categories
749 ---
750
751 `)
752
753 for _, lang := range []string{"en", "nn"} {
754 b.WithContent(lang+"/mysection/page.md", `
755 ---
756 Title: My Page
757 categories: ["mycat"]
758 ---
759
760 `)
761 }
762
763 b.Build(BuildCfg{})
764
765 for _, path := range []string{
766 "/",
767 "/mysection",
768 "/categories",
769 "/categories/mycat",
770 } {
771 t.Run(path, func(t *testing.T) {
772 c := qt.New(t)
773
774 s1, _ := b.H.Sites[0].getPageNew(nil, path)
775 s2, _ := b.H.Sites[1].getPageNew(nil, path)
776
777 c.Assert(s1, qt.Not(qt.IsNil))
778 c.Assert(s2, qt.Not(qt.IsNil))
779
780 c.Assert(len(s1.Translations()), qt.Equals, 1)
781 c.Assert(len(s2.Translations()), qt.Equals, 1)
782 c.Assert(s1.Translations()[0], qt.Equals, s2)
783 c.Assert(s2.Translations()[0], qt.Equals, s1)
784
785 m1 := s1.Translations().MergeByLanguage(s2.Translations())
786 m2 := s2.Translations().MergeByLanguage(s1.Translations())
787
788 c.Assert(len(m1), qt.Equals, 1)
789 c.Assert(len(m2), qt.Equals, 1)
790 })
791 }
792 }
793
794 var tocShortcode = `
795 TOC1: {{ .Page.TableOfContents }}
796
797 TOC2: {{ .Page.TableOfContents }}
798 `
799
800 func TestSelfReferencedContentInShortcode(t *testing.T) {
801 t.Parallel()
802
803 b := newMultiSiteTestDefaultBuilder(t)
804
805 var (
806 shortcode = `{{- .Page.Content -}}{{- .Page.Summary -}}{{- .Page.Plain -}}{{- .Page.PlainWords -}}{{- .Page.WordCount -}}{{- .Page.ReadingTime -}}`
807
808 page = `---
809 title: sctest
810 ---
811 Empty:{{< mycontent >}}:
812 `
813 )
814
815 b.WithTemplatesAdded("layouts/shortcodes/mycontent.html", shortcode)
816 b.WithContent("post/simple.en.md", page)
817
818 b.CreateSites().Build(BuildCfg{})
819
820 b.AssertFileContent("public/en/post/simple/index.html", "Empty:[]00:")
821 }
822
823 var tocPageSimple = `---
824 title: tocTest
825 publishdate: "2000-01-01"
826 ---
827 {{< toc >}}
828 # Heading 1 {#1}
829 Some text.
830 ## Subheading 1.1 {#1-1}
831 Some more text.
832 # Heading 2 {#2}
833 Even more text.
834 ## Subheading 2.1 {#2-1}
835 Lorem ipsum...
836 `
837
838 var tocPageVariants1 = `---
839 title: tocTest
840 publishdate: "2000-01-01"
841 ---
842 Variant 1:
843 {{% wrapper %}}
844 {{< toc >}}
845 {{% /wrapper %}}
846 # Heading 1
847
848 Variant 3:
849 {{% toc %}}
850
851 `
852
853 var tocPageVariants2 = `---
854 title: tocTest
855 publishdate: "2000-01-01"
856 ---
857 Variant 1:
858 {{% wrapper %}}
859 {{< toc >}}
860 {{% /wrapper %}}
861 # Heading 1
862
863 Variant 2:
864 {{< wrapper >}}
865 {{< toc >}}
866 {{< /wrapper >}}
867
868 Variant 3:
869 {{% toc %}}
870
871 `
872
873 var tocPageSimpleExpected = `<nav id="TableOfContents">
874 <ul>
875 <li><a href="#1">Heading 1</a>
876 <ul>
877 <li><a href="#1-1">Subheading 1.1</a></li>
878 </ul></li>
879 <li><a href="#2">Heading 2</a>
880 <ul>
881 <li><a href="#2-1">Subheading 2.1</a></li>
882 </ul></li>
883 </ul>
884 </nav>`
885
886 var tocPageWithShortcodesInHeadings = `---
887 title: tocTest
888 publishdate: "2000-01-01"
889 ---
890
891 {{< toc >}}
892
893 # Heading 1 {#1}
894
895 Some text.
896
897 ## Subheading 1.1 {{< shortcode >}} {#1-1}
898
899 Some more text.
900
901 # Heading 2 {{% shortcode %}} {#2}
902
903 Even more text.
904
905 ## Subheading 2.1 {#2-1}
906
907 Lorem ipsum...
908 `
909
910 var tocPageWithShortcodesInHeadingsExpected = `<nav id="TableOfContents">
911 <ul>
912 <li><a href="#1">Heading 1</a>
913 <ul>
914 <li><a href="#1-1">Subheading 1.1 Shortcode: Hello</a></li>
915 </ul></li>
916 <li><a href="#2">Heading 2 Shortcode: Hello</a>
917 <ul>
918 <li><a href="#2-1">Subheading 2.1</a></li>
919 </ul></li>
920 </ul>
921 </nav>`
922
923 var multiSiteTOMLConfigTemplate = `
924 baseURL = "http://example.com/blog"
925
926 paginate = 1
927 disablePathToLower = true
928 defaultContentLanguage = "{{ .DefaultContentLanguage }}"
929 defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }}
930 enableRobotsTXT = true
931
932 [permalinks]
933 other = "/somewhere/else/:filename"
934
935 [Taxonomies]
936 tag = "tags"
937
938 [Languages]
939 [Languages.en]
940 weight = 10
941 title = "In English"
942 languageName = "English"
943 [[Languages.en.menu.main]]
944 url = "/"
945 name = "Home"
946 weight = 0
947
948 [Languages.fr]
949 weight = 20
950 title = "Le Français"
951 languageName = "Français"
952 [Languages.fr.Taxonomies]
953 plaque = "plaques"
954
955 [Languages.nn]
956 weight = 30
957 title = "På nynorsk"
958 languageName = "Nynorsk"
959 paginatePath = "side"
960 [Languages.nn.Taxonomies]
961 lag = "lag"
962 [[Languages.nn.menu.main]]
963 url = "/"
964 name = "Heim"
965 weight = 1
966
967 [Languages.nb]
968 weight = 40
969 title = "På bokmål"
970 languageName = "Bokmål"
971 paginatePath = "side"
972 [Languages.nb.Taxonomies]
973 lag = "lag"
974 `
975
976 var multiSiteYAMLConfigTemplate = `
977 baseURL: "http://example.com/blog"
978
979 disablePathToLower: true
980 paginate: 1
981 defaultContentLanguage: "{{ .DefaultContentLanguage }}"
982 defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }}
983 enableRobotsTXT: true
984
985 permalinks:
986 other: "/somewhere/else/:filename"
987
988 Taxonomies:
989 tag: "tags"
990
991 Languages:
992 en:
993 weight: 10
994 title: "In English"
995 languageName: "English"
996 menu:
997 main:
998 - url: "/"
999 name: "Home"
1000 weight: 0
1001 fr:
1002 weight: 20
1003 title: "Le Français"
1004 languageName: "Français"
1005 Taxonomies:
1006 plaque: "plaques"
1007 nn:
1008 weight: 30
1009 title: "På nynorsk"
1010 languageName: "Nynorsk"
1011 paginatePath: "side"
1012 Taxonomies:
1013 lag: "lag"
1014 menu:
1015 main:
1016 - url: "/"
1017 name: "Heim"
1018 weight: 1
1019 nb:
1020 weight: 40
1021 title: "På bokmål"
1022 languageName: "Bokmål"
1023 paginatePath: "side"
1024 Taxonomies:
1025 lag: "lag"
1026
1027 `
1028
1029 // TODO(bep) clean move
1030 var multiSiteJSONConfigTemplate = `
1031 {
1032 "baseURL": "http://example.com/blog",
1033 "paginate": 1,
1034 "disablePathToLower": true,
1035 "defaultContentLanguage": "{{ .DefaultContentLanguage }}",
1036 "defaultContentLanguageInSubdir": true,
1037 "enableRobotsTXT": true,
1038 "permalinks": {
1039 "other": "/somewhere/else/:filename"
1040 },
1041 "Taxonomies": {
1042 "tag": "tags"
1043 },
1044 "Languages": {
1045 "en": {
1046 "weight": 10,
1047 "title": "In English",
1048 "languageName": "English",
1049 "menu": {
1050 "main": [
1051 {
1052 "url": "/",
1053 "name": "Home",
1054 "weight": 0
1055 }
1056 ]
1057 }
1058 },
1059 "fr": {
1060 "weight": 20,
1061 "title": "Le Français",
1062 "languageName": "Français",
1063 "Taxonomies": {
1064 "plaque": "plaques"
1065 }
1066 },
1067 "nn": {
1068 "weight": 30,
1069 "title": "På nynorsk",
1070 "paginatePath": "side",
1071 "languageName": "Nynorsk",
1072 "Taxonomies": {
1073 "lag": "lag"
1074 },
1075 "menu": {
1076 "main": [
1077 {
1078 "url": "/",
1079 "name": "Heim",
1080 "weight": 1
1081 }
1082 ]
1083 }
1084 },
1085 "nb": {
1086 "weight": 40,
1087 "title": "På bokmål",
1088 "paginatePath": "side",
1089 "languageName": "Bokmål",
1090 "Taxonomies": {
1091 "lag": "lag"
1092 }
1093 }
1094 }
1095 }
1096 `
1097
1098 func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) {
1099 t.Helper()
1100 writeToFs(t, fs.Source, filename, content)
1101 }
1102
1103 func writeToFs(t testing.TB, fs afero.Fs, filename, content string) {
1104 t.Helper()
1105 if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
1106 t.Fatalf("Failed to write file: %s", err)
1107 }
1108 }
1109
1110 func readWorkingDir(t testing.TB, fs *hugofs.Fs, filename string) string {
1111 t.Helper()
1112 return readFileFromFs(t, fs.WorkingDirReadOnly, filename)
1113 }
1114
1115 func workingDirExists(fs *hugofs.Fs, filename string) bool {
1116 b, err := helpers.Exists(filename, fs.WorkingDirReadOnly)
1117 if err != nil {
1118 panic(err)
1119 }
1120 return b
1121 }
1122
1123 func readSource(t *testing.T, fs *hugofs.Fs, filename string) string {
1124 return readFileFromFs(t, fs.Source, filename)
1125 }
1126
1127 func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {
1128 t.Helper()
1129 filename = filepath.Clean(filename)
1130 b, err := afero.ReadFile(fs, filename)
1131 if err != nil {
1132 // Print some debug info
1133 hadSlash := strings.HasPrefix(filename, helpers.FilePathSeparator)
1134 start := 0
1135 if hadSlash {
1136 start = 1
1137 }
1138 end := start + 1
1139
1140 parts := strings.Split(filename, helpers.FilePathSeparator)
1141 if parts[start] == "work" {
1142 end++
1143 }
1144
1145 /*
1146 root := filepath.Join(parts[start:end]...)
1147 if hadSlash {
1148 root = helpers.FilePathSeparator + root
1149 }
1150
1151 helpers.PrintFs(fs, root, os.Stdout)
1152 */
1153
1154 t.Fatalf("Failed to read file: %s", err)
1155 }
1156 return string(b)
1157 }
1158
1159 const testPageTemplate = `---
1160 title: "%s"
1161 publishdate: "%s"
1162 weight: %d
1163 ---
1164 # Doc %s
1165 `
1166
1167 func newTestPage(title, date string, weight int) string {
1168 return fmt.Sprintf(testPageTemplate, title, date, weight, title)
1169 }
1170
1171 func writeNewContentFile(t *testing.T, fs afero.Fs, title, date, filename string, weight int) {
1172 content := newTestPage(title, date, weight)
1173 writeToFs(t, fs, filename, content)
1174 }
1175
1176 type multiSiteTestBuilder struct {
1177 configData any
1178 config string
1179 configFormat string
1180
1181 *sitesBuilder
1182 }
1183
1184 func newMultiSiteTestDefaultBuilder(t testing.TB) *multiSiteTestBuilder {
1185 return newMultiSiteTestBuilder(t, "", "", nil)
1186 }
1187
1188 func (b *multiSiteTestBuilder) WithNewConfig(config string) *multiSiteTestBuilder {
1189 b.WithConfigTemplate(b.configData, b.configFormat, config)
1190 return b
1191 }
1192
1193 func (b *multiSiteTestBuilder) WithNewConfigData(data any) *multiSiteTestBuilder {
1194 b.WithConfigTemplate(data, b.configFormat, b.config)
1195 return b
1196 }
1197
1198 func newMultiSiteTestBuilder(t testing.TB, configFormat, config string, configData any) *multiSiteTestBuilder {
1199 if configData == nil {
1200 configData = map[string]any{
1201 "DefaultContentLanguage": "fr",
1202 "DefaultContentLanguageInSubdir": true,
1203 }
1204 }
1205
1206 if config == "" {
1207 config = multiSiteTOMLConfigTemplate
1208 }
1209
1210 if configFormat == "" {
1211 configFormat = "toml"
1212 }
1213
1214 b := newTestSitesBuilder(t).WithConfigTemplate(configData, configFormat, config)
1215 b.WithContent("root.en.md", `---
1216 title: root
1217 weight: 10000
1218 slug: root
1219 publishdate: "2000-01-01"
1220 ---
1221 # root
1222 `,
1223 "sect/doc1.en.md", `---
1224 title: doc1
1225 weight: 1
1226 slug: doc1-slug
1227 tags:
1228 - tag1
1229 publishdate: "2000-01-01"
1230 ---
1231 # doc1
1232 *some "content"*
1233
1234 {{< shortcode >}}
1235
1236 {{< lingo >}}
1237
1238 NOTE: slug should be used as URL
1239 `,
1240 "sect/doc1.fr.md", `---
1241 title: doc1
1242 weight: 1
1243 plaques:
1244 - FRtag1
1245 - FRtag2
1246 publishdate: "2000-01-04"
1247 ---
1248 # doc1
1249 *quelque "contenu"*
1250
1251 {{< shortcode >}}
1252
1253 {{< lingo >}}
1254
1255 NOTE: should be in the 'en' Page's 'Translations' field.
1256 NOTE: date is after "doc3"
1257 `,
1258 "sect/doc2.en.md", `---
1259 title: doc2
1260 weight: 2
1261 publishdate: "2000-01-02"
1262 ---
1263 # doc2
1264 *some content*
1265 NOTE: without slug, "doc2" should be used, without ".en" as URL
1266 `,
1267 "sect/doc3.en.md", `---
1268 title: doc3
1269 weight: 3
1270 publishdate: "2000-01-03"
1271 aliases: [/en/al/alias1,/al/alias2/]
1272 tags:
1273 - tag2
1274 - tag1
1275 url: /superbob/
1276 ---
1277 # doc3
1278 *some content*
1279 NOTE: third 'en' doc, should trigger pagination on home page.
1280 `,
1281 "sect/doc4.md", `---
1282 title: doc4
1283 weight: 4
1284 plaques:
1285 - FRtag1
1286 publishdate: "2000-01-05"
1287 ---
1288 # doc4
1289 *du contenu francophone*
1290 NOTE: should use the defaultContentLanguage and mark this doc as 'fr'.
1291 NOTE: doesn't have any corresponding translation in 'en'
1292 `,
1293 "other/doc5.fr.md", `---
1294 title: doc5
1295 weight: 5
1296 publishdate: "2000-01-06"
1297 ---
1298 # doc5
1299 *autre contenu francophone*
1300 NOTE: should use the "permalinks" configuration with :filename
1301 `,
1302 // Add some for the stats
1303 "stats/expired.fr.md", `---
1304 title: expired
1305 publishdate: "2000-01-06"
1306 expiryDate: "2001-01-06"
1307 ---
1308 # Expired
1309 `,
1310 "stats/future.fr.md", `---
1311 title: future
1312 weight: 6
1313 publishdate: "2100-01-06"
1314 ---
1315 # Future
1316 `,
1317 "stats/expired.en.md", `---
1318 title: expired
1319 weight: 7
1320 publishdate: "2000-01-06"
1321 expiryDate: "2001-01-06"
1322 ---
1323 # Expired
1324 `,
1325 "stats/future.en.md", `---
1326 title: future
1327 weight: 6
1328 publishdate: "2100-01-06"
1329 ---
1330 # Future
1331 `,
1332 "stats/draft.en.md", `---
1333 title: expired
1334 publishdate: "2000-01-06"
1335 draft: true
1336 ---
1337 # Draft
1338 `,
1339 "stats/tax.nn.md", `---
1340 title: Tax NN
1341 weight: 8
1342 publishdate: "2000-01-06"
1343 weight: 1001
1344 lag:
1345 - Sogndal
1346 ---
1347 # Tax NN
1348 `,
1349 "stats/tax.nb.md", `---
1350 title: Tax NB
1351 weight: 8
1352 publishdate: "2000-01-06"
1353 weight: 1002
1354 lag:
1355 - Sogndal
1356 ---
1357 # Tax NB
1358 `,
1359 // Bundle
1360 "bundles/b1/index.en.md", `---
1361 title: Bundle EN
1362 publishdate: "2000-01-06"
1363 weight: 2001
1364 ---
1365 # Bundle Content EN
1366 `,
1367 "bundles/b1/index.md", `---
1368 title: Bundle Default
1369 publishdate: "2000-01-06"
1370 weight: 2002
1371 ---
1372 # Bundle Content Default
1373 `,
1374 "bundles/b1/logo.png", `
1375 PNG Data
1376 `)
1377
1378 i18nContent := func(id, value string) string {
1379 return fmt.Sprintf(`
1380 [%s]
1381 other = %q
1382 `, id, value)
1383 }
1384
1385 b.WithSourceFile("i18n/en.toml", i18nContent("hello", "Hello"))
1386 b.WithSourceFile("i18n/fr.toml", i18nContent("hello", "Bonjour"))
1387 b.WithSourceFile("i18n/nb.toml", i18nContent("hello", "Hallo"))
1388 b.WithSourceFile("i18n/nn.toml", i18nContent("hello", "Hallo"))
1389
1390 return &multiSiteTestBuilder{sitesBuilder: b, configFormat: configFormat, config: config, configData: configData}
1391 }
1392
1393 func TestRebuildOnAssetChange(t *testing.T) {
1394 b := newTestSitesBuilder(t).Running()
1395 b.WithTemplatesAdded("index.html", `
1396 {{ (resources.Get "data.json").Content }}
1397 `)
1398 b.WithSourceFile("assets/data.json", "orig data")
1399
1400 b.Build(BuildCfg{})
1401 b.AssertFileContent("public/index.html", `orig data`)
1402
1403 b.EditFiles("assets/data.json", "changed data")
1404
1405 b.Build(BuildCfg{})
1406 b.AssertFileContent("public/index.html", `changed data`)
1407 }