MM-59761 Add tests for i18n functionality (#8510)

* test: Add tests for i18n functionality

* Remove test causing i18n ci check failure
This commit is contained in:
Joram Wilander 2025-01-31 09:30:42 -05:00 committed by GitHub
parent 444364b63c
commit 6f8d6632b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 205 additions and 1 deletions

204
app/i18n/index.test.ts Normal file
View file

@ -0,0 +1,204 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import moment from 'moment';
import {getLocales} from 'react-native-localize';
import en from '@assets/i18n/en.json';
import * as logUtils from '@utils/log';
import {
getLocaleFromLanguage,
resetMomentLocale,
getTranslations,
getLocalizedMessage,
DEFAULT_LOCALE,
} from './index';
jest.mock('react-native-localize', () => ({
getLocales: jest.fn().mockReturnValue([{languageTag: 'en'}]),
}));
jest.mock('@utils/log', () => ({
logError: jest.fn(),
}));
jest.mock('@assets/i18n/en.json', () => ({test: 'Test', hello: 'Hello'}));
jest.mock('@assets/i18n/bg.json', () => ({test: 'Тест', hello: 'Здравейте'}));
jest.mock('@assets/i18n/de.json', () => ({test: 'Test', hello: 'Hallo'}));
jest.mock('@assets/i18n/en_AU.json', () => ({test: 'Test', hello: 'G\'day'}));
jest.mock('@assets/i18n/es.json', () => ({test: 'Prueba', hello: 'Hola'}));
jest.mock('@assets/i18n/fa.json', () => ({test: 'تست', hello: 'سلام'}));
jest.mock('@assets/i18n/fr.json', () => ({test: 'Test', hello: 'Bonjour'}));
jest.mock('@assets/i18n/hu.json', () => ({test: 'Teszt', hello: 'Helló'}));
jest.mock('@assets/i18n/it.json', () => ({test: 'Test', hello: 'Ciao'}));
jest.mock('@assets/i18n/ja.json', () => ({test: 'テスト', hello: 'こんにちは'}));
jest.mock('@assets/i18n/ko.json', () => ({test: '테스트', hello: '안녕하세요'}));
jest.mock('@assets/i18n/nl.json', () => ({test: 'Test', hello: 'Hallo'}));
jest.mock('@assets/i18n/pl.json', () => ({test: 'Test', hello: 'Cześć'}));
jest.mock('@assets/i18n/pt-BR.json', () => ({test: 'Teste', hello: 'Olá'}));
jest.mock('@assets/i18n/ro.json', () => ({test: 'Test', hello: 'Bună'}));
jest.mock('@assets/i18n/ru.json', () => ({test: 'Тест', hello: 'Привет'}));
jest.mock('@assets/i18n/sv.json', () => ({test: 'Test', hello: 'Hej'}));
jest.mock('@assets/i18n/tr.json', () => ({test: 'Test', hello: 'Merhaba'}));
jest.mock('@assets/i18n/uk.json', () => ({test: 'Тест', hello: 'Привіт'}));
jest.mock('@assets/i18n/vi.json', () => ({test: 'Kiểm tra', hello: 'Xin chào'}));
jest.mock('@assets/i18n/zh-CN.json', () => ({test: '测试', hello: '你好'}));
jest.mock('@assets/i18n/zh-TW.json', () => ({test: '測試', hello: '你好'}));
describe('i18n', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.mocked(getLocales).mockReturnValue([{
languageTag: 'en',
languageCode: '',
countryCode: '',
isRTL: false,
}]);
});
describe('getLocaleFromLanguage', () => {
it('returns correct locale for full language code', () => {
expect(getLocaleFromLanguage('es-ES')).toBe('es');
expect(getLocaleFromLanguage('pt-BR')).toBe('pt-BR');
expect(getLocaleFromLanguage('zh-TW')).toBe('zh-TW');
expect(getLocaleFromLanguage('zh-CN')).toBe('zh-CN');
expect(getLocaleFromLanguage('en-AU')).toBe('en-AU');
});
it('returns correct locale for language code', () => {
expect(getLocaleFromLanguage('es')).toBe('es');
expect(getLocaleFromLanguage('en')).toBe('en');
expect(getLocaleFromLanguage('fr')).toBe('fr');
expect(getLocaleFromLanguage('de')).toBe('de');
expect(getLocaleFromLanguage('ja')).toBe('ja');
});
it('returns default locale for unsupported language', () => {
expect(getLocaleFromLanguage('xx')).toBe('en');
expect(getLocaleFromLanguage('')).toBe('en');
expect(getLocaleFromLanguage('not-valid')).toBe('en');
});
});
describe('resetMomentLocale', () => {
let spy: jest.SpyInstance;
beforeEach(() => {
spy = jest.spyOn(moment, 'locale');
});
afterEach(() => {
spy.mockRestore();
});
it('sets moment locale correctly for various languages', () => {
resetMomentLocale('es-ES');
expect(spy).toHaveBeenCalledWith('es');
resetMomentLocale('en');
expect(spy).toHaveBeenCalledWith('en');
resetMomentLocale('zh-TW');
expect(spy).toHaveBeenCalledWith('zh');
resetMomentLocale('pt-BR');
expect(spy).toHaveBeenCalledWith('pt');
});
it('uses default locale when no locale provided', () => {
resetMomentLocale();
expect(spy).toHaveBeenCalledWith(DEFAULT_LOCALE.split('-')[0]);
});
it('handles invalid locales gracefully', () => {
resetMomentLocale('invalid-locale');
expect(spy).toHaveBeenCalledWith('invalid');
resetMomentLocale('');
expect(spy).toHaveBeenCalledWith(DEFAULT_LOCALE.split('-')[0]);
});
});
describe('getTranslations', () => {
beforeEach(() => {
(logUtils.logError as jest.Mock).mockClear();
});
it('returns correct translations for all supported locales', () => {
const testCases = [
{locale: 'bg', hello: 'Здравейте', test: 'Тест'},
{locale: 'de', hello: 'Hallo', test: 'Test'},
{locale: 'en', hello: 'Hello', test: 'Test'},
{locale: 'en-AU', hello: 'G\'day', test: 'Test'},
{locale: 'es', hello: 'Hola', test: 'Prueba'},
{locale: 'fa', hello: 'سلام', test: 'تست'},
{locale: 'fr', hello: 'Bonjour', test: 'Test'},
{locale: 'hu', hello: 'Helló', test: 'Teszt'},
{locale: 'it', hello: 'Ciao', test: 'Test'},
{locale: 'ja', hello: 'こんにちは', test: 'テスト'},
{locale: 'ko', hello: '안녕하세요', test: '테스트'},
{locale: 'nl', hello: 'Hallo', test: 'Test'},
{locale: 'pl', hello: 'Cześć', test: 'Test'},
{locale: 'pt-BR', hello: 'Olá', test: 'Teste'},
{locale: 'ro', hello: 'Bună', test: 'Test'},
{locale: 'ru', hello: 'Привет', test: 'Тест'},
{locale: 'sv', hello: 'Hej', test: 'Test'},
{locale: 'tr', hello: 'Merhaba', test: 'Test'},
{locale: 'uk', hello: 'Привіт', test: 'Тест'},
{locale: 'vi', hello: 'Xin chào', test: 'Kiểm tra'},
{locale: 'zh-CN', hello: '你好', test: '测试'},
{locale: 'zh-TW', hello: '你好', test: '測試'},
];
testCases.forEach(({locale, hello, test: testWord}) => {
const translations = getTranslations(locale);
expect(translations.hello).toBe(hello);
expect(translations.test).toBe(testWord);
});
});
it('returns english translations for unsupported locale', () => {
const translations = getTranslations('xx');
expect(translations).toEqual(en);
expect(logUtils.logError).not.toHaveBeenCalled();
});
it('loads correct polyfills for Chinese locales', () => {
const zhCNTranslations = getTranslations('zh-CN');
expect(zhCNTranslations.hello).toBe('你好');
expect(zhCNTranslations.test).toBe('测试');
const zhTWTranslations = getTranslations('zh-TW');
expect(zhTWTranslations.hello).toBe('你好');
expect(zhTWTranslations.test).toBe('測試');
});
});
describe('getLocalizedMessage', () => {
it('returns correct message for existing key in various languages', () => {
expect(getLocalizedMessage('en', 'test')).toBe('Test');
expect(getLocalizedMessage('es', 'test')).toBe('Prueba');
expect(getLocalizedMessage('en', 'hello')).toBe('Hello');
expect(getLocalizedMessage('es', 'hello')).toBe('Hola');
});
it('returns default message when key not found', () => {
expect(getLocalizedMessage('en', 'nonexistent', 'Default')).toBe('Default');
expect(getLocalizedMessage('es', 'nonexistent', 'Por defecto')).toBe('Por defecto');
});
it('returns empty string when no translation or default', () => {
expect(getLocalizedMessage('en', 'nonexistent')).toBe('');
expect(getLocalizedMessage('es', 'nonexistent')).toBe('');
});
it('handles invalid inputs gracefully', () => {
expect(getLocalizedMessage('', 'test')).toBe('Test');
expect(getLocalizedMessage('invalid-locale', 'test')).toBe('Test');
expect(getLocalizedMessage('en', '')).toBe('');
expect(getLocalizedMessage('en', undefined as unknown as string)).toBe('');
expect(getLocalizedMessage('en', null as unknown as string)).toBe('');
});
});
});

View file

@ -10,8 +10,8 @@ import {logError} from '@utils/log';
import availableLanguages from './languages';
const deviceLocale = getLocales()[0].languageTag;
const PRIMARY_LOCALE = 'en';
const deviceLocale = getLocales()[0]?.languageTag || PRIMARY_LOCALE;
export const DEFAULT_LOCALE = getLocaleFromLanguage(deviceLocale);
function loadTranslation(locale?: string): {[x: string]: string} {