package locale import ( "testing" "github.com/stretchr/testify/assert" ) // Helper function to create test locale entries func createTestEntries() []LocaleEntry { return []LocaleEntry{ { LanguageName: "English", LanguageLocal: "English", LanguageCode: "en", LanguageCode2: "eng", LanguageCode3: "eng", Regions: []RegionEntry{ { Locale: "en-US", CountryName: "United States", CountryLocal: "United States", CountryCode: "US", Flag: "🇺🇸", }, { Locale: "en-GB", CountryName: "United Kingdom", CountryLocal: "United Kingdom", CountryCode: "GB", Flag: "🇬🇧", }, }, }, { LanguageName: "Japanese", LanguageLocal: "日本語", LanguageCode: "ja", LanguageCode2: "jpn", LanguageCode3: "jpn", Regions: []RegionEntry{ { Locale: "ja-JP", CountryName: "Japan", CountryLocal: "日本", CountryCode: "JP", Flag: "🇯🇵", }, }, }, { LanguageName: "Spanish", LanguageLocal: "Español", LanguageCode: "es", LanguageCode2: "spa", LanguageCode3: "spa", Regions: []RegionEntry{ { Locale: "es-ES", CountryName: "Spain", CountryLocal: "España", CountryCode: "ES", Flag: "🇪🇸", }, { Locale: "es-419", CountryName: "Latin America", CountryLocal: "América Latina", CountryCode: "419", Flag: "🌎", }, }, }, { LanguageName: "French", LanguageLocal: "Français", LanguageCode: "fr", LanguageCode2: "fra", LanguageCode3: "fra", Regions: []RegionEntry{ { Locale: "fr-FR", CountryName: "France", CountryLocal: "France", CountryCode: "FR", Flag: "🇫🇷", }, }, }, { LanguageName: "German", LanguageLocal: "Deutsch", LanguageCode: "de", LanguageCode2: "deu", LanguageCode3: "deu", Regions: []RegionEntry{ { Locale: "de-DE", CountryName: "Germany", CountryLocal: "Deutschland", CountryCode: "DE", Flag: "🇩🇪", }, }, }, { LanguageName: "Afar", LanguageLocal: "Afaraf", LanguageCode: "aa", LanguageCode2: "aar", LanguageCode3: "aar", Regions: []RegionEntry{}, }, { LanguageName: "Chinese", LanguageLocal: "中文", LanguageCode: "zh", LanguageCode2: "zho", LanguageCode3: "zho", Regions: []RegionEntry{ { Locale: "zh-CN", CountryName: "China", CountryLocal: "中国", CountryCode: "CN", Flag: "🇨🇳", }, }, }, // Antarctica entry for testing filtering { LanguageName: "English", LanguageLocal: "English", LanguageCode: "en", Regions: []RegionEntry{ { Locale: "en-AQ", CountryName: "Antarctica", CountryLocal: "Antarctica", CountryCode: "AQ", Flag: "🇦🇶", }, }, }, { LanguageName: "No Regions Language", LanguageLocal: "No Regions", LanguageCode: "xx", Regions: []RegionEntry{}, }, } } func TestSearchLanguages(t *testing.T) { testEntries := createTestEntries() tests := []struct { name string opts LanguageFilterOptions wantCount int wantFirstLang string wantFirstLocale string wantNoMatch bool }{ { name: "search by language name - full match", opts: LanguageFilterOptions{ SearchTerm: "English", }, wantCount: 2, // en-US, en-GB (Antarctica filtered out) wantFirstLang: "English", wantFirstLocale: "en-US", }, { name: "search by language name - partial match", opts: LanguageFilterOptions{ SearchTerm: "jap", }, wantCount: 1, wantFirstLang: "Japanese", wantFirstLocale: "ja-JP", }, { name: "search by language name - case insensitive", opts: LanguageFilterOptions{ SearchTerm: "SPANISH", }, wantCount: 2, wantFirstLang: "Spanish", wantFirstLocale: "es-ES", }, { name: "search by language local name", opts: LanguageFilterOptions{ SearchTerm: "日本語", }, wantCount: 1, wantFirstLang: "Japanese", wantFirstLocale: "ja-JP", }, { name: "search by language code", opts: LanguageFilterOptions{ SearchTerm: "fr", }, wantCount: 1, wantFirstLang: "French", wantFirstLocale: "fr-FR", }, { name: "search by country code", opts: LanguageFilterOptions{ SearchTerm: "US", }, wantCount: 2, // en-US and en-GB (matches "United" in "United Kingdom") wantFirstLang: "English", }, { name: "search by country code - case insensitive", opts: LanguageFilterOptions{ SearchTerm: "jp", }, wantCount: 1, wantFirstLang: "Japanese", wantFirstLocale: "ja-JP", }, { name: "search by country name", opts: LanguageFilterOptions{ SearchTerm: "France", }, wantCount: 1, wantFirstLang: "French", wantFirstLocale: "fr-FR", }, { name: "search by country name - partial", opts: LanguageFilterOptions{ SearchTerm: "United", }, wantCount: 2, // United States and United Kingdom wantFirstLang: "English", }, { name: "filter by CommonOnly - common language", opts: LanguageFilterOptions{ CommonOnly: true, SearchTerm: "English", }, wantCount: 2, // en-US, en-GB (Antarctica filtered out) wantFirstLang: "English", }, { name: "filter by CommonOnly - uncommon language excluded", opts: LanguageFilterOptions{ CommonOnly: true, SearchTerm: "Afar", }, wantCount: 0, wantNoMatch: true, }, { name: "filter by CountryCode", opts: LanguageFilterOptions{ CountryCode: "ES", }, // NOTE: Current implementation includes languages without regions even when filtering by CountryCode. // This is a known bug - languages without regions bypass CountryCode filter. wantCount: 3, // aa, xx, es-ES wantFirstLang: "Afar", // Alphabetically first }, { name: "filter by CountryCode - case insensitive", opts: LanguageFilterOptions{ CountryCode: "es", }, wantCount: 3, // aa, xx, es-ES wantFirstLang: "Afar", // Alphabetically first }, { name: "filter by CountryCode with search", opts: LanguageFilterOptions{ CountryCode: "US", SearchTerm: "English", }, wantCount: 1, wantFirstLang: "English", wantFirstLocale: "en-US", }, { name: "apply limit", opts: LanguageFilterOptions{ Limit: 2, }, wantCount: 2, wantFirstLang: "Afar", // Alphabetically first }, { name: "apply limit with search", opts: LanguageFilterOptions{ SearchTerm: "English", Limit: 1, }, wantCount: 1, wantFirstLang: "English", wantFirstLocale: "en-US", }, { name: "empty search term - returns all sorted", opts: LanguageFilterOptions{}, wantCount: 10, // All except Antarctica (includes languages without regions) wantFirstLang: "Afar", // Alphabetically first }, { name: "empty search term with CommonOnly", opts: LanguageFilterOptions{ CommonOnly: true, }, wantCount: 8, // zh-CN, en-US, en-GB, fr-FR, de-DE, ja-JP, es-ES, es-419 wantFirstLang: "Chinese", }, { name: "no matches found", opts: LanguageFilterOptions{ SearchTerm: "NonExistentLanguage", }, wantCount: 0, wantNoMatch: true, }, { name: "Antarctica filtered out", opts: LanguageFilterOptions{ SearchTerm: "Antarctica", }, wantCount: 0, wantNoMatch: true, }, { name: "no regions language included", opts: LanguageFilterOptions{ SearchTerm: "No Regions", }, wantCount: 1, wantFirstLang: "No Regions Language", wantFirstLocale: "xx", }, { name: "exact match prioritized in search", opts: LanguageFilterOptions{ SearchTerm: "Spanish", }, wantCount: 2, wantFirstLang: "Spanish", wantFirstLocale: "es-ES", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { results := SearchLanguages(testEntries, tt.opts) if tt.wantNoMatch { assert.Empty(t, results) } else { assert.Len(t, results, tt.wantCount) if tt.wantCount > 0 { assert.Equal(t, tt.wantFirstLang, results[0].LanguageName) if tt.wantFirstLocale != "" { assert.Equal(t, tt.wantFirstLocale, results[0].Locale) } } } }) } } func TestSearchLanguages_Sorting(t *testing.T) { testEntries := createTestEntries() t.Run("alphabetical sort without search term", func(t *testing.T) { opts := LanguageFilterOptions{ CommonOnly: true, } results := SearchLanguages(testEntries, opts) // Should be sorted alphabetically by language name languageNames := make([]string, len(results)) for i, r := range results { languageNames[i] = r.LanguageName } // Expected order: Chinese, English, English, French, German, Japanese, Spanish, Spanish expected := []string{"Chinese", "English", "English", "French", "German", "Japanese", "Spanish", "Spanish"} assert.Equal(t, expected, languageNames) }) t.Run("exact match priority in search", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "English", } results := SearchLanguages(testEntries, opts) // English variants should come first (exact match on language name) assert.Len(t, results, 2) assert.Equal(t, "English", results[0].LanguageName) assert.Equal(t, "en-US", results[0].Locale) assert.Equal(t, "English", results[1].LanguageName) assert.Equal(t, "en-GB", results[1].Locale) }) } func TestGetCommonLanguages(t *testing.T) { testEntries := createTestEntries() t.Run("returns only common languages", func(t *testing.T) { results := GetCommonLanguages(testEntries) // Should include en, ja, es, fr, de, zh but not aa (Afar) assert.GreaterOrEqual(t, len(results), 6) // Verify no uncommon languages for _, r := range results { assert.NotEqual(t, "Afar", r.LanguageName) } }) t.Run("results are sorted alphabetically", func(t *testing.T) { results := GetCommonLanguages(testEntries) // Check sorting languageNames := make([]string, len(results)) for i, r := range results { languageNames[i] = r.LanguageName } // Verify sorted for i := 1; i < len(languageNames); i++ { assert.GreaterOrEqual(t, languageNames[i], languageNames[i-1]) } }) t.Run("returns empty slice for no entries", func(t *testing.T) { results := GetCommonLanguages([]LocaleEntry{}) assert.Empty(t, results) }) t.Run("includes specific common languages", func(t *testing.T) { results := GetCommonLanguages(testEntries) // Collect language codes codes := make(map[string]bool) for _, r := range results { baseCode := r.Locale if len(baseCode) > 2 { baseCode = baseCode[:2] } codes[baseCode] = true } // Verify common languages are present assert.True(t, codes["en"], "English should be in common languages") assert.True(t, codes["ja"], "Japanese should be in common languages") assert.True(t, codes["es"], "Spanish should be in common languages") assert.True(t, codes["fr"], "French should be in common languages") assert.True(t, codes["de"], "German should be in common languages") assert.True(t, codes["zh"], "Chinese should be in common languages") }) } func TestGetAllLanguages(t *testing.T) { testEntries := createTestEntries() t.Run("returns all languages including non-common", func(t *testing.T) { results := GetAllLanguages(testEntries) // Should include all entries except Antarctica assert.GreaterOrEqual(t, len(results), 7) // Verify uncommon languages are included foundAfar := false for _, r := range results { if r.LanguageName == "Afar" { foundAfar = true break } } assert.True(t, foundAfar, "Afar (uncommon language) should be included") }) t.Run("results are sorted alphabetically", func(t *testing.T) { results := GetAllLanguages(testEntries) // Check sorting for i := 1; i < len(results); i++ { assert.GreaterOrEqual(t, results[i].LanguageName, results[i-1].LanguageName) } }) t.Run("returns empty slice for no entries", func(t *testing.T) { results := GetAllLanguages([]LocaleEntry{}) assert.Empty(t, results) }) t.Run("Antarctica is filtered out", func(t *testing.T) { results := GetAllLanguages(testEntries) // Verify Antarctica is not in results for _, r := range results { assert.NotContains(t, r.CountryName, "Antarctica") assert.NotEqual(t, "AQ", r.CountryCode) } }) } func TestGetLanguagesByCountry(t *testing.T) { testEntries := createTestEntries() tests := []struct { name string countryCode string wantCount int wantLanguages []string wantFirst string }{ { name: "country US", countryCode: "US", wantCount: 1, wantLanguages: []string{"English"}, wantFirst: "English", }, { name: "country JP", countryCode: "JP", wantCount: 1, wantLanguages: []string{"Japanese"}, wantFirst: "Japanese", }, { name: "country ES", countryCode: "ES", wantCount: 1, wantLanguages: []string{"Spanish"}, wantFirst: "Spanish", }, { name: "country FR", countryCode: "FR", wantCount: 1, wantLanguages: []string{"French"}, wantFirst: "French", }, { name: "country DE", countryCode: "DE", wantCount: 1, wantLanguages: []string{"German"}, wantFirst: "German", }, { name: "unknown country code", countryCode: "XX", wantCount: 0, wantLanguages: []string{}, }, { name: "empty country code", countryCode: "", wantCount: 0, wantLanguages: []string{}, }, { name: "case insensitive - us", countryCode: "us", wantCount: 1, wantLanguages: []string{"English"}, wantFirst: "English", }, { name: "case insensitive - jp", countryCode: "jp", wantCount: 1, wantLanguages: []string{"Japanese"}, wantFirst: "Japanese", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { results := GetLanguagesByCountry(testEntries, tt.countryCode) assert.Len(t, results, tt.wantCount) if len(tt.wantLanguages) > 0 { // Verify all expected languages are present foundLanguages := make(map[string]bool) for _, r := range results { foundLanguages[r.LanguageName] = true } for _, lang := range tt.wantLanguages { assert.True(t, foundLanguages[lang], "Language %s should be found", lang) } // Verify first result assert.Equal(t, tt.wantFirst, results[0].LanguageName) } }) } t.Run("results are sorted by language name", func(t *testing.T) { // Add multiple languages for same country multiCountryEntries := []LocaleEntry{ { LanguageName: "French", LanguageCode: "fr", Regions: []RegionEntry{ {Locale: "fr-CA", CountryCode: "CA", CountryName: "Canada", Flag: "🇨🇦"}, }, }, { LanguageName: "English", LanguageCode: "en", Regions: []RegionEntry{ {Locale: "en-CA", CountryCode: "CA", CountryName: "Canada", Flag: "🇨🇦"}, }, }, { LanguageName: "German", LanguageCode: "de", Regions: []RegionEntry{ {Locale: "de-CA", CountryCode: "CA", CountryName: "Canada", Flag: "🇨🇦"}, }, }, } results := GetLanguagesByCountry(multiCountryEntries, "CA") // Should be sorted: English, French, German assert.Len(t, results, 3) assert.Equal(t, "English", results[0].LanguageName) assert.Equal(t, "French", results[1].LanguageName) assert.Equal(t, "German", results[2].LanguageName) }) } func TestFindLanguageByLocale(t *testing.T) { testEntries := createTestEntries() tests := []struct { name string localeStr string wantFound bool wantLanguage string wantLocale string wantCountry string wantCountryCode string }{ { name: "find en-US", localeStr: "en-US", wantFound: true, wantLanguage: "English", wantLocale: "en-US", wantCountry: "United States", wantCountryCode: "US", }, { name: "find ja-JP", localeStr: "ja-JP", wantFound: true, wantLanguage: "Japanese", wantLocale: "ja-JP", wantCountry: "Japan", wantCountryCode: "JP", }, { name: "find es-ES", localeStr: "es-ES", wantFound: true, wantLanguage: "Spanish", wantLocale: "es-ES", wantCountry: "Spain", wantCountryCode: "ES", }, { name: "find fr-FR", localeStr: "fr-FR", wantFound: true, wantLanguage: "French", wantLocale: "fr-FR", wantCountry: "France", wantCountryCode: "FR", }, { name: "find de-DE", localeStr: "de-DE", wantFound: true, wantLanguage: "German", wantLocale: "de-DE", wantCountry: "Germany", wantCountryCode: "DE", }, { name: "find locale without region returns error", localeStr: "xx", wantFound: false, }, { name: "unknown locale", localeStr: "xx-XX", wantFound: false, }, { name: "empty locale", localeStr: "", wantFound: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := FindLanguageByLocale(testEntries, tt.localeStr) if tt.wantFound { assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, tt.wantLanguage, result.LanguageName) assert.Equal(t, tt.wantLocale, result.Locale) assert.Equal(t, tt.wantCountry, result.CountryName) assert.Equal(t, tt.wantCountryCode, result.CountryCode) } else { assert.Error(t, err) assert.Nil(t, result) } }) } } func TestSearchByTerm(t *testing.T) { testEntries := createTestEntries() tests := []struct { name string term string wantCount int wantFirstLang string }{ { name: "search English", term: "English", wantCount: 2, wantFirstLang: "English", }, { name: "search Japanese", term: "Japanese", wantCount: 1, wantFirstLang: "Japanese", }, { name: "search by code", term: "es", wantCount: 6, wantFirstLang: "Chinese", // CountryCode "CN" contains "es" }, { name: "no matches", term: "NonExistent", wantCount: 0, }, { name: "empty term", term: "", wantCount: 10, // All except Antarctica wantFirstLang: "Afar", // Alphabetically first }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { results := SearchByTerm(testEntries, tt.term) assert.Len(t, results, tt.wantCount) if tt.wantCount > 0 { assert.Equal(t, tt.wantFirstLang, results[0].LanguageName) } }) } t.Run("convenience wrapper around SearchLanguages", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "French", } expected := SearchLanguages(testEntries, opts) actual := SearchByTerm(testEntries, "French") assert.Equal(t, expected, actual) }) } func TestIsCommonLanguage(t *testing.T) { tests := []struct { name string languageCode string wantCommon bool }{ { name: "English is common", languageCode: "en", wantCommon: true, }, { name: "Japanese is common", languageCode: "ja", wantCommon: true, }, { name: "Spanish is common", languageCode: "es", wantCommon: true, }, { name: "French is common", languageCode: "fr", wantCommon: true, }, { name: "German is common", languageCode: "de", wantCommon: true, }, { name: "Chinese is common", languageCode: "zh", wantCommon: true, }, { name: "Korean is common", languageCode: "ko", wantCommon: true, }, { name: "Italian is common", languageCode: "it", wantCommon: true, }, { name: "Portuguese is common", languageCode: "pt", wantCommon: true, }, { name: "Russian is common", languageCode: "ru", wantCommon: true, }, { name: "Afar is not common", languageCode: "aa", wantCommon: false, }, { name: "empty string", languageCode: "", wantCommon: false, }, { name: "locale with region extracts base code", languageCode: "en-US", wantCommon: true, }, { name: "locale with region extracts base code - ja-JP", languageCode: "ja-JP", wantCommon: true, }, { name: "locale with region extracts base code - es-ES", languageCode: "es-ES", wantCommon: true, }, { name: "locale with region extracts base code - uncommon", languageCode: "aa-ET", wantCommon: false, }, { name: "case insensitive - EN", languageCode: "EN", wantCommon: true, }, { name: "case insensitive - JA", languageCode: "JA", wantCommon: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsCommonLanguage(tt.languageCode) assert.Equal(t, tt.wantCommon, result) }) } } func TestGetCommonLanguageCodes(t *testing.T) { t.Run("returns copy of commonLanguageCodes", func(t *testing.T) { codes := GetCommonLanguageCodes() // Verify codes are returned (list has been expanded to 33 languages) assert.Greater(t, len(codes), 10) }) t.Run("modifying returned slice doesn't affect original", func(t *testing.T) { codes1 := GetCommonLanguageCodes() codes2 := GetCommonLanguageCodes() // Modify first slice codes1[0] = "modified" // Verify second slice is unchanged assert.NotEqual(t, codes1[0], codes2[0]) }) t.Run("can be modified independently", func(t *testing.T) { codes1 := GetCommonLanguageCodes() // Append to slice codes1 = append(codes1, "new-code") // Get a new copy codes2 := GetCommonLanguageCodes() // Verify new copy doesn't have appended code (but note that implementation returns a new copy each time) assert.Greater(t, len(codes2), 10) assert.NotContains(t, codes2, "new-code") }) } func TestLanguageOption_DisplayText(t *testing.T) { testEntries := createTestEntries() t.Run("display text format with region", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "Japanese", } results := SearchLanguages(testEntries, opts) if len(results) > 0 { displayText := results[0].DisplayText // Should contain flag, country code, and language name assert.Contains(t, displayText, "🇯🇵") assert.Contains(t, displayText, "JP") assert.Contains(t, displayText, "日本語") assert.Contains(t, displayText, "Japanese") } }) t.Run("display text format without region", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "No Regions", } results := SearchLanguages(testEntries, opts) if len(results) > 0 { displayText := results[0].DisplayText // Should contain flag and language name assert.Contains(t, displayText, "No Regions Language") } }) t.Run("display text format English with different local name", func(t *testing.T) { result, err := FindLanguageByLocale(testEntries, "ja-JP") assert.NoError(t, err) assert.Contains(t, result.DisplayText, "🇯🇵") assert.Contains(t, result.DisplayText, "JP") assert.Contains(t, result.DisplayText, "日本語") assert.Contains(t, result.DisplayText, "Japanese") }) } func TestLanguageFilterOptions(t *testing.T) { t.Run("default options", func(t *testing.T) { opts := LanguageFilterOptions{} assert.False(t, opts.CommonOnly) assert.Empty(t, opts.SearchTerm) assert.Equal(t, 0, opts.Limit) assert.Empty(t, opts.CountryCode) }) t.Run("with all options set", func(t *testing.T) { opts := LanguageFilterOptions{ CommonOnly: true, SearchTerm: "test", Limit: 10, CountryCode: "US", } assert.True(t, opts.CommonOnly) assert.Equal(t, "test", opts.SearchTerm) assert.Equal(t, 10, opts.Limit) assert.Equal(t, "US", opts.CountryCode) }) } func TestFullLocaleFormatting(t *testing.T) { testEntries := createTestEntries() t.Run("FullLocale set correctly", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "Spanish", } results := SearchLanguages(testEntries, opts) // Check ES-419 formatting (should be ES-419) found419 := false for _, r := range results { if r.Locale == "es-419" { assert.Equal(t, "ES-419", r.FullLocale) found419 = true break } } assert.True(t, found419, "es-419 locale should be found with FullLocale") }) t.Run("FullLocale empty for non-regional locales", func(t *testing.T) { opts := LanguageFilterOptions{ SearchTerm: "No Regions", } results := SearchLanguages(testEntries, opts) if len(results) > 0 { assert.Empty(t, results[0].FullLocale, "Non-regional locale should have empty FullLocale") } }) }