package tasks import ( "strings" "testing" "time" ) func stallNow() time.Time { return time.Date(2026, 8, 5, 12, 0, 0, 0, time.UTC) } func TestStallsCountsTheThreeShapes(t *testing.T) { now := stallNow() day := 24 * time.Hour yesterday := now.Add(-day) items := []Item{ // Overdue and sitting at once: it counts in both, because they are two // different things wrong with one task. {ID: 1, Text: "оплатить интернет", Status: StatusOpen, Created: now.Add(-20 * day), Due: &yesterday}, {ID: 2, Text: "купить молоко", Status: StatusOpen, Created: now.Add(-12 * day)}, {ID: 3, Text: "позвонить маме", Status: StatusOpen, Created: now.Add(-time.Hour)}, {ID: 4, Text: "продлить домен", Status: StatusCandidate, Created: now.Add(-30 * day), Due: &yesterday}, } got := Stalls(items, now) if len(got) != 3 { t.Fatalf("shapes = %+v, want overdue, sitting, unconfirmed", got) } if got[0].N != 1 { t.Errorf("overdue = %d, want 1 — a candidate's due date is Maven's reading of a mail", got[0].N) } if got[1].N != 2 { t.Errorf("sitting = %d, want 2", got[1].N) } if got[2].N != 1 { t.Errorf("unconfirmed = %d, want 1", got[2].N) } line := StallsRU(got) for _, want := range []string{"просрочено", "лежит", "подтверждения"} { if !strings.Contains(line, want) { t.Errorf("StallsRU = %q, want it to mention %q", line, want) } } } func TestStallsSaysNothingWhenThereIsNothing(t *testing.T) { now := stallNow() items := []Item{{ID: 1, Text: "купить молоко", Status: StatusOpen, Created: now.Add(-time.Hour)}} if got := Stalls(items, now); len(got) != 0 { t.Fatalf("shapes = %+v, want none", got) } // "ничего не залежалось" appended to every list read is a nag with a // friendly face, so the empty case renders as nothing at all. if got := StallsRU(nil); got != "" { t.Errorf("StallsRU(nil) = %q, want empty", got) } } func TestStallsCountsOverdueByCalendarDay(t *testing.T) { // Same reading as Rank, or the count above the table contradicts the reason // in the row: due at 09:00, asked at 12:00, and it is still due today. now := stallNow() earlier := now.Add(-3 * time.Hour) items := []Item{{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Created: now.Add(-time.Hour), Due: &earlier}} if got := Stalls(items, now); len(got) != 0 { t.Fatalf("shapes = %+v, want none — a task due today is not overdue", got) } } func TestStallsIgnoresATaskWithNoCaptureTime(t *testing.T) { // The zero time is January of year 1, so an unstamped row would count as // sitting forever. now := stallNow() items := []Item{{ID: 1, Text: "купить молоко", Status: StatusOpen}} if got := Stalls(items, now); len(got) != 0 { t.Fatalf("shapes = %+v, want none", got) } } func TestStallsCountsNoJudgement(t *testing.T) { // The line this shape may not cross. Every sentence states a count; none of // them says whether the work matters or should be dropped. now := stallNow() items := []Item{{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Created: now.Add(-40 * 24 * time.Hour)}} line := StallsRU(Stalls(items, now)) for _, banned := range []string{"стоит", "лучше", "надо", "брось", "важно"} { if strings.Contains(line, banned) { t.Errorf("StallsRU = %q — %q is an assessment, and counting is the whole licence here", line, banned) } } }