filter_test.go

  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}