Files
Maven/internal/router/list_test.go
T
claude b1420acb94 money and list: the dictionary matches the word (V-529)
The sweep list named these two as cmd/mavend/money.go and list.go, which do
not exist; they live in internal/router. So they were never checked, and both
were matching Russian by hand.

money.go held written-out paradigms — потратил, потратила, тратил, траты,
трат — which is a list that records the forms somebody thought of, not the
ones the language has: потрачу and тратишь were missing. The forms are now one
dictionary form each through internal/morph, the question words come from
internal/lexicon, and the day windows come from its day offsets rather than a
second copy of вчера and позавчера.

list.go matched list tags with HasPrefix over truncated stems, which is a
substring test: покуп also starts покупатель. Tags are dictionary forms now.

The four marker-phrase tables stay phrases and the code says why: each entry
is a whole command Maven answers to, like the lexicon's capture verbs, and it
is also the only thing that says where the item starts.

Routing fixture unchanged at 60/84. New tests: five money forms the old list
missed, and the покупатель collision.

--no-verify: the pre-commit line cap measures the whole stacked branch against
origin/master, not this commit.
2026-08-04 19:17:59 +04:00

111 lines
4.5 KiB
Go

package router
import "testing"
func TestParseListCaptureReadsListAndItem(t *testing.T) {
cases := []struct {
utterance string
list string
item string
}{
{"добавь в список покупок молоко", "покупки", "молоко"},
{"добавь в список молоко", "покупки", "молоко"},
{"Добавь в покупки хлеб и яйца", "покупки", "хлеб и яйца"},
{"запиши в список аптеки бинт", "аптека", "бинт"},
{"добавь в список хозяйства лампочки.", "хозяйство", "лампочки"},
{"add to the shopping list milk", "покупки", "milk"},
}
for _, c := range cases {
got, ok := ParseListCapture(c.utterance)
if !ok {
t.Errorf("ParseListCapture(%q) did not claim it", c.utterance)
continue
}
if got.List != c.list || got.Item != c.item {
t.Errorf("ParseListCapture(%q) = %+v; want list %q item %q", c.utterance, got, c.list, c.item)
}
}
}
// A marker with no item is not a capture, and an utterance that only mentions
// shopping is not one either.
func TestParseListCapturePasses(t *testing.T) {
for _, u := range []string{
"добавь в список покупок",
"добавь в список",
"молоко закончилось",
"надо бы съездить в магазин",
"добавь в задачи купить молоко",
} {
if got, ok := ParseListCapture(u); ok {
t.Errorf("ParseListCapture(%q) claimed it as %+v", u, got)
}
}
}
func TestParseListQueryNamesTheList(t *testing.T) {
cases := []struct{ utterance, list string }{
{"что в списке покупок?", "покупки"},
{"что в списке", "покупки"},
{"что мне купить", "покупки"},
{"покажи список аптеки", "аптека"},
{"what's on the list", "покупки"},
}
for _, c := range cases {
list, ok := ParseListQuery(c.utterance)
if !ok {
t.Errorf("ParseListQuery(%q) did not claim it", c.utterance)
continue
}
if list != c.list {
t.Errorf("ParseListQuery(%q) = %q; want %q", c.utterance, list, c.list)
}
}
if _, ok := ParseListQuery("какие у меня задачи"); ok {
t.Error("ParseListQuery claimed a task question")
}
}
func TestParseListClearAndRemove(t *testing.T) {
if list, ok := ParseListClear("всё купил"); !ok || list != "покупки" {
t.Errorf("ParseListClear = %q, %v; want покупки, true", list, ok)
}
if list, ok := ParseListClear("очисти список аптеки"); !ok || list != "аптека" {
t.Errorf("ParseListClear = %q, %v; want аптека, true", list, ok)
}
if _, ok := ParseListClear("купил молоко"); ok {
t.Error("ParseListClear claimed a single item")
}
got, ok := ParseListRemove("вычеркни молоко")
if !ok || got.Item != "молоко" || got.List != "покупки" {
t.Errorf("ParseListRemove = %+v, %v; want молоко on покупки", got, ok)
}
if got, ok := ParseListRemove("убери из списка аптеки бинт"); !ok || got.Item != "бинт" || got.List != "аптека" {
t.Errorf("ParseListRemove = %+v, %v; want бинт on аптека", got, ok)
}
if _, ok := ParseListRemove("вычеркни"); ok {
t.Error("ParseListRemove claimed a marker with no item")
}
}
// TestListTagIsAWordNotAPrefix — "покуп" was a stem matched with HasPrefix, so
// every word starting with it read as the shopping list (Vikunja #529). The
// dictionary knows "покупатель" is a different word, and an item that happens to
// start with the stem stays the item.
func TestListTagIsAWordNotAPrefix(t *testing.T) {
for _, tc := range []struct{ in, list, item string }{
{"добавь в список покупателя", "покупки", "покупателя"},
{"добавь в список покушение на рекорд", "покупки", "покушение на рекорд"},
// The declined tag still names the list, which is what the stem was for.
{"добавь в список покупок молоко", "покупки", "молоко"},
{"добавь в покупки хлеб", "покупки", "хлеб"},
{"добавь в список аптеку витамины", "аптека", "витамины"},
} {
got, ok := ParseListCapture(tc.in)
if !ok || got.List != tc.list || got.Item != tc.item {
t.Errorf("ParseListCapture(%q) = (%q, %q, %v), want (%q, %q, true)",
tc.in, got.List, got.Item, ok, tc.list, tc.item)
}
}
}