test: add comprehensive tests for new UX features and fix ISO date timezone bug

Add 33 new test functions covering annotations, undo system, history
formatting, relative date display, and weekday parsing pipeline. Fix ISO
date parsing to use ParseInLocation instead of Parse to respect the
parser's timezone context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 16:42:49 +01:00
parent 07d1a78dfc
commit 04fa9222d8
6 changed files with 1117 additions and 1 deletions
+214
View File
@@ -0,0 +1,214 @@
package engine
import (
"testing"
"time"
)
func TestFormatRelativeDate(t *testing.T) {
// Fix timeNow for deterministic tests
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
// Wednesday, Feb 18, 2026 at 14:30 local time
now := time.Date(2026, 2, 18, 14, 30, 0, 0, time.Local)
timeNow = func() time.Time { return now }
tests := []struct {
name string
input time.Time
expected string
}{
// Core relative dates
{"today", time.Date(2026, 2, 18, 0, 0, 0, 0, time.Local), "today"},
{"today with time", time.Date(2026, 2, 18, 23, 59, 0, 0, time.Local), "today"},
{"tomorrow", time.Date(2026, 2, 19, 0, 0, 0, 0, time.Local), "tomorrow"},
{"yesterday", time.Date(2026, 2, 17, 0, 0, 0, 0, time.Local), "yesterday"},
// Near future
{"in 2d", time.Date(2026, 2, 20, 0, 0, 0, 0, time.Local), "in 2d"},
{"in 7d", time.Date(2026, 2, 25, 0, 0, 0, 0, time.Local), "in 7d"},
{"in 14d", time.Date(2026, 3, 4, 0, 0, 0, 0, time.Local), "in 14d"},
// Near past
{"2d ago", time.Date(2026, 2, 16, 0, 0, 0, 0, time.Local), "2d ago"},
{"7d ago", time.Date(2026, 2, 11, 0, 0, 0, 0, time.Local), "7d ago"},
{"14d ago", time.Date(2026, 2, 4, 0, 0, 0, 0, time.Local), "14d ago"},
// Beyond 14 days - same year
{"15d future", time.Date(2026, 3, 5, 0, 0, 0, 0, time.Local), "Mar 5"},
{"15d past", time.Date(2026, 2, 3, 0, 0, 0, 0, time.Local), "Feb 3"},
// Cross-year
{"next year", time.Date(2027, 6, 15, 0, 0, 0, 0, time.Local), "Jun 15 2027"},
{"last year", time.Date(2025, 12, 1, 0, 0, 0, 0, time.Local), "Dec 1 2025"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := FormatRelativeDate(tt.input)
if result != tt.expected {
t.Errorf("FormatRelativeDate(%v) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestFormatRelativeDate_WeekdayPipeline(t *testing.T) {
// This test reproduces the reported bug:
// On Wednesday, "due:friday" should show "in 2d", not "tomorrow"
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
// Wednesday, Feb 18, 2026
wednesday := time.Date(2026, 2, 18, 10, 0, 0, 0, time.Local)
timeNow = func() time.Time { return wednesday }
parser := NewDateParser(wednesday, time.Monday)
tests := []struct {
name string
weekday string
expectedRel string
expectedDay time.Weekday
}{
{"friday from wednesday", "friday", "in 2d", time.Friday},
{"fri from wednesday", "fri", "in 2d", time.Friday},
{"thursday from wednesday", "thu", "tomorrow", time.Thursday},
{"saturday from wednesday", "sat", "in 3d", time.Saturday},
{"sunday from wednesday", "sun", "in 4d", time.Sunday},
{"monday from wednesday", "mon", "in 5d", time.Monday},
{"tuesday from wednesday", "tue", "in 6d", time.Tuesday},
{"wednesday from wednesday", "wed", "in 7d", time.Wednesday},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parsed, err := parser.ParseDate(tt.weekday)
if err != nil {
t.Fatalf("ParseDate(%q) error: %v", tt.weekday, err)
}
// Verify correct weekday
if parsed.Weekday() != tt.expectedDay {
t.Errorf("ParseDate(%q) weekday = %v, want %v", tt.weekday, parsed.Weekday(), tt.expectedDay)
}
// Verify relative display
rel := FormatRelativeDate(parsed)
if rel != tt.expectedRel {
t.Errorf("FormatRelativeDate(ParseDate(%q)) = %q, want %q (parsed date: %v)",
tt.weekday, rel, tt.expectedRel, parsed)
}
})
}
}
func TestFormatRelativeDate_AllWeekdaysFromAllDays(t *testing.T) {
// Exhaustive: parse every weekday name from every starting day of the week
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
// Week starting Monday Feb 16 2026
weekStart := time.Date(2026, 2, 16, 12, 0, 0, 0, time.Local) // Monday
weekdays := []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
targetWeekdays := []time.Weekday{
time.Monday, time.Tuesday, time.Wednesday, time.Thursday,
time.Friday, time.Saturday, time.Sunday,
}
for fromOffset := 0; fromOffset < 7; fromOffset++ {
fromDate := weekStart.AddDate(0, 0, fromOffset)
fromName := fromDate.Weekday().String()
timeNow = func() time.Time { return fromDate }
parser := NewDateParser(fromDate, time.Monday)
for i, dayName := range weekdays {
t.Run(fromName+"_to_"+dayName, func(t *testing.T) {
parsed, err := parser.ParseDate(dayName)
if err != nil {
t.Fatalf("ParseDate(%q) from %s: %v", dayName, fromName, err)
}
// Must be the correct weekday
if parsed.Weekday() != targetWeekdays[i] {
t.Errorf("wrong weekday: got %v, want %v", parsed.Weekday(), targetWeekdays[i])
}
// Must be in the future (1-7 days from now)
diff := parsed.Sub(time.Date(fromDate.Year(), fromDate.Month(), fromDate.Day(), 0, 0, 0, 0, time.Local))
days := int(diff.Hours() / 24)
if days < 1 || days > 7 {
t.Errorf("ParseDate(%q) from %s: expected 1-7 days ahead, got %d (parsed: %v)",
dayName, fromName, days, parsed)
}
// FormatRelativeDate must match the days offset
rel := FormatRelativeDate(parsed)
if days == 1 && rel != "tomorrow" {
t.Errorf("1 day ahead should be 'tomorrow', got %q", rel)
}
if days > 1 && days <= 7 {
expected := "in " + string(rune('0'+days)) + "d"
if days >= 10 {
// won't happen for weekdays (max 7)
}
if rel != expected {
t.Errorf("from %s, %q: %d days ahead, got rel=%q, want %q",
fromName, dayName, days, rel, expected)
}
}
})
}
}
}
func TestFormatRelativeDate_TimezoneConsistency(t *testing.T) {
// Verify that dates in UTC vs Local don't produce wrong relative strings
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
now := time.Date(2026, 2, 18, 14, 0, 0, 0, time.Local)
timeNow = func() time.Time { return now }
// A date 2 days from now, but in UTC
targetUTC := time.Date(2026, 2, 20, 0, 0, 0, 0, time.UTC)
// Same date in Local
targetLocal := time.Date(2026, 2, 20, 0, 0, 0, 0, time.Local)
relUTC := FormatRelativeDate(targetUTC)
relLocal := FormatRelativeDate(targetLocal)
// Both should show "in 2d" - if UTC shows something different, that's a bug
if relLocal != "in 2d" {
t.Errorf("Local target: expected 'in 2d', got %q", relLocal)
}
// Note: UTC target may differ depending on system timezone.
// This test documents the behavior.
t.Logf("Local timezone: now=%v", now)
t.Logf("UTC target relative: %q, Local target relative: %q", relUTC, relLocal)
if relUTC != relLocal {
t.Logf("WARNING: timezone mismatch detected — UTC shows %q vs Local shows %q", relUTC, relLocal)
t.Logf("This could explain the 'due:friday shows tomorrow' bug if dates are stored/loaded in wrong timezone")
}
}
func TestFormatDateWithRelative(t *testing.T) {
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
now := time.Date(2026, 2, 18, 14, 0, 0, 0, time.Local)
timeNow = func() time.Time { return now }
input := time.Date(2026, 2, 20, 15, 30, 0, 0, time.Local)
result := FormatDateWithRelative(input)
// Should contain both absolute and relative
if result != "2026-02-20 15:30 (in 2d)" {
t.Errorf("FormatDateWithRelative = %q, want %q", result, "2026-02-20 15:30 (in 2d)")
}
}