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) } } 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]) } } }