mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
- Character sheet now checks isHolidayToday() for correct remaining action counts on holidays - Remove renderAdvHolidaySecondPrompt (dead code after economy split) and its test - Babysit harvest counter clamped to max to prevent exceeding budget Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
305 lines
8.1 KiB
Go
305 lines
8.1 KiB
Go
package plugin
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEasterDate(t *testing.T) {
|
|
tests := []struct {
|
|
year int
|
|
month time.Month
|
|
day int
|
|
}{
|
|
{2024, time.March, 31},
|
|
{2025, time.April, 20},
|
|
{2026, time.April, 5},
|
|
{2027, time.March, 28},
|
|
{2028, time.April, 16},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := easterDate(tc.year)
|
|
want := time.Date(tc.year, tc.month, tc.day, 0, 0, 0, 0, time.UTC)
|
|
if !got.Equal(want) {
|
|
t.Errorf("Easter %d: got %s, want %s", tc.year, got.Format("2006-01-02"), want.Format("2006-01-02"))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNthMonday(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
year int
|
|
month time.Month
|
|
n int
|
|
want string
|
|
}{
|
|
{"Family Day 2026", 2026, time.February, 3, "2026-02-16"},
|
|
{"Labour Day 2026", 2026, time.September, 1, "2026-09-07"},
|
|
{"Thanksgiving CA 2026", 2026, time.October, 2, "2026-10-12"},
|
|
{"Family Day 2027", 2027, time.February, 3, "2027-02-15"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := nthMonday(tc.year, tc.month, tc.n)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("%s: got %s, want %s", tc.desc, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMondayBeforeMay25(t *testing.T) {
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2026, "2026-05-18"},
|
|
{2027, "2027-05-24"},
|
|
{2028, "2028-05-22"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := mondayBeforeMay25(tc.year)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("Victoria Day %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Hebrew Calendar Tests ───────────────────────────────────────────────────
|
|
|
|
func TestHebrewRoshHashanah(t *testing.T) {
|
|
// Verified against hebcal.com
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2023, "2023-09-16"}, // 5784
|
|
{2024, "2024-10-03"}, // 5785
|
|
{2025, "2025-09-23"}, // 5786
|
|
{2026, "2026-09-12"}, // 5787
|
|
{2027, "2027-10-02"}, // 5788
|
|
{2028, "2028-09-21"}, // 5789
|
|
{2030, "2030-09-28"}, // 5791
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := hebrewRoshHashanah(tc.year)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("Rosh Hashanah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHebrewYomKippur(t *testing.T) {
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2026, "2026-09-21"},
|
|
{2027, "2027-10-11"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := hebrewYomKippur(tc.year)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("Yom Kippur %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHebrewPassover(t *testing.T) {
|
|
// 15 Nisan (first full day of Pesach)
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2024, "2024-04-23"},
|
|
{2025, "2025-04-13"},
|
|
{2026, "2026-04-02"},
|
|
{2027, "2027-04-22"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := hebrewPassover(tc.year)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("Passover %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHebrewHanukkah(t *testing.T) {
|
|
// 25 Kislev (first full day)
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2024, "2024-12-26"},
|
|
{2025, "2025-12-15"},
|
|
{2026, "2026-12-05"},
|
|
{2027, "2027-12-25"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := hebrewHanukkah(tc.year)
|
|
if got.Format("2006-01-02") != tc.want {
|
|
t.Errorf("Hanukkah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Tabular Islamic Calendar Tests ──────────────────────────────────────────
|
|
|
|
func TestIslamicEidAlFitr(t *testing.T) {
|
|
// Tabular Islamic calendar — ±1 day from observed
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2026, "2026-03-20"},
|
|
{2027, "2027-03-10"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
dates := tabularIslamicDates(tc.year, 10, 1)
|
|
if len(dates) == 0 {
|
|
t.Errorf("Eid al-Fitr %d: no dates returned", tc.year)
|
|
continue
|
|
}
|
|
got := dates[0].Format("2006-01-02")
|
|
if got != tc.want {
|
|
t.Errorf("Eid al-Fitr %d: got %s, want %s", tc.year, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIslamicEidAlAdha(t *testing.T) {
|
|
tests := []struct {
|
|
year int
|
|
want string
|
|
}{
|
|
{2026, "2026-05-27"},
|
|
{2027, "2027-05-17"}, // Tabular Islamic calendar ±1 day from observed
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
dates := tabularIslamicDates(tc.year, 12, 10)
|
|
if len(dates) == 0 {
|
|
t.Errorf("Eid al-Adha %d: no dates returned", tc.year)
|
|
continue
|
|
}
|
|
got := dates[0].Format("2006-01-02")
|
|
if got != tc.want {
|
|
t.Errorf("Eid al-Adha %d: got %s, want %s", tc.year, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Integration Tests ───────────────────────────────────────────────────────
|
|
|
|
func TestFloatingHolidays_AllComputed(t *testing.T) {
|
|
for year := 2024; year <= 2035; year++ {
|
|
holidays := floatingHolidays(year)
|
|
if len(holidays) < 14 { // at least: 4 Easter + Nowruz + 4 Canadian + 4 Hebrew + 2 Islamic
|
|
t.Errorf("year %d: only %d floating holidays, expected at least 14", year, len(holidays))
|
|
}
|
|
|
|
// Verify no zero-time dates (would indicate a computation failure)
|
|
for _, h := range holidays {
|
|
if h.Date.IsZero() {
|
|
t.Errorf("year %d: holiday %q has zero date", year, h.Name)
|
|
}
|
|
if h.Date.Year() != year {
|
|
t.Errorf("year %d: holiday %q falls in %d", year, h.Name, h.Date.Year())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFloatingHolidays_DateCollisions(t *testing.T) {
|
|
holidays := floatingHolidays(2026)
|
|
seen := make(map[string][]string)
|
|
for _, h := range holidays {
|
|
key := h.Date.Format("2006-01-02")
|
|
seen[key] = append(seen[key], h.Name)
|
|
}
|
|
for date, names := range seen {
|
|
if len(names) > 1 {
|
|
t.Logf("NOTE: %d holidays on %s: %s", len(names), date, strings.Join(names, ", "))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFixedHolidays_Christmas(t *testing.T) {
|
|
found := false
|
|
for _, h := range majorHolidays {
|
|
if h.Month == 12 && h.Day == 25 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Christmas not in majorHolidays")
|
|
}
|
|
}
|
|
|
|
func TestFixedHolidays_NoDuplicateMonthDay(t *testing.T) {
|
|
seen := make(map[[2]int]string)
|
|
for _, h := range majorHolidays {
|
|
key := [2]int{h.Month, h.Day}
|
|
if prev, ok := seen[key]; ok {
|
|
t.Errorf("duplicate fixed holiday on %d/%d: %s and %s", h.Month, h.Day, prev, h.Name)
|
|
}
|
|
seen[key] = h.Name
|
|
}
|
|
}
|
|
|
|
func TestDailySummary_HolidayBlock(t *testing.T) {
|
|
players := []AdvPlayerDaySummary{
|
|
{DisplayName: "Alice", Activity: "dungeon", Location: "Cellar", Outcome: "success", LootValue: 500, HolidayActions: 2},
|
|
{DisplayName: "Bob", Activity: "mine", Location: "Quarry", Outcome: "success", LootValue: 300, HolidayActions: 1},
|
|
{DisplayName: "Carol", IsDead: true, DeadUntil: "14:00 UTC", Activity: "dungeon", Location: "Cave", Outcome: "death", HolidayActions: 1},
|
|
}
|
|
|
|
text := renderAdvDailySummary("2026-12-25", nil, TwinBeeRewardSummary{}, players, "Christmas")
|
|
|
|
if !strings.Contains(text, "Christmas") {
|
|
t.Error("summary should contain holiday name")
|
|
}
|
|
if !strings.Contains(text, "two actions today") {
|
|
t.Error("summary should mention two actions on holidays")
|
|
}
|
|
if !strings.Contains(text, "before their second action") {
|
|
t.Error("summary should note Carol died before second action")
|
|
}
|
|
|
|
// Without holiday
|
|
textNoHol := renderAdvDailySummary("2026-12-26", nil, TwinBeeRewardSummary{}, players, "")
|
|
if strings.Contains(textNoHol, "two actions") {
|
|
t.Error("non-holiday summary should NOT mention two actions")
|
|
}
|
|
}
|
|
|
|
// ── JDN Roundtrip Test ──────────────────────────────────────────────────────
|
|
|
|
func TestGregorianFromJDN_Roundtrip(t *testing.T) {
|
|
// Verify known JDN ↔ Gregorian conversions
|
|
tests := []struct {
|
|
jdn int
|
|
date string
|
|
}{
|
|
{2451545, "2000-01-01"}, // J2000.0
|
|
{2440588, "1970-01-01"}, // Unix epoch
|
|
{2299161, "1582-10-15"}, // Gregorian calendar adoption
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := gregorianFromJDN(tc.jdn)
|
|
if got.Format("2006-01-02") != tc.date {
|
|
t.Errorf("JDN %d: got %s, want %s", tc.jdn, got.Format("2006-01-02"), tc.date)
|
|
}
|
|
}
|
|
}
|