From 42a4e32daca13b518e64e5821080ff3d6adf0e39 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 26 Jan 2026 16:49:44 -1000 Subject: Use configured timezone throughout codebase - Add config/timezone.go with timezone utilities: - SetDisplayTimezone(), GetDisplayTimezone() - Now(), Today() - current time/date in display TZ - ParseDateInDisplayTZ(), ToDisplayTZ() - parsing helpers - Initialize timezone at startup in main.go - Update all datetime logic to use configured timezone: - handlers/handlers.go - all time.Now() calls - handlers/timeline.go - date parsing - handlers/timeline_logic.go - now calculation - models/atom.go - ComputeUIFields() - models/timeline.go - ComputeDaySection() - api/plantoeat.go - meal date parsing - api/todoist.go - due date parsing - api/trello.go - due date parsing This ensures all dates/times display correctly regardless of server timezone setting. Co-Authored-By: Claude Opus 4.5 --- internal/api/plantoeat_test.go | 126 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 internal/api/plantoeat_test.go (limited to 'internal/api/plantoeat_test.go') diff --git a/internal/api/plantoeat_test.go b/internal/api/plantoeat_test.go new file mode 100644 index 0000000..50ad4ed --- /dev/null +++ b/internal/api/plantoeat_test.go @@ -0,0 +1,126 @@ +package api + +import ( + "strings" + "testing" +) + +func TestParseShoppingListHTML(t *testing.T) { + // Sample HTML structure matching PlanToEat's shopping list + html := ` +
+
+
+

Costco (3)

+
+ + + + +
+
+

Baking

+
    +
  • + +
  • +
  • + +
  • +
+
+
+

Meat

+
    +
  • + +
  • +
+
+
+
+
` + + items, err := parseShoppingListHTML(strings.NewReader(html)) + if err != nil { + t.Fatalf("parseShoppingListHTML failed: %v", err) + } + + if len(items) != 3 { + t.Errorf("Expected 3 items, got %d", len(items)) + } + + // Check first item + if items[0].Name != "Olive oil" { + t.Errorf("Expected first item name 'Olive oil', got '%s'", items[0].Name) + } + if items[0].Quantity != "2 tablespoons" { + t.Errorf("Expected quantity '2 tablespoons', got '%s'", items[0].Quantity) + } + if items[0].Category != "Baking" { + t.Errorf("Expected category 'Baking', got '%s'", items[0].Category) + } + if items[0].Store != "Costco" { + t.Errorf("Expected store 'Costco', got '%s'", items[0].Store) + } + if items[0].ID != "493745871" { + t.Errorf("Expected ID '493745871', got '%s'", items[0].ID) + } + if items[0].Checked { + t.Error("Expected first item to not be checked") + } + + // Check checked item + if !items[1].Checked { + t.Error("Expected second item to be checked") + } + + // Check item from different category + if items[2].Name != "Ground beef" { + t.Errorf("Expected 'Ground beef', got '%s'", items[2].Name) + } + if items[2].Category != "Meat" { + t.Errorf("Expected category 'Meat', got '%s'", items[2].Category) + } +} + +func TestParseShoppingListHTML_EmptyList(t *testing.T) { + html := `
` + + items, err := parseShoppingListHTML(strings.NewReader(html)) + if err != nil { + t.Fatalf("parseShoppingListHTML failed: %v", err) + } + + if len(items) != 0 { + t.Errorf("Expected 0 items for empty list, got %d", len(items)) + } +} + +func TestCleanQuantity(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"1 cup", "1 cup"}, + {"2 tablespoons", "2 tablespoons"}, + {"1\u00a0pound", "1 pound"}, + {" 3 oz ", "3 oz"}, + } + + for _, tt := range tests { + result := cleanQuantity(tt.input) + if result != tt.expected { + t.Errorf("cleanQuantity(%q) = %q, want %q", tt.input, result, tt.expected) + } + } +} -- cgit v1.2.3