be62660be9
Step 5 of the board build. internal/tasks/stall.go counts three shapes — overdue, sitting longer than StallDays, waiting for confirmation — and states nothing about what any of them means. That is the line internal/memory/behavior.go already drew for habits, and the reason is the same: a 1.7B asked to judge will agree fluently and launder a guess into a decision. A test asserts the wording carries no assessment. Sitting is measured from created_ts, the only clock a live row carries: the store stamps resolved_ts and nothing else. So "no state change in eleven days" is exactly "captured eleven days ago and still live", which is narrower than the plan's wording and is the claim the data supports. A candidate is never counted as overdue, because its due date is Maven's reading of a mail rather than a deadline he set. Not a nag. No tick rule reads the counts; they go on /tasks and into the list reply when he asks, and tickLoop.dayPlan still does not read tasks at all. The empty case renders as nothing: "ничего не залежалось" appended to every list read is a nag with a friendly face. Three say entries, so the page and the spoken list cannot word it differently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SoL7EBdYC5Mhz3DJd49GJy
69 lines
2.5 KiB
Go
69 lines
2.5 KiB
Go
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 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)
|
|
}
|
|
}
|
|
}
|