feat(generator): add -default-view to choose timeline or grid as site root
The chosen view is published at /, the other under /timeline/ or /grid/. Page paths, pager links and the cross-links between the views are derived from the setting. The directory named after the default view is removed on build, since it can only be a leftover from before a switch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// buildSite builds a site of three photos spread over two days, with a page
|
||||
// size of two so both views need a second page, and returns the output dir.
|
||||
func buildSite(t *testing.T, output, defaultView string) {
|
||||
t.Helper()
|
||||
content := t.TempDir()
|
||||
|
||||
var img bytes.Buffer
|
||||
if err := jpeg.Encode(&img, image.NewRGBA(image.Rect(0, 0, 8, 8)), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for slug, date := range map[string]string{
|
||||
"one": "2026-06-14",
|
||||
"two": "2026-06-14",
|
||||
"three": "2026-06-10",
|
||||
} {
|
||||
writeFile(t, filepath.Join(content, slug+".jpg"), img.Bytes())
|
||||
writeFile(t, filepath.Join(content, slug+".toml"),
|
||||
[]byte("caption = \""+slug+"\"\ndate = \""+date+"\"\n"))
|
||||
}
|
||||
|
||||
g := NewGenerator(content, output, "https://photos.example.com",
|
||||
"Ada", "", "", "", "", "Crimson Pro", "Manjari", 2, defaultView)
|
||||
if err := g.Build(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPage(t *testing.T, output, rel string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(filepath.Join(output, filepath.FromSlash(rel), "index.html"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// Timeline pages carry day sections, grid pages do not.
|
||||
func isTimeline(page string) bool {
|
||||
return strings.Contains(page, `class="tl-day"`)
|
||||
}
|
||||
|
||||
func TestDefaultViewOwnsSiteRoot(t *testing.T) {
|
||||
tests := []struct {
|
||||
defaultView string
|
||||
gridAt string // URL of the grid's first page
|
||||
timelineAt string // URL of the timeline's first page
|
||||
}{
|
||||
{viewTimeline, "/grid/", "/"},
|
||||
{viewGrid, "/", "/timeline/"},
|
||||
{"nonsense", "/grid/", "/"}, // unknown values fall back to the timeline
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.defaultView, func(t *testing.T) {
|
||||
output := t.TempDir()
|
||||
buildSite(t, output, tt.defaultView)
|
||||
|
||||
timeline := readPage(t, output, tt.timelineAt)
|
||||
grid := readPage(t, output, tt.gridAt)
|
||||
if !isTimeline(timeline) {
|
||||
t.Errorf("%s is not the timeline", tt.timelineAt)
|
||||
}
|
||||
if isTimeline(grid) {
|
||||
t.Errorf("%s is not the grid", tt.gridAt)
|
||||
}
|
||||
|
||||
// The views link to each other, and to their own second page.
|
||||
for page, want := range map[string]string{
|
||||
timeline: `href="` + tt.gridAt + `" class="rss-a">Grid`,
|
||||
grid: `href="` + tt.timelineAt + `" class="rss-a">Timeline`,
|
||||
} {
|
||||
if !strings.Contains(page, want) {
|
||||
t.Errorf("missing link %q", want)
|
||||
}
|
||||
}
|
||||
for first, base := range map[string]string{
|
||||
timeline: strings.TrimSuffix(tt.timelineAt, "/"),
|
||||
grid: strings.TrimSuffix(tt.gridAt, "/"),
|
||||
} {
|
||||
next := base + "/page/2/"
|
||||
if !strings.Contains(first, `href="`+next+`" data-load-more`) {
|
||||
t.Errorf("first page does not link to %s", next)
|
||||
}
|
||||
second := readPage(t, output, next)
|
||||
if !strings.Contains(second, `class="pager-newer" href="`+base+`/"`) {
|
||||
t.Errorf("%s does not link back to %s/", next, base)
|
||||
}
|
||||
}
|
||||
|
||||
// Day pages lead back to the timeline, wherever it lives.
|
||||
day := readPage(t, output, "/day/2026-06-14/")
|
||||
if !strings.Contains(day, `href="`+tt.timelineAt+`" class="rss-a">← Timeline`) {
|
||||
t.Errorf("day page does not link to the timeline at %s", tt.timelineAt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchingDefaultViewDropsOldViewDir(t *testing.T) {
|
||||
output := t.TempDir()
|
||||
|
||||
buildSite(t, output, viewTimeline)
|
||||
if _, err := os.Stat(filepath.Join(output, "grid", "index.html")); err != nil {
|
||||
t.Fatalf("grid view missing before the switch: %v", err)
|
||||
}
|
||||
|
||||
buildSite(t, output, viewGrid)
|
||||
if _, err := os.Stat(filepath.Join(output, "grid")); !os.IsNotExist(err) {
|
||||
t.Errorf("stale /grid/ survived the switch to -default-view grid")
|
||||
}
|
||||
if isTimeline(readPage(t, output, "/")) {
|
||||
t.Errorf("site root is still the timeline")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user