tasks: compare due dates in the caller's day, not in UTC
dayDelta truncated both instants to a UTC day. A task due at 02:00 Moscow time tonight read as due tomorrow, and one due at 23:00 last night read as due today, so the two classes that decide the whole order were assigned from the wrong calendar. Both sides are now truncated in now's location. Dated work also lost to age alone because the later-due score sat below the age cap, and the tail said "и ещё 3" with no noun and no Russian plural agreement. The page hardcoded time.Now, so none of this was testable from a fixed clock. It now takes an injectable clock, parses the due date in that clock's location, parses ids and weights with strconv instead of a hand-rolled scan, caps the resolved table and says so, shows who resolved each row, and reports a promotion as the confirmation it is. Found in review of #61.
This commit is contained in:
@@ -175,3 +175,71 @@ func TestFormatRUEmpty(t *testing.T) {
|
||||
t.Errorf("reply = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A due date read back from the store is a UTC instant, so comparing calendar
|
||||
// days in ITS location put every date a day out east of Greenwich: the row said
|
||||
// "сегодня" for a task due tomorrow, and "просрочено на день" on the due date
|
||||
// itself while the due column one cell over said otherwise.
|
||||
func TestRankComparesDaysInTheCallersLocation(t *testing.T) {
|
||||
tz := time.FixedZone("UTC+4", 4*3600)
|
||||
// Entered on the web form as 2026-08-02 local, stored and read back as UTC.
|
||||
due := time.Date(2026, 8, 2, 0, 0, 0, 0, tz).UTC()
|
||||
local := time.Date(2026, 8, 1, 10, 0, 0, 0, tz)
|
||||
|
||||
got := Rank([]Item{{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Due: &due, Created: local}}, local)
|
||||
if got[0].Reason != "завтра" {
|
||||
t.Errorf("reason = %q, want завтра on the day before", got[0].Reason)
|
||||
}
|
||||
// The morning of the due date itself.
|
||||
onTheDay := time.Date(2026, 8, 2, 10, 0, 0, 0, tz)
|
||||
got = Rank([]Item{{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Due: &due, Created: local}}, onTheDay)
|
||||
if got[0].Reason != "сегодня" {
|
||||
t.Errorf("reason = %q, want сегодня on the due date", got[0].Reason)
|
||||
}
|
||||
if got[0].Score != scoreDueToday {
|
||||
t.Errorf("score = %v, want %v", got[0].Score, float64(scoreDueToday))
|
||||
}
|
||||
}
|
||||
|
||||
// "срочно" and "важно" are two rungs and the read-back said "важно" for both,
|
||||
// which reports a word he did not say.
|
||||
func TestRankNamesTheUrgencyHeStated(t *testing.T) {
|
||||
got := Rank([]Item{
|
||||
{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Weight: 3, Created: now()},
|
||||
{ID: 2, Text: "починить кран", Status: StatusOpen, Weight: 2, Created: now()},
|
||||
}, now())
|
||||
if got[0].Reason != "срочно" {
|
||||
t.Errorf("reason = %q, want срочно", got[0].Reason)
|
||||
}
|
||||
if got[1].Reason != "важно" {
|
||||
t.Errorf("reason = %q, want важно", got[1].Reason)
|
||||
}
|
||||
}
|
||||
|
||||
// The package doc guarantees a class ordering. Age used to invert it: an
|
||||
// undated task at the age cap outscored a dated one three weeks out.
|
||||
func TestRankDatedWorkBeatsAgeAlone(t *testing.T) {
|
||||
got := Rank([]Item{
|
||||
{ID: 1, Text: "старьё", Status: StatusOpen, Created: now().AddDate(0, 0, -70)},
|
||||
{ID: 2, Text: "через три недели", Status: StatusOpen, Due: at(2026, 8, 22), Created: now()},
|
||||
}, now())
|
||||
if got[0].Text != "через три недели" {
|
||||
t.Errorf("order = %v, want the dated task first", texts(got))
|
||||
}
|
||||
}
|
||||
|
||||
// A bare "и ещё 5" trails off when spoken.
|
||||
func TestFormatRUTailCarriesTheNoun(t *testing.T) {
|
||||
var items []Item
|
||||
for i := 0; i < SpokenLimit+3; i++ {
|
||||
items = append(items, Item{ID: int64(i), Text: "дело", Status: StatusOpen, Created: now()})
|
||||
}
|
||||
if got := FormatRU(Rank(items, now())); !strings.Contains(got, "и ещё 3 задачи") {
|
||||
t.Errorf("reply = %q, want the count with its noun", got)
|
||||
}
|
||||
for n, want := range map[int]string{1: "задача", 2: "задачи", 5: "задач", 11: "задач", 21: "задача"} {
|
||||
if got := pluralTasksRU(n); got != want {
|
||||
t.Errorf("pluralTasksRU(%d) = %q, want %q", n, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user