package lang import ( "math" "strings" "testing" ) func testDict() *Dictionary { d := NewDictionary() d.Add("改札", Entry{Kana: "かいさつ", Romaji: "kaisatsu", En: "Ticket gate", Sv: "Spärr", Tier: 1}) d.Add("番線", Entry{Kana: "ばんせん", Romaji: "bansen", En: "Track no.", Sv: "Spår", Tier: 1}) d.Add("方面", Entry{Kana: "ほうめん", Romaji: "hōmen", En: "for", Sv: "mot", Tier: 2}) d.Add("新宿", Entry{Kana: "しんじゅく", Romaji: "Shinjuku", En: "Shinjuku", Sv: "Shinjuku", Tier: 2}) d.Add("新宿駅", Entry{Kana: "しんじゅくえき", Romaji: "Shinjuku-eki", En: "Shinjuku Station", Sv: "Shinjuku station", Tier: 3}) d.Add("のりば", Entry{Kana: "のりば", Romaji: "noriba", En: "Platform", Sv: "Perrong", Tier: 1}) d.Add("トイレ", Entry{Kana: "トイレ", Romaji: "toire", En: "Toilet", Sv: "Toalett", Tier: 1}) d.Add("チャージ", Entry{Kana: "チャージ", Romaji: "chāji", En: "Top up", Sv: "Fyll på", Tier: 2}) return d } func TestTokenizeLongestMatchAndKinds(t *testing.T) { d := testDict() segs := d.Tokenize("改札 ← 2番線 新宿駅方面 京成") var got []string for _, s := range segs { got = append(got, map[Kind]string{Plain: "P", Word: "W", Unknown: "U"}[s.Kind]+":"+s.Text) } want := []string{"W:改札", "P: ← 2", "W:番線", "P: ", "W:新宿駅", "W:方面", "P: ", "U:京成"} if strings.Join(got, "|") != strings.Join(want, "|") { t.Fatalf("got %v\nwant %v", got, want) } if u := d.Unknown("改札 京成 のりば 上野"); strings.Join(u, ",") != "京成,上野" { t.Fatalf("Unknown = %v", u) } if w := d.WordsIn("新宿方面 新宿"); strings.Join(w, ",") != "新宿,方面,新宿" { t.Fatalf("WordsIn = %v", w) } } func TestRenderPartialTranslation(t *testing.T) { d := testDict() k := NewKnowledge() k.Learn("改札") k.Learn("方面") r := Render(d, k, "改札 → 2番線 新宿方面", "sv") if r.Complete { t.Fatal("ska inte vara komplett med okända ord") } if got := PlainText(r.Partial); got != "Spärr → 2番線 新宿 mot" { t.Fatalf("Partial = %q", got) } known := map[string]bool{} for _, sh := range r.Original { known[sh.Text] = sh.Known } if !known["改札"] || !known["方面"] || known["番線"] || known["新宿"] { t.Fatalf("markering fel: %+v", r.Original) } // Allt känt (nil) -> komplett. full := Render(d, nil, "改札 → 2番線 新宿方面", "sv") if !full.Complete || PlainText(full.Partial) != "Spärr → 2 Spår Shinjuku mot" { t.Fatalf("full = %q complete=%v", PlainText(full.Partial), full.Complete) } // Engelska som UI-språk. if got := PlainText(Render(d, nil, "改札", "en").Partial); got != "Ticket gate" { t.Fatalf("en = %q", got) } } func TestKanaReadingWithoutMeaning(t *testing.T) { d := testDict() k := NewKnowledge() r := Render(d, k, "のりば", "sv") if PlainText(r.Partial) != "のりば" { t.Fatalf("utan kana ska kana-ordet stå kvar: %q", PlainText(r.Partial)) } k.Kana = true r = Render(d, k, "のりば 改札", "sv") if PlainText(r.Partial) != "noriba 改札" || !r.Partial[0].Reading || r.Complete { t.Fatalf("med kana: %q reading=%v complete=%v", PlainText(r.Partial), r.Partial[0].Reading, r.Complete) } } func TestLearnUnlocksKanaAndLevels(t *testing.T) { k := NewKnowledge() if k.Level() != 0 || k.Kana { t.Fatal("startar på noll") } if !k.Learn("改札") || k.Learn("改札") { t.Fatal("Learn ska rapportera nytt bara första gången") } k.Learn("のりば") k.Learn("トイレ") if k.Kana { t.Fatal("två kana-ord räcker inte") } k.Learn("チャージ") if !k.Kana { t.Fatal("tre kana-ord ska låsa upp kana") } if k.Count() != 4 || k.Level() != 1 || !k.ShowsDestinationJP() || k.ShowsReadings() { t.Fatalf("count=%d level=%d", k.Count(), k.Level()) } for i := 0; i < 40; i++ { k.Learn(strings.Repeat("語", i+1)) } if k.Level() != MaxLevel || !k.ShowsReadings() { t.Fatalf("level=%d", k.Level()) } } func TestReadingsShownFromLevel2(t *testing.T) { d := testDict() k := NewKnowledge() for _, w := range []string{"改札", "番線", "方面", "新宿", "新宿駅", "のりば", "トイレ", "チャージ"} { k.Learn(w) } r := Render(d, k, "改札", "sv") if k.Level() < 2 || r.Original[0].Romaji != "kaisatsu" { t.Fatalf("level=%d romaji=%q", k.Level(), r.Original[0].Romaji) } } func TestFirstUnknownWord(t *testing.T) { d := testDict() k := NewKnowledge() k.Learn("改札") if w := FirstUnknownWord(d, k, "改札 2番線 新宿方面"); w != "番線" { t.Fatalf("got %q", w) } if w := FirstUnknownWord(d, k, "改札 改札"); w != "" { t.Fatalf("allt känt ska ge tom sträng, fick %q", w) } } func TestHelpSeconds(t *testing.T) { prev := math.Inf(1) for lvl := 0; lvl <= MaxLevel; lvl++ { s := HelpSeconds(lvl) if s > prev || s < HelpMinSeconds { t.Fatalf("nivå %d: %.1f s (förra %.1f)", lvl, s, prev) } prev = s } if HelpSeconds(0) != HelpBaseSeconds || HelpSeconds(100) != HelpMinSeconds { t.Fatal("gränserna stämmer inte") } } func TestEnglishShareAndDeterminism(t *testing.T) { if EnglishShare(1) != 1 || EnglishShare(0) != 1 || EnglishShare(5) != 0 || EnglishShare(9) != 0 { t.Fatal("ytterligheterna") } for d := 1; d < 5; d++ { if EnglishShare(d) <= EnglishShare(d+1) { t.Fatalf("andelen ska minska med svårigheten: %d", d) } } for i := 0; i < 50; i++ { if !ShowEnglish("x", i, 0, 1) || ShowEnglish("x", i, 0, 5) { t.Fatal("svårighet 1 = alltid engelska, 5 = aldrig") } } if ShowEnglish("lvl", 3, 4, 2) != ShowEnglish("lvl", 3, 4, 2) { t.Fatal("ska vara deterministiskt") } // Andelen ska ungefär stämma över många skyltar. n := 0 for x := 0; x < 100; x++ { for y := 0; y < 10; y++ { if ShowEnglish("lvl", x, y, 2) { n++ } } } if n < 500 || n > 700 { t.Fatalf("svårighet 2: %d/1000 visar engelska, vill ha ~600", n) } } func TestParseDictionaryValidation(t *testing.T) { good := `{"_comment": "x", "改札": {"kana": "かいさつ", "romaji": "kaisatsu", "en": "Gate", "sv": "Spärr", "tier": 1}}` d, err := ParseDictionary([]byte(good)) if err != nil || d.Len() != 1 || d.Lookup("改札") == nil || d.Lookup("改札").Word != "改札" { t.Fatalf("d=%v err=%v", d, err) } for name, src := range map[string]string{ "saknar sv": `{"改札": {"kana": "か", "romaji": "k", "en": "G", "tier": 1}}`, "tier 0": `{"改札": {"kana": "か", "romaji": "k", "en": "G", "sv": "S", "tier": 0}}`, "latinsk nyckel": `{"gate": {"kana": "か", "romaji": "k", "en": "G", "sv": "S", "tier": 1}}`, "trasig": `{`, } { if _, err := ParseDictionary([]byte(src)); err == nil { t.Errorf("%s: förväntade fel", name) } } } func TestCharClasses(t *testing.T) { if !IsKana('の') || !IsKana('ト') || !IsKana('ー') || IsKana('改') || IsKana('a') { t.Fatal("IsKana") } if !IsKanji('改') || IsKanji('の') || !IsAllKana("のりば") || IsAllKana("新宿") || IsAllKana("") { t.Fatal("IsKanji/IsAllKana") } if !HasKana("成田エクスプレス") || HasKana("新宿") || !IsJapanese("2番線") || IsJapanese("Track 2") { t.Fatal("HasKana/IsJapanese") } }