1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package lunatask_test
6
7import (
8 "testing"
9 "time"
10
11 lunatask "git.secluded.site/go-lunatask"
12)
13
14func TestFilterTasks(t *testing.T) {
15 t.Parallel()
16
17 today := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)
18 yesterday := today.Add(-24 * time.Hour)
19 tomorrow := today.Add(24 * time.Hour)
20
21 areaA := "area-a"
22 areaB := "area-b"
23 goalX := "goal-x"
24 goalY := "goal-y"
25
26 tasks := []lunatask.Task{
27 {ID: "1", AreaID: &areaA, GoalID: &goalX, Status: ptr(lunatask.StatusNext)},
28 {ID: "2", AreaID: &areaA, GoalID: &goalY, Status: ptr(lunatask.StatusLater)},
29 {ID: "3", AreaID: &areaB, Status: ptr(lunatask.StatusCompleted), CompletedAt: &yesterday},
30 {ID: "4", AreaID: &areaB, Status: ptr(lunatask.StatusCompleted), CompletedAt: &tomorrow},
31 {ID: "5", Status: ptr(lunatask.StatusInProgress)},
32 }
33
34 completed := ptr(lunatask.StatusCompleted)
35
36 tests := []struct {
37 name string
38 opts *lunatask.TaskFilterOptions
39 wantIDs []string
40 }{
41 {"nil_opts_returns_all", nil, []string{"1", "2", "3", "4", "5"}},
42 {"empty_opts_excludes_old_completed", &lunatask.TaskFilterOptions{Today: today}, []string{"1", "2", "4", "5"}},
43 {"include_completed", &lunatask.TaskFilterOptions{IncludeCompleted: true}, []string{"1", "2", "3", "4", "5"}},
44 {"filter_by_area", &lunatask.TaskFilterOptions{AreaID: &areaA, IncludeCompleted: true}, []string{"1", "2"}},
45 {"filter_by_goal", &lunatask.TaskFilterOptions{GoalID: &goalX, IncludeCompleted: true}, []string{"1"}},
46 {"filter_by_status", &lunatask.TaskFilterOptions{Status: completed, IncludeCompleted: true}, []string{"3", "4"}},
47 {"filter_by_status_includes_old", &lunatask.TaskFilterOptions{Status: completed, Today: today}, []string{"3", "4"}},
48 {"combined_area_and_status", &lunatask.TaskFilterOptions{AreaID: &areaB, Status: completed}, []string{"3", "4"}},
49 {"no_matches", &lunatask.TaskFilterOptions{AreaID: ptr("nonexistent"), IncludeCompleted: true}, []string{}},
50 }
51
52 for _, testCase := range tests {
53 t.Run(testCase.name, func(t *testing.T) {
54 t.Parallel()
55 assertFilterResult(t, tasks, testCase.opts, testCase.wantIDs)
56 })
57 }
58}
59
60func TestIsOldCompleted(t *testing.T) {
61 t.Parallel()
62
63 today := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)
64 yesterday := today.Add(-24 * time.Hour)
65 tomorrow := today.Add(24 * time.Hour)
66 completed := ptr(lunatask.StatusCompleted)
67
68 tests := []struct {
69 name string
70 task lunatask.Task
71 today time.Time
72 expect bool
73 }{
74 {"not_completed", lunatask.Task{Status: ptr(lunatask.StatusNext)}, today, false},
75 {"nil_status", lunatask.Task{}, today, false},
76 {"completed_yesterday", lunatask.Task{Status: completed, CompletedAt: &yesterday}, today, true},
77 {"completed_tomorrow", lunatask.Task{Status: completed, CompletedAt: &tomorrow}, today, false},
78 {"completed_nil_timestamp", lunatask.Task{Status: completed}, today, true},
79 {"completed_exactly_today", lunatask.Task{Status: completed, CompletedAt: &today}, today, false},
80 }
81
82 for _, testCase := range tests {
83 t.Run(testCase.name, func(t *testing.T) {
84 t.Parallel()
85
86 got := lunatask.IsOldCompleted(testCase.task, testCase.today)
87 if got != testCase.expect {
88 t.Errorf("IsOldCompleted() = %v, want %v", got, testCase.expect)
89 }
90 })
91 }
92}
93
94func TestFilterTasks_NilTaskFields(t *testing.T) {
95 t.Parallel()
96
97 areaA := "area-a"
98 goalX := "goal-x"
99
100 tasks := []lunatask.Task{
101 {ID: "1"},
102 {ID: "2", AreaID: &areaA},
103 {ID: "3", GoalID: &goalX},
104 }
105
106 tests := []struct {
107 name string
108 opts *lunatask.TaskFilterOptions
109 wantIDs []string
110 }{
111 {
112 "area_filter_skips_nil_area",
113 &lunatask.TaskFilterOptions{AreaID: &areaA, IncludeCompleted: true},
114 []string{"2"},
115 },
116 {
117 "goal_filter_skips_nil_goal",
118 &lunatask.TaskFilterOptions{GoalID: &goalX, IncludeCompleted: true},
119 []string{"3"},
120 },
121 {
122 "status_filter_skips_nil_status",
123 &lunatask.TaskFilterOptions{Status: ptr(lunatask.StatusNext), IncludeCompleted: true},
124 []string{},
125 },
126 }
127
128 for _, testCase := range tests {
129 t.Run(testCase.name, func(t *testing.T) {
130 t.Parallel()
131 assertFilterResult(t, tasks, testCase.opts, testCase.wantIDs)
132 })
133 }
134}
135
136func assertFilterResult(t *testing.T, tasks []lunatask.Task, opts *lunatask.TaskFilterOptions, wantIDs []string) {
137 t.Helper()
138
139 got := lunatask.FilterTasks(tasks, opts)
140 gotIDs := make([]string, len(got))
141
142 for i, task := range got {
143 gotIDs[i] = task.ID
144 }
145
146 if len(gotIDs) != len(wantIDs) {
147 t.Errorf("FilterTasks() got %v, want %v", gotIDs, wantIDs)
148
149 return
150 }
151
152 for i, id := range gotIDs {
153 if id != wantIDs[i] {
154 t.Errorf("FilterTasks() got %v, want %v", gotIDs, wantIDs)
155
156 return
157 }
158 }
159}