package build import ( "os" "path/filepath" "strings" "testing" "time" ) func TestEvents(t *testing.T) { dir := writeStarter(t) cfg, _ := os.ReadFile(filepath.Join(dir, "site.yaml")) cfg = []byte(strings.Replace(string(cfg), "language: en\n", "language: en\ntimezone: America/Phoenix\n", 1)) cfg = []byte(strings.Replace(string(cfg), "collections:\n", "collections:\n events:\n calendar: true\n", 1)) os.WriteFile(filepath.Join(dir, "site.yaml"), cfg, 0o644) os.MkdirAll(filepath.Join(dir, "content", "events"), 0o755) write := func(name, fm string) { os.WriteFile(filepath.Join(dir, "content", "events", name), []byte("---\n"+fm+"---\nAbout it.\n"), 0o644) } write("open-house.md", "title: Open house\nstart: 2026-11-07 18:00\nend: 2026-11-07 20:00\nlocation: Hall, 12 Main St; Mesa\n") write("fair.md", "title: Fair\nstart: 2026-12-04\nend: 2026-12-06\n") write("zoned.md", "title: Zoned\nstart: 2026-11-20T09:30:00+01:00\n") write("swap.md", "title: Swap\nstart: 2026-11-14 11:00\nstatus: cancelled\n") write("picnic.md", "title: Picnic\nstart: 2026-07-04 12:00\nend: 2026-07-04 15:00\n") now := time.Date(2026, 11, 7, 12, 0, 0, 0, time.UTC) res, err := Run(Options{SiteDir: dir, Now: now}) if err != nil { t.Fatal(err) } read := func(p string) string { b, _ := os.ReadFile(filepath.Join(res.Out, p)); return string(b) } feed := read("events/calendar.ics") for _, want := range []string{ "X-WR-CALNAME:Test: Events\r\n", "SUMMARY:Open house\r\nDESCRIPTION:", "DTSTART:20261108T010000Z\r\nDTEND:20261108T030000Z\r\n", // 6 pm in Phoenix (UTC-7, no daylight saving) "DTSTART;VALUE=DATE:20261204\r\nDTEND;VALUE=DATE:20261207\r\n", "DTSTART:20261120T083000Z\r\n", // its own zone, kept "DTSTART:20260704T190000Z\r\n", `LOCATION:Hall\, 12 Main St\; Mesa` + "\r\n", "STATUS:CANCELLED\r\n", } { if !strings.Contains(feed, want) { t.Errorf("feed lacks %q", want) } } for _, l := range strings.Split(feed, "\r\n") { if len(l) > 75 { t.Errorf("line over 75 octets: %q", l) } } if !strings.Contains(read("events/open-house/event.ics"), "SUMMARY:Open house") { t.Error("no event.ics beside the event") } page := read("events/open-house/index.html") if !strings.Contains(page, `"@type":"Event"`) || !strings.Contains(page, `"startDate":"2026-11-07T18:00:00-07:00"`) { t.Errorf("event page lacks its structured data:\n%s", page) } // The next moment the site changes by itself: the open house ending. if want := time.Date(2026, 11, 8, 3, 0, 0, 0, time.UTC); !res.Site.NextScheduled.Equal(want) { t.Errorf("next scheduled %v, want %v", res.Site.NextScheduled, want) } // Upcoming, as templates see it: today's open house still on, the // picnic gone. up := upcoming(res.Site, "events") var names []string for _, p := range up { names = append(names, p.Title) } if got := strings.Join(names, ", "); got != "Open house, Swap, Zoned, Fair" { t.Errorf("upcoming: %s", got) } if w := up[0].Event.When(); w != "Saturday, November 7, 2026, 6:00–8:00 pm MST" { t.Errorf("when: %s", w) } if w := up[3].Event.When(); w != "December 4–6, 2026" { t.Errorf("when: %s", w) } write("bad.md", "title: Bad\nstart: 2026-11-07 18:00\nend: 2026-11-07\n") if _, err := Run(Options{SiteDir: dir, Now: now}); err == nil || !strings.Contains(err.Error(), "give both start and end a time") { t.Errorf("mixed all-day and timed: %v", err) } } func TestBanners(t *testing.T) { dir := writeStarter(t) os.WriteFile(filepath.Join(dir, "data", "banners.yaml"), []byte(`- text: Closed Thursday for Thanksgiving. link: /about/ link_text: Hours tone: alert from: 2026-11-20 00:00 until: 2026-11-27 00:00 - text: New on the home page only. show: home - text: Over already. until: 2026-11-01 00:00 `), 0o644) now := time.Date(2026, 11, 21, 12, 0, 0, 0, time.UTC) res, err := Run(Options{SiteDir: dir, Now: now}) if err != nil { t.Fatal(err) } read := func(p string) string { b, _ := os.ReadFile(filepath.Join(res.Out, p)); return string(b) } home, about := read("index.html"), read("about/index.html") if !strings.Contains(home, `Closed Thursday for Thanksgiving. Hours`) || !strings.Contains(home, "New on the home page only.") { t.Errorf("home banners:\n%s", home) } if !strings.Contains(about, "Closed Thursday") || strings.Contains(about, "home page only") || strings.Contains(home+about, "Over already") { t.Error("banners on the wrong pages") } if want := time.Date(2026, 11, 27, 0, 0, 0, 0, time.UTC); !res.Site.NextScheduled.Equal(want) { t.Errorf("next scheduled %v, want %v (the banner coming down)", res.Site.NextScheduled, want) } os.WriteFile(filepath.Join(dir, "data", "banners.yaml"), []byte("- text: \"\"\n"), 0o644) if _, err := Run(Options{SiteDir: dir, Now: now}); err == nil { t.Error("an empty banner built") } } func TestFeedBeforeFirstPost(t *testing.T) { dir := writeStarter(t) os.MkdirAll(filepath.Join(dir, "content", "news"), 0o755) os.WriteFile(filepath.Join(dir, "content", "news", "_index.md"), []byte("---\ntitle: News\n---\n"), 0o644) cfg, _ := os.ReadFile(filepath.Join(dir, "site.yaml")) os.WriteFile(filepath.Join(dir, "site.yaml"), []byte(strings.Replace(string(cfg), "collections:\n", "collections:\n news:\n feed: true\n", 1)), 0o644) res, err := Run(Options{SiteDir: dir}) if err != nil { t.Fatal(err) } feed, err := os.ReadFile(filepath.Join(res.Out, "news", "feed.xml")) if err != nil || !strings.Contains(string(feed), "Coffey Labs LLC

`) || !strings.Contains(string(home), `Built with HotDog CMS`) { t.Errorf("footer:\n%s", home[strings.Index(string(home), "