summaryrefslogtreecommitdiff
path: root/internal/handlers/chains_timeline_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/chains_timeline_test.go')
-rw-r--r--internal/handlers/chains_timeline_test.go69
1 files changed, 67 insertions, 2 deletions
diff --git a/internal/handlers/chains_timeline_test.go b/internal/handlers/chains_timeline_test.go
index e9c8a3a..d4bc369 100644
--- a/internal/handlers/chains_timeline_test.go
+++ b/internal/handlers/chains_timeline_test.go
@@ -5,10 +5,15 @@ import (
"testing"
"time"
+ "task-dashboard/internal/config"
"task-dashboard/internal/models"
)
-func TestBuildTimeline_PopulatesChainBadgeForUnlockedTask(t *testing.T) {
+// Chain steps carry no due date and are kept out of the undated list, so they
+// stay off the day's timeline entirely until deliberately committed. This is the
+// whole point of the pull model: the day contains what was chosen plus real
+// deadlines, so finishing at zero is reachable.
+func TestBuildTimeline_UnlockedChainTaskAbsentUntilCommitted(t *testing.T) {
s, cleanup := setupTestDB(t)
defer cleanup()
@@ -28,6 +33,36 @@ func TestBuildTimeline_PopulatesChainBadgeForUnlockedTask(t *testing.T) {
if err != nil {
t.Fatalf("BuildTimeline: %v", err)
}
+ for _, item := range items {
+ if item.ID == tasks[0].ID {
+ t.Error("uncommitted chain step appeared on the timeline; chain work should only arrive by being committed")
+ }
+ }
+}
+
+func TestBuildTimeline_CommittedChainTaskAppearsWithBadge(t *testing.T) {
+ s, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ chain, err := s.CreateChain("Ham Radio Track", []models.ChainTaskInput{
+ {Content: "Study Technician"}, {Content: "Pass exam"}, {Content: "Study General"},
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ tasks, err := s.GetChainTasks(chain.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := s.SetTaskCommitted(tasks[0].ID, config.Now()); err != nil {
+ t.Fatal(err)
+ }
+
+ now := time.Now()
+ items, err := BuildTimeline(context.Background(), s, now.Add(-time.Hour), now.Add(24*time.Hour))
+ if err != nil {
+ t.Fatalf("BuildTimeline: %v", err)
+ }
var found bool
for _, item := range items {
@@ -42,6 +77,36 @@ func TestBuildTimeline_PopulatesChainBadgeForUnlockedTask(t *testing.T) {
}
}
if !found {
- t.Fatal("expected the unlocked chain task (position 0) to appear in the timeline")
+ t.Fatal("expected the committed chain step to appear on the timeline")
+ }
+}
+
+// A task that is both committed and due today must appear once, not twice.
+func TestBuildTimeline_CommittedAndDueTaskNotDuplicated(t *testing.T) {
+ s, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ due := config.Now()
+ if err := s.CreateNativeTask(models.Task{ID: "dual", Content: "Bills", DueDate: &due}); err != nil {
+ t.Fatal(err)
+ }
+ if err := s.SetTaskCommitted("dual", config.Now()); err != nil {
+ t.Fatal(err)
+ }
+
+ now := time.Now()
+ items, err := BuildTimeline(context.Background(), s, now.Add(-24*time.Hour), now.Add(24*time.Hour))
+ if err != nil {
+ t.Fatalf("BuildTimeline: %v", err)
+ }
+
+ var count int
+ for _, item := range items {
+ if item.ID == "dual" {
+ count++
+ }
+ }
+ if count != 1 {
+ t.Errorf("task appeared %d times, want exactly 1 (committed and due are two reasons for the same row)", count)
}
}