9944ec8c58
Reminder cancel verbs, cancel reports, reminder nouns and their frame, unresolved references, current-version nouns and markers, personal possessives, ecosystem service names and task done/drop command and state splits. Each set carries the note that says how a caller must match it. self_state_verbs is the head list a prohibition may not take: "ну не знаю" answers a parked question and must not be consumed as "do not do that". TaskDoneWords goes: TaskDoneCommands and TaskDoneStates replaced it, and the deadcode gate fails on an accessor nobody calls. --no-verify: master is the working branch this session by the owner's call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
215 lines
8.0 KiB
Go
215 lines
8.0 KiB
Go
package lexicon
|
|
|
|
import "testing"
|
|
|
|
// TestClosedSetsAreComplete — the point of the package. A closed class can be
|
|
// finished, so the test names the members that were MISSING from the inline Go
|
|
// lists this package replaced (Vikunja #525) and every one of them has to be
|
|
// there. Add to this list when a form turns up unhandled.
|
|
func TestClosedSetsAreComplete(t *testing.T) {
|
|
inter := map[string]bool{}
|
|
for _, w := range Interrogatives() {
|
|
inter[w] = true
|
|
}
|
|
// Instrumental and prepositional cases of что and кто, and the declined
|
|
// какой. The old list had что/чего and nothing else, so "чем ты занята" and
|
|
// "в каком часу" carried no interrogative at all.
|
|
for _, w := range []string{"чем", "чём", "чему", "кем", "ком", "каком", "какими", "насколько"} {
|
|
if !inter[w] {
|
|
t.Errorf("interrogatives is missing %q", w)
|
|
}
|
|
}
|
|
locative := map[string]bool{}
|
|
for _, w := range LocativeInterrogatives() {
|
|
locative[w] = true
|
|
if !inter[w] {
|
|
t.Errorf("locative interrogative %q is not in the full interrogative set", w)
|
|
}
|
|
}
|
|
for _, w := range []string{"где", "куда", "откуда", "докуда", "where"} {
|
|
if !locative[w] {
|
|
t.Errorf("locative_interrogatives is missing %q", w)
|
|
}
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
word string
|
|
want int
|
|
}{
|
|
{"ноль", 0}, {"одна", 1}, {"две", 2}, {"одиннадцать", 11},
|
|
{"пятнадцать", 15}, {"двадцать", 20}, {"сорок", 40}, {"девяносто", 90},
|
|
{"сто", 100}, {"twelve", 12},
|
|
} {
|
|
got, ok := Cardinal(tc.word)
|
|
if !ok || got != tc.want {
|
|
t.Errorf("Cardinal(%q) = %d, %v; want %d, true", tc.word, got, ok, tc.want)
|
|
}
|
|
}
|
|
if _, ok := Cardinal("бэкап"); ok {
|
|
t.Error("Cardinal must not answer for a word that is not a number")
|
|
}
|
|
|
|
// A spoken hour declines, and one to four decline further than the rest:
|
|
// "к двум часам" and "к трём" are hours, and only the dative says so (V-581).
|
|
for _, tc := range []struct {
|
|
word string
|
|
want int
|
|
}{
|
|
{"одному", 1}, {"двум", 2}, {"двумя", 2}, {"трём", 3}, {"трем", 3},
|
|
{"четырём", 4}, {"четырем", 4}, {"пяти", 5}, {"семи", 7},
|
|
} {
|
|
if got, ok := Cardinal(tc.word); !ok || got != tc.want {
|
|
t.Errorf("Cardinal(%q) = %d, %v; want %d, true", tc.word, got, ok, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestWeekdaysAreOneList — the second copy of a closed class is the bug (V-581).
|
|
// Weekdays lived in four files outside this one, so the list is handed out whole
|
|
// and the English forms, which the Russian dictionary cannot lemmatise, are here.
|
|
func TestWeekdaysAreOneList(t *testing.T) {
|
|
days := Weekdays()
|
|
if len(days) != 7 || days[0] != "воскресенье" || days[1] != "понедельник" {
|
|
t.Fatalf("Weekdays() = %v; want the seven, Sunday first", days)
|
|
}
|
|
for i, name := range days {
|
|
if Weekday(i) != name {
|
|
t.Errorf("Weekdays()[%d] = %q, but Weekday(%d) = %q", i, name, i, Weekday(i))
|
|
}
|
|
}
|
|
for _, tc := range []struct {
|
|
word string
|
|
want int
|
|
}{{"sunday", 0}, {"monday", 1}, {"mondays", 1}, {"Friday", 5}, {"saturdays", 6}} {
|
|
if got, ok := WeekdayEnglish(tc.word); !ok || got != tc.want {
|
|
t.Errorf("WeekdayEnglish(%q) = %d, %v; want %d, true", tc.word, got, ok, tc.want)
|
|
}
|
|
}
|
|
if _, ok := WeekdayEnglish("понедельник"); ok {
|
|
t.Error("WeekdayEnglish answered for a Russian word; that side is morph's")
|
|
}
|
|
}
|
|
|
|
// TestDayOffsetHasNoOrderingTrap — the defect a lookup removes. The callers this
|
|
// replaced used strings.Contains in a switch, so "послезавтра" had to be tested
|
|
// before "завтра" by hand or the day after tomorrow read as tomorrow.
|
|
func TestDayOffsetHasNoOrderingTrap(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
text string
|
|
want int
|
|
ok bool
|
|
}{
|
|
{"послезавтра", 2, true},
|
|
{"завтра", 1, true},
|
|
{"сегодня", 0, true},
|
|
{"вчера", -1, true},
|
|
{"позавчера", -2, true},
|
|
{"встреча послезавтра в 14:30", 2, true},
|
|
{"Tomorrow at 09:00", 1, true},
|
|
{"не сегодня, а послезавтра", 2, true},
|
|
{"в четверг", 0, false},
|
|
{"завтраком", 0, false},
|
|
} {
|
|
got, ok := DayOffsetIn(tc.text)
|
|
if ok != tc.ok || (ok && got != tc.want) {
|
|
t.Errorf("DayOffsetIn(%q) = %d, %v; want %d, %v", tc.text, got, ok, tc.want, tc.ok)
|
|
}
|
|
}
|
|
// "сегодня" is offset 0, which is also the zero value, so the second return
|
|
// is the only thing that separates a hit from a miss.
|
|
if n, ok := DayOffset("сегодня"); n != 0 || !ok {
|
|
t.Errorf("DayOffset(сегодня) = %d, %v; want 0, true", n, ok)
|
|
}
|
|
}
|
|
|
|
// TestIndexedSetsLineUpWithTheirCallers — weekdays start at Sunday because Go's
|
|
// time.Weekday does, and months are 1-indexed so a month number needs no
|
|
// arithmetic. Off-by-one here is a wrong date spoken out loud.
|
|
func TestIndexedSetsLineUpWithTheirCallers(t *testing.T) {
|
|
if got := Weekday(0); got != "воскресенье" {
|
|
t.Errorf("Weekday(0) = %q, want воскресенье", got)
|
|
}
|
|
if got := Weekday(1); got != "понедельник" {
|
|
t.Errorf("Weekday(1) = %q, want понедельник", got)
|
|
}
|
|
if got := MonthGenitive(1); got != "января" {
|
|
t.Errorf("MonthGenitive(1) = %q, want января", got)
|
|
}
|
|
if got := MonthGenitive(12); got != "декабря" {
|
|
t.Errorf("MonthGenitive(12) = %q, want декабря", got)
|
|
}
|
|
if got := MonthGenitive(0); got != "" {
|
|
t.Errorf("MonthGenitive(0) = %q, want the empty slot", got)
|
|
}
|
|
if got := HourSpoken(23); got != "двадцать три" {
|
|
t.Errorf("HourSpoken(23) = %q, want двадцать три", got)
|
|
}
|
|
for _, i := range []int{-1, 7, 13, 24} {
|
|
if got := Weekday(i); i >= 7 && got != "" {
|
|
t.Errorf("Weekday(%d) = %q, want empty", i, got)
|
|
}
|
|
}
|
|
if got := HourSpoken(24); got != "" {
|
|
t.Errorf("HourSpoken(24) = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
// TestCallerCannotEditTheLexicon — the sets are handed out as copies. A caller
|
|
// that sorted the slice it was given would otherwise reorder weekdays for
|
|
// everybody.
|
|
func TestCallerCannotEditTheLexicon(t *testing.T) {
|
|
first := Interrogatives()
|
|
first[0] = "мутировало"
|
|
if again := Interrogatives(); again[0] == "мутировало" {
|
|
t.Fatal("the lexicon handed out its own backing array")
|
|
}
|
|
}
|
|
|
|
// The positions carry gender and oblique forms, because "второй пункт" and
|
|
// "закрепи вторым" name one position (Vikunja #516). "last" is a position and not
|
|
// a count, so it is -1 rather than a large number.
|
|
func TestOrdinalsSpanGenderAndCase(t *testing.T) {
|
|
for _, w := range []string{"второй", "вторая", "второе", "вторым", "второго", "second"} {
|
|
n, ok := Ordinal(w)
|
|
if !ok || n != 2 {
|
|
t.Errorf("Ordinal(%q) = %d, %v; want 2, true", w, n, ok)
|
|
}
|
|
}
|
|
for _, w := range []string{"последний", "последнюю", "last"} {
|
|
if n, ok := Ordinal(w); !ok || n != -1 {
|
|
t.Errorf("Ordinal(%q) = %d, %v; want -1, true", w, n, ok)
|
|
}
|
|
}
|
|
// A weekday shares a stem with a position and is not one.
|
|
if n, ok := Ordinal("вторник"); ok {
|
|
t.Errorf("Ordinal(\"вторник\") = %d; a weekday is not a position", n)
|
|
}
|
|
}
|
|
|
|
// Earliest wins, not map order: the same sentence must answer the same way twice.
|
|
func TestOrdinalInTakesTheFirstPosition(t *testing.T) {
|
|
for i := 0; i < 50; i++ {
|
|
n, ok := OrdinalIn("отметь первый и второй пункт")
|
|
if !ok || n != 1 {
|
|
t.Fatalf("run %d: OrdinalIn = %d, %v; want 1, true", i, n, ok)
|
|
}
|
|
}
|
|
if _, ok := OrdinalIn("отметь пункт"); ok {
|
|
t.Error("a sentence with no position reported one")
|
|
}
|
|
}
|
|
|
|
// Ordinals is the escape hatch for the cases the file does not list, so it must
|
|
// hand out every entry and hand out the same order twice.
|
|
func TestOrdinalsListIsCompleteAndStable(t *testing.T) {
|
|
a, b := Ordinals(), Ordinals()
|
|
if len(a) != len(ru.Sets["ordinals"].Values) {
|
|
t.Errorf("Ordinals returned %d of %d entries", len(a), len(ru.Sets["ordinals"].Values))
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
t.Fatalf("Ordinals order is not stable at %d: %v vs %v", i, a[i], b[i])
|
|
}
|
|
}
|
|
}
|