338 lines
9.1 KiB
Go
338 lines
9.1 KiB
Go
|
|
package checkvist
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestFilter_WithTag(t *testing.T) {
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Task 1", TagsAsText: "important, urgent"},
|
||
|
|
{ID: 2, Content: "Task 2", TagsAsText: "urgent"},
|
||
|
|
{ID: 3, Content: "Task 3", TagsAsText: ""},
|
||
|
|
}
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
tag string
|
||
|
|
expected []int
|
||
|
|
}{
|
||
|
|
{"filter by important", "important", []int{1}},
|
||
|
|
{"filter by urgent", "urgent", []int{1, 2}},
|
||
|
|
{"filter by nonexistent", "nonexistent", []int{}},
|
||
|
|
{"case insensitive", "IMPORTANT", []int{1}},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
result := NewFilter(tasks).WithTag(tt.tag).Apply()
|
||
|
|
if len(result) != len(tt.expected) {
|
||
|
|
t.Errorf("expected %d tasks, got %d", len(tt.expected), len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for i, task := range result {
|
||
|
|
if task.ID != tt.expected[i] {
|
||
|
|
t.Errorf("expected task ID %d at index %d, got %d", tt.expected[i], i, task.ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithMultipleTags(t *testing.T) {
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Task 1", TagsAsText: "important, urgent, work"},
|
||
|
|
{ID: 2, Content: "Task 2", TagsAsText: "urgent, work"},
|
||
|
|
{ID: 3, Content: "Task 3", TagsAsText: "important"},
|
||
|
|
}
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
tags []string
|
||
|
|
expected []int
|
||
|
|
}{
|
||
|
|
{"single tag", []string{"urgent"}, []int{1, 2}},
|
||
|
|
{"two tags AND", []string{"urgent", "work"}, []int{1, 2}},
|
||
|
|
{"three tags AND", []string{"important", "urgent", "work"}, []int{1}},
|
||
|
|
{"no match", []string{"important", "urgent", "missing"}, []int{}},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
result := NewFilter(tasks).WithTags(tt.tags...).Apply()
|
||
|
|
if len(result) != len(tt.expected) {
|
||
|
|
t.Errorf("expected %d tasks, got %d", len(tt.expected), len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for i, task := range result {
|
||
|
|
if task.ID != tt.expected[i] {
|
||
|
|
t.Errorf("expected task ID %d at index %d, got %d", tt.expected[i], i, task.ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithStatus(t *testing.T) {
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Open task", Status: StatusOpen},
|
||
|
|
{ID: 2, Content: "Closed task", Status: StatusClosed},
|
||
|
|
{ID: 3, Content: "Invalidated task", Status: StatusInvalidated},
|
||
|
|
{ID: 4, Content: "Another open", Status: StatusOpen},
|
||
|
|
}
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
status TaskStatus
|
||
|
|
expected []int
|
||
|
|
}{
|
||
|
|
{"open tasks", StatusOpen, []int{1, 4}},
|
||
|
|
{"closed tasks", StatusClosed, []int{2}},
|
||
|
|
{"invalidated tasks", StatusInvalidated, []int{3}},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
result := NewFilter(tasks).WithStatus(tt.status).Apply()
|
||
|
|
if len(result) != len(tt.expected) {
|
||
|
|
t.Errorf("expected %d tasks, got %d", len(tt.expected), len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for i, task := range result {
|
||
|
|
if task.ID != tt.expected[i] {
|
||
|
|
t.Errorf("expected task ID %d at index %d, got %d", tt.expected[i], i, task.ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithDueBefore(t *testing.T) {
|
||
|
|
now := time.Now()
|
||
|
|
yesterday := now.AddDate(0, 0, -1)
|
||
|
|
tomorrow := now.AddDate(0, 0, 1)
|
||
|
|
nextWeek := now.AddDate(0, 0, 7)
|
||
|
|
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Yesterday", DueDate: &yesterday},
|
||
|
|
{ID: 2, Content: "Tomorrow", DueDate: &tomorrow},
|
||
|
|
{ID: 3, Content: "Next week", DueDate: &nextWeek},
|
||
|
|
{ID: 4, Content: "No due date", DueDate: nil},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).WithDueBefore(now).Apply()
|
||
|
|
if len(result) != 1 {
|
||
|
|
t.Errorf("expected 1 task, got %d", len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if result[0].ID != 1 {
|
||
|
|
t.Errorf("expected task ID 1, got %d", result[0].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithDueAfter(t *testing.T) {
|
||
|
|
now := time.Now()
|
||
|
|
yesterday := now.AddDate(0, 0, -1)
|
||
|
|
tomorrow := now.AddDate(0, 0, 1)
|
||
|
|
nextWeek := now.AddDate(0, 0, 7)
|
||
|
|
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Yesterday", DueDate: &yesterday},
|
||
|
|
{ID: 2, Content: "Tomorrow", DueDate: &tomorrow},
|
||
|
|
{ID: 3, Content: "Next week", DueDate: &nextWeek},
|
||
|
|
{ID: 4, Content: "No due date", DueDate: nil},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).WithDueAfter(now).Apply()
|
||
|
|
if len(result) != 2 {
|
||
|
|
t.Errorf("expected 2 tasks, got %d", len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if result[0].ID != 2 || result[1].ID != 3 {
|
||
|
|
t.Errorf("expected task IDs [2, 3], got [%d, %d]", result[0].ID, result[1].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithDueOn(t *testing.T) {
|
||
|
|
today := time.Now().Truncate(24 * time.Hour)
|
||
|
|
todayMorning := today.Add(9 * time.Hour)
|
||
|
|
todayEvening := today.Add(18 * time.Hour)
|
||
|
|
tomorrow := today.AddDate(0, 0, 1)
|
||
|
|
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Today morning", DueDate: &todayMorning},
|
||
|
|
{ID: 2, Content: "Today evening", DueDate: &todayEvening},
|
||
|
|
{ID: 3, Content: "Tomorrow", DueDate: &tomorrow},
|
||
|
|
{ID: 4, Content: "No due date", DueDate: nil},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).WithDueOn(today).Apply()
|
||
|
|
if len(result) != 2 {
|
||
|
|
t.Errorf("expected 2 tasks, got %d", len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if result[0].ID != 1 || result[1].ID != 2 {
|
||
|
|
t.Errorf("expected task IDs [1, 2], got [%d, %d]", result[0].ID, result[1].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithOverdue(t *testing.T) {
|
||
|
|
today := time.Now().Truncate(24 * time.Hour)
|
||
|
|
yesterday := today.AddDate(0, 0, -1)
|
||
|
|
tomorrow := today.AddDate(0, 0, 1)
|
||
|
|
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Overdue open", DueDate: &yesterday, Status: StatusOpen},
|
||
|
|
{ID: 2, Content: "Overdue closed", DueDate: &yesterday, Status: StatusClosed},
|
||
|
|
{ID: 3, Content: "Future open", DueDate: &tomorrow, Status: StatusOpen},
|
||
|
|
{ID: 4, Content: "No due date", DueDate: nil, Status: StatusOpen},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).WithOverdue().Apply()
|
||
|
|
if len(result) != 1 {
|
||
|
|
t.Errorf("expected 1 task, got %d", len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if result[0].ID != 1 {
|
||
|
|
t.Errorf("expected task ID 1, got %d", result[0].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_WithSearch(t *testing.T) {
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Buy groceries"},
|
||
|
|
{ID: 2, Content: "Call the doctor"},
|
||
|
|
{ID: 3, Content: "Review PR for grocery app"},
|
||
|
|
{ID: 4, Content: "Send email"},
|
||
|
|
}
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
query string
|
||
|
|
expected []int
|
||
|
|
}{
|
||
|
|
{"exact match", "groceries", []int{1}},
|
||
|
|
{"partial match", "grocer", []int{1, 3}},
|
||
|
|
{"case insensitive", "CALL", []int{2}},
|
||
|
|
{"no match", "xyz", []int{}},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
result := NewFilter(tasks).WithSearch(tt.query).Apply()
|
||
|
|
if len(result) != len(tt.expected) {
|
||
|
|
t.Errorf("expected %d tasks, got %d", len(tt.expected), len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for i, task := range result {
|
||
|
|
if task.ID != tt.expected[i] {
|
||
|
|
t.Errorf("expected task ID %d at index %d, got %d", tt.expected[i], i, task.ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_Combined(t *testing.T) {
|
||
|
|
today := time.Now().Truncate(24 * time.Hour)
|
||
|
|
yesterday := today.AddDate(0, 0, -1)
|
||
|
|
tomorrow := today.AddDate(0, 0, 1)
|
||
|
|
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Important task", TagsAsText: "important", Status: StatusOpen, DueDate: &yesterday},
|
||
|
|
{ID: 2, Content: "Important closed", TagsAsText: "important", Status: StatusClosed, DueDate: &yesterday},
|
||
|
|
{ID: 3, Content: "Regular task", TagsAsText: "regular", Status: StatusOpen, DueDate: &yesterday},
|
||
|
|
{ID: 4, Content: "Important future", TagsAsText: "important", Status: StatusOpen, DueDate: &tomorrow},
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter: important AND open AND due before today
|
||
|
|
result := NewFilter(tasks).
|
||
|
|
WithTag("important").
|
||
|
|
WithStatus(StatusOpen).
|
||
|
|
WithDueBefore(today).
|
||
|
|
Apply()
|
||
|
|
|
||
|
|
if len(result) != 1 {
|
||
|
|
t.Errorf("expected 1 task, got %d", len(result))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if result[0].ID != 1 {
|
||
|
|
t.Errorf("expected task ID 1, got %d", result[0].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_EmptyFilters(t *testing.T) {
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Task 1"},
|
||
|
|
{ID: 2, Content: "Task 2"},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).Apply()
|
||
|
|
if len(result) != 2 {
|
||
|
|
t.Errorf("expected 2 tasks, got %d", len(result))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_EmptyTasks(t *testing.T) {
|
||
|
|
result := NewFilter([]Task{}).WithTag("test").Apply()
|
||
|
|
if len(result) != 0 {
|
||
|
|
t.Errorf("expected 0 tasks, got %d", len(result))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_Performance_1000Tasks(t *testing.T) {
|
||
|
|
// Create 1000 tasks with various attributes
|
||
|
|
tasks := make([]Task, 1000)
|
||
|
|
now := time.Now()
|
||
|
|
|
||
|
|
for i := 0; i < 1000; i++ {
|
||
|
|
due := now.AddDate(0, 0, i-500) // -500 to +499 days from now
|
||
|
|
tasks[i] = Task{
|
||
|
|
ID: i + 1,
|
||
|
|
Content: "Task with some content to search through",
|
||
|
|
TagsAsText: "tag1, tag2, tag3",
|
||
|
|
Status: TaskStatus(i % 3),
|
||
|
|
DueDate: &due,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mark important tasks
|
||
|
|
for i := 0; i < 100; i++ {
|
||
|
|
tasks[i].TagsAsText = "important, urgent"
|
||
|
|
}
|
||
|
|
|
||
|
|
start := time.Now()
|
||
|
|
|
||
|
|
// Run a complex filter
|
||
|
|
result := NewFilter(tasks).
|
||
|
|
WithTag("important").
|
||
|
|
WithStatus(StatusOpen).
|
||
|
|
WithDueBefore(now).
|
||
|
|
WithSearch("content").
|
||
|
|
Apply()
|
||
|
|
|
||
|
|
elapsed := time.Since(start)
|
||
|
|
|
||
|
|
// Performance target: <10ms for 1000 tasks
|
||
|
|
if elapsed > 10*time.Millisecond {
|
||
|
|
t.Errorf("filter took %v, expected <10ms", elapsed)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify we got some results
|
||
|
|
if len(result) == 0 {
|
||
|
|
t.Log("No matching tasks found, but performance test passed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFilter_TagsMap(t *testing.T) {
|
||
|
|
// Test that Tags map is also checked
|
||
|
|
tasks := []Task{
|
||
|
|
{ID: 1, Content: "Task 1", Tags: Tags{"important": true}, TagsAsText: ""},
|
||
|
|
{ID: 2, Content: "Task 2", Tags: nil, TagsAsText: "important"},
|
||
|
|
}
|
||
|
|
|
||
|
|
result := NewFilter(tasks).WithTag("important").Apply()
|
||
|
|
if len(result) != 2 {
|
||
|
|
t.Errorf("expected 2 tasks, got %d", len(result))
|
||
|
|
}
|
||
|
|
}
|