diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-02-03 15:15:07 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-02-03 15:15:07 -1000 |
| commit | 9f35f7149d8fb790bbe8e4f0ee74f895aea1fc58 (patch) | |
| tree | 5faa41878609a5c9c332b794a85300090d65bec5 /internal/handlers/handlers.go | |
| parent | f10044eac1997537bcdf7699f5b4284aac16f8e2 (diff) | |
Refactor template rendering with Renderer interface for testability
Introduce a Renderer interface to abstract template rendering, enabling
tests to use MockRenderer instead of requiring real template files.
Changes:
- Add renderer.go with Renderer interface, TemplateRenderer, and MockRenderer
- Update Handler struct to use Renderer instead of *template.Template
- Update HTMLResponse() to accept Renderer interface
- Replace all h.templates.ExecuteTemplate() calls with h.renderer.Render()
- Update all tests to use MockRenderer, removing template file dependencies
This eliminates 15+ tests that previously skipped with "Templates not
available" and improves test isolation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/handlers.go')
| -rw-r--r-- | internal/handlers/handlers.go | 44 |
1 files changed, 22 insertions, 22 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index f0f2a19..bd05cd0 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -31,7 +31,7 @@ type Handler struct { googleCalendarClient api.GoogleCalendarAPI googleTasksClient api.GoogleTasksAPI config *config.Config - templates *template.Template + renderer Renderer } // New creates a new Handler instance @@ -61,7 +61,7 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat googleCalendarClient: googleCalendar, googleTasksClient: googleTasks, config: cfg, - templates: tmpl, + renderer: NewTemplateRenderer(tmpl), } } @@ -84,8 +84,8 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { } // Render template - if h.templates == nil { - http.Error(w, "Templates not loaded", http.StatusInternalServerError) + if h.renderer == nil { + http.Error(w, "Renderer not configured", http.StatusInternalServerError) return } @@ -106,7 +106,7 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { BackgroundURL: backgroundURL, } - if err := h.templates.ExecuteTemplate(w, "index.html", data); err != nil { + if err := h.renderer.Render(w, "index.html", data); err != nil { http.Error(w, "Failed to render template", http.StatusInternalServerError) log.Printf("Error rendering template: %v", err) } @@ -177,7 +177,7 @@ func (h *Handler) HandleTasksTab(w http.ResponseWriter, r *http.Request) { JSONError(w, http.StatusInternalServerError, "Failed to load tasks", err) return } - HTMLResponse(w, h.templates, "tasks-tab", data) + HTMLResponse(w, h.renderer, "tasks-tab", data) } // HandleRefreshTab refreshes and re-renders the specified tab @@ -187,7 +187,7 @@ func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) { JSONError(w, http.StatusInternalServerError, "Failed to refresh", err) return } - HTMLResponse(w, h.templates, "tasks-tab", data) + HTMLResponse(w, h.renderer, "tasks-tab", data) } // aggregateData fetches and caches data from all sources concurrently @@ -516,7 +516,7 @@ func (h *Handler) HandleCreateCard(w http.ResponseWriter, r *http.Request) { return } - HTMLResponse(w, h.templates, "trello-board", targetBoard) + HTMLResponse(w, h.renderer, "trello-board", targetBoard) } // HandleCompleteCard marks a Trello card as complete @@ -574,7 +574,7 @@ func (h *Handler) HandleCreateTask(w http.ResponseWriter, r *http.Request) { Projects []models.Project }{Tasks: tasks, Projects: projects} - HTMLResponse(w, h.templates, "todoist-tasks", data) + HTMLResponse(w, h.renderer, "todoist-tasks", data) } // HandleCompleteTask marks a Todoist task as complete @@ -698,7 +698,7 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl Source string Title string }{id, source, title} - HTMLResponse(w, h.templates, "completed-atom", data) + HTMLResponse(w, h.renderer, "completed-atom", data) } else { // Invalidate cache to force refresh switch source { @@ -1019,7 +1019,7 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) { Today: config.Now().Format("2006-01-02"), } - HTMLResponse(w, h.templates, "tasks-tab", data) + HTMLResponse(w, h.renderer, "tasks-tab", data) } // HandleTabPlanning renders the Planning tab with structured sections @@ -1153,7 +1153,7 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { Today: today.Format("2006-01-02"), } - HTMLResponse(w, h.templates, "planning-tab", data) + HTMLResponse(w, h.renderer, "planning-tab", data) } // CombinedMeal represents multiple meals combined for same date+mealType @@ -1214,7 +1214,7 @@ func (h *Handler) HandleTabMeals(w http.ResponseWriter, r *http.Request) { return mealTypeOrder(combined[i].MealType) < mealTypeOrder(combined[j].MealType) }) - HTMLResponse(w, h.templates, "meals-tab", struct{ Meals []CombinedMeal }{combined}) + HTMLResponse(w, h.renderer, "meals-tab", struct{ Meals []CombinedMeal }{combined}) } // mealTypeOrder returns sort order for meal types @@ -1236,7 +1236,7 @@ func (h *Handler) HandleTabShopping(w http.ResponseWriter, r *http.Request) { ctx := r.Context() stores := h.aggregateShoppingLists(ctx) grouped := r.URL.Query().Get("grouped") != "false" // Default to grouped - HTMLResponse(w, h.templates, "shopping-tab", struct { + HTMLResponse(w, h.renderer, "shopping-tab", struct { Stores []models.ShoppingStore Grouped bool }{stores, grouped}) @@ -1287,7 +1287,7 @@ func (h *Handler) HandleShoppingQuickAdd(w http.ResponseWriter, r *http.Request) } return items[i].Name < items[j].Name }) - HTMLResponse(w, h.templates, "shopping-mode-items", struct { + HTMLResponse(w, h.renderer, "shopping-mode-items", struct { StoreName string Items []models.UnifiedShoppingItem }{storeName, items}) @@ -1295,7 +1295,7 @@ func (h *Handler) HandleShoppingQuickAdd(w http.ResponseWriter, r *http.Request) } // Return refreshed shopping tab - HTMLResponse(w, h.templates, "shopping-tab", struct { + HTMLResponse(w, h.renderer, "shopping-tab", struct { Stores []models.ShoppingStore Grouped bool }{allStores, true}) @@ -1336,7 +1336,7 @@ func (h *Handler) HandleShoppingToggle(w http.ResponseWriter, r *http.Request) { // Return refreshed shopping tab stores := h.aggregateShoppingLists(r.Context()) - HTMLResponse(w, h.templates, "shopping-tab", struct { + HTMLResponse(w, h.renderer, "shopping-tab", struct { Stores []models.ShoppingStore Grouped bool }{stores, true}) @@ -1389,7 +1389,7 @@ func (h *Handler) HandleShoppingMode(w http.ResponseWriter, r *http.Request) { CSRFToken: auth.GetCSRFTokenFromContext(ctx), } - HTMLResponse(w, h.templates, "shopping-mode.html", data) + HTMLResponse(w, h.renderer, "shopping-mode.html", data) } // HandleShoppingModeToggle toggles an item in shopping mode and returns updated list @@ -1447,7 +1447,7 @@ func (h *Handler) HandleShoppingModeToggle(w http.ResponseWriter, r *http.Reques return items[i].Name < items[j].Name }) - HTMLResponse(w, h.templates, "shopping-mode-items", struct { + HTMLResponse(w, h.renderer, "shopping-mode-items", struct { StoreName string Items []models.UnifiedShoppingItem }{storeName, items}) @@ -1505,7 +1505,7 @@ func (h *Handler) HandleShoppingModeComplete(w http.ResponseWriter, r *http.Requ return items[i].Name < items[j].Name }) - HTMLResponse(w, h.templates, "shopping-mode-items", struct { + HTMLResponse(w, h.renderer, "shopping-mode-items", struct { StoreName string Items []models.UnifiedShoppingItem }{storeName, items}) @@ -1649,12 +1649,12 @@ func (h *Handler) aggregateShoppingLists(ctx context.Context) []models.ShoppingS // HandleTabConditions renders the Conditions tab with live feeds func (h *Handler) HandleTabConditions(w http.ResponseWriter, r *http.Request) { - HTMLResponse(w, h.templates, "conditions-tab", nil) + HTMLResponse(w, h.renderer, "conditions-tab", nil) } // HandleConditionsPage renders the standalone Conditions page with live feeds func (h *Handler) HandleConditionsPage(w http.ResponseWriter, r *http.Request) { - if err := h.templates.ExecuteTemplate(w, "conditions.html", nil); err != nil { + if err := h.renderer.Render(w, "conditions.html", nil); err != nil { http.Error(w, "Failed to render conditions page", http.StatusInternalServerError) log.Printf("Error rendering conditions page: %v", err) } |
