7b4fb6229a
"добавь в список" was a marker in two places: task_phrases.json for task capture, and listCapturePrefixes for the grocery list. ListGrammars is wired before TaskCaptureGrammar in buildRouter, so the list claimed every one of them, and takeListTag does not know "дел" as a list name — "добавь в список дел хлеб" filed a grocery item called "дел хлеб". The bare marker stays a grocery item, because an unnamed list already defaults to покупки and the task side always names its list. A named task list now declines in ParseListCapture, ParseListQuery and ParseListRemove, so the turn falls through to task capture. The bare forms are gone from task_phrases.json, so the data says what the code does rather than being shadowed by grammar order. Reversible if he asks for the other default: move the two bare phrases back and the list will need to decline them instead.
149 lines
6.4 KiB
Go
149 lines
6.4 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNamedTaskListIsNotAGroceryItem — "добавь в список" is a marker on both
|
|
// sides, and ListGrammars is wired before TaskCaptureGrammar, so the list used
|
|
// to claim every one of them (Vikunja #520). takeListTag does not know "дел" as
|
|
// a list name, so the item filed was "дел хлеб". The bare marker stays a
|
|
// grocery item; a named task list falls through to task capture.
|
|
func TestNamedTaskListIsNotAGroceryItem(t *testing.T) {
|
|
for _, in := range []string{
|
|
"добавь в список дел хлеб",
|
|
"добавь в список задач позвонить врачу",
|
|
"запиши в список дел купить молоко",
|
|
"add to the list of todos milk",
|
|
} {
|
|
if got, ok := ParseListCapture(in); ok {
|
|
t.Errorf("ParseListCapture(%q) claimed it as list %q item %q, want a pass to task capture",
|
|
in, got.List, got.Item)
|
|
}
|
|
}
|
|
// The bare form is still the grocery list, and the tagged forms are untouched.
|
|
for _, tc := range []struct{ in, list, item string }{
|
|
{"добавь в список хлеб", "покупки", "хлеб"},
|
|
{"добавь в список покупок молоко", "покупки", "молоко"},
|
|
{"добавь в список аптеку витамины", "аптека", "витамины"},
|
|
} {
|
|
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)
|
|
}
|
|
}
|
|
// A read-back and a cross-off name the same lists and must split the same way.
|
|
if _, ok := ParseListQuery("покажи список дел"); ok {
|
|
t.Error(`ParseListQuery("покажи список дел") claimed a task list`)
|
|
}
|
|
if got, ok := ParseListRemove("вычеркни из списка дел хлеб"); ok {
|
|
t.Errorf("ParseListRemove claimed a task list: %q / %q", got.List, got.Item)
|
|
}
|
|
}
|