4766167c3a
"отметь второй пункт" and "закрепи вторым" name one position, so the ordinals belong in the data file beside the cardinals, with the gender and oblique forms Russian requires. Values are the 1-based position, and -1 is the last one, which is a position rather than a count. Ordinal and OrdinalIn are the Cardinal pair again, and for the same reason: a caller matching stems would also match "вторник". Ordinals() hands out the whole set sorted, for a caller that needs a case the file does not list and can ask the dictionary whether one of these is the same word. The genitive forms are also what a half-past hour needs (V-538), so this set is written for two callers.
163 lines
6.1 KiB
Go
163 lines
6.1 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)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
}
|
|
}
|