From 06f34745698665be807d59cd143d6e8c5174115c Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 16 Jul 2026 06:31:26 +0000 Subject: Infer a default estimate from same-project/label averages --- internal/store/estimate_inference_test.go | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 internal/store/estimate_inference_test.go (limited to 'internal/store/estimate_inference_test.go') diff --git a/internal/store/estimate_inference_test.go b/internal/store/estimate_inference_test.go new file mode 100644 index 0000000..9321f11 --- /dev/null +++ b/internal/store/estimate_inference_test.go @@ -0,0 +1,90 @@ +// internal/store/estimate_inference_test.go +package store + +import ( + "testing" + "time" + + "task-dashboard/internal/models" +) + +func TestAverageEstimateForProject_AveragesUserEnteredEstimates(t *testing.T) { + s := newNativeTasksTestStore(t) + project, err := s.CreateProject("Sailing prep", "#3B82F6") + if err != nil { + t.Fatal(err) + } + due := time.Now() + for _, minutes := range []int{30, 60} { + task := models.Task{ID: newTaskID(), Content: "task", ProjectID: project.ID, EstimatedMinutes: minutes, DueDate: &due} + if err := s.CreateNativeTask(task); err != nil { + t.Fatal(err) + } + } + // An unestimated task under the same project must not skew the average. + if err := s.CreateNativeTask(models.Task{ID: "t-unestimated", Content: "no estimate", ProjectID: project.ID}); err != nil { + t.Fatal(err) + } + + avg, ok, err := s.AverageEstimateForProject(project.ID) + if err != nil { + t.Fatalf("AverageEstimateForProject: %v", err) + } + if !ok { + t.Fatal("expected ok = true") + } + if avg != 45 { + t.Errorf("avg = %d, want 45", avg) + } +} + +func TestAverageEstimateForProject_NoEstimatedTasks_ReturnsNotOK(t *testing.T) { + s := newNativeTasksTestStore(t) + project, err := s.CreateProject("Empty", "#111111") + if err != nil { + t.Fatal(err) + } + + _, ok, err := s.AverageEstimateForProject(project.ID) + if err != nil { + t.Fatal(err) + } + if ok { + t.Error("expected ok = false when no tasks have an estimate") + } +} + +func TestAverageEstimateForLabel_AveragesUserEnteredEstimates(t *testing.T) { + s := newNativeTasksTestStore(t) + for _, minutes := range []int{20, 40} { + task := models.Task{ID: newTaskID(), Content: "task", Labels: []string{"errands"}, EstimatedMinutes: minutes} + if err := s.CreateNativeTask(task); err != nil { + t.Fatal(err) + } + } + if err := s.CreateNativeTask(models.Task{ID: "t-other-label", Content: "other", Labels: []string{"unrelated"}, EstimatedMinutes: 100}); err != nil { + t.Fatal(err) + } + + avg, ok, err := s.AverageEstimateForLabel("errands") + if err != nil { + t.Fatalf("AverageEstimateForLabel: %v", err) + } + if !ok { + t.Fatal("expected ok = true") + } + if avg != 30 { + t.Errorf("avg = %d, want 30", avg) + } +} + +func TestAverageEstimateForLabel_NoMatches_ReturnsNotOK(t *testing.T) { + s := newNativeTasksTestStore(t) + _, ok, err := s.AverageEstimateForLabel("nonexistent") + if err != nil { + t.Fatal(err) + } + if ok { + t.Error("expected ok = false") + } +} -- cgit v1.2.3