Files
Maven/internal/lexicon/lexicon_test.go
T
claude e6923490fd the lexicon owns the weekdays and the oblique small numbers (V-581)
Weekday names lived in four files outside internal/lexicon and each copy was
short in a different direction. The habit map had the prepositional plural of
Sunday and no dative of Wednesday. The plan refusal had the accusative of
Wednesday and not the prepositional. cmd/mavend matched the stem.

Weekdays hands out the seven nominatives whole, because every Russian case
lemmatises to one of them and the case is morph's question. WeekdayEnglish is
the half that has to be data: the vendored dictionary is Russian and leaves
mondays as it found it.

Cardinals gain the dative and instrumental of one to four. A spoken hour
declines and five upward has one oblique form for the genitive, dative and
prepositional, so pyati was already whole while dvum was missing and k dvum
chasam is an hour he says.
2026-08-06 03:32:48 +04:00

203 lines
7.6 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")
}
// 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])
}
}
}