@logistically/i18n-react-core
Version:
React integration for @logistically/i18n with state management agnostic design
574 lines (457 loc) • 19.3 kB
text/typescript
import { TranslationCore } from './TranslationCore';
import { ReactTranslationConfig } from '../types';
describe('TranslationCore', () => {
let core: TranslationCore;
let config: ReactTranslationConfig;
beforeEach(() => {
config = {
defaultLocale: 'en',
supportedLocales: ['en', 'fr', 'es'],
translationsPath: 'test-translations',
debug: false,
serviceName: 'test-service',
};
core = new TranslationCore(config);
});
afterEach(() => {
core.destroy();
});
describe('initialization', () => {
it('should initialize with correct config', () => {
expect(core.getState().locale).toBe('en');
expect(core.getState().isInitialized).toBe(false);
expect(core.getState().isLoading).toBe(false);
});
it('should initialize successfully', async () => {
await core.initialize();
expect(core.getState().isInitialized).toBe(true);
expect(core.getState().isLoading).toBe(false);
});
it('should initialize with custom locale', async () => {
await core.initialize('fr');
expect(core.getState().locale).toBe('fr');
expect(core.getState().isInitialized).toBe(true);
});
});
describe('translation', () => {
beforeEach(async () => {
await core.initialize();
});
it('should translate text correctly', () => {
const result = core.translate('welcome.title');
expect(result).toBe('Welcome');
});
it('should translate with parameters', () => {
const result = core.translate('welcome.message', { name: 'John' });
expect(result).toBe('Hello, John!');
});
it('should handle missing translations', () => {
const result = core.translate('missing.key');
expect(result).toBe('missing.key');
});
it('should translate plural forms', () => {
const result = core.translatePlural('user.count', 5);
expect(result).toBe('You have 5 users');
});
});
describe('locale management', () => {
beforeEach(async () => {
await core.initialize();
});
it('should change locale', async () => {
await core.setLocale('fr');
expect(core.getState().locale).toBe('fr');
});
it('should load translations for new locale', async () => {
await core.setLocale('fr');
expect(core.getState().translations).toBeDefined();
});
});
describe('formatting', () => {
beforeEach(async () => {
await core.initialize();
});
it('should format numbers', () => {
const result = core.formatNumber(1234.56);
expect(result).toBe('1234.56');
});
it('should format dates', () => {
const date = new Date('2023-01-01');
const result = core.formatDate(date);
expect(result).toBeDefined();
});
});
describe('RTL support', () => {
beforeEach(async () => {
await core.initialize();
});
it('should detect RTL locales', () => {
const result = core.isRTLLocale('ar');
expect(typeof result).toBe('boolean');
});
it('should get text direction', () => {
const result = core.getTextDirection('Hello');
expect(result).toBe('ltr');
});
});
describe('state management', () => {
it('should notify listeners of state changes', async () => {
const listener = jest.fn();
const unsubscribe = core.subscribe(listener);
await core.initialize();
expect(listener).toHaveBeenCalled();
unsubscribe();
});
it('should handle multiple listeners', async () => {
const listener1 = jest.fn();
const listener2 = jest.fn();
const unsubscribe1 = core.subscribe(listener1);
const unsubscribe2 = core.subscribe(listener2);
await core.initialize();
expect(listener1).toHaveBeenCalled();
expect(listener2).toHaveBeenCalled();
unsubscribe1();
unsubscribe2();
});
});
describe('error handling', () => {
it('should handle initialization errors gracefully', async () => {
// Mock the translation service to throw an error during initialization
const mockService = jest.fn().mockImplementation(() => ({
translate: jest.fn(),
translatePlural: jest.fn(),
formatNumberForLocale: jest.fn(),
formatDateForLocale: jest.fn(),
getTextDirection: jest.fn(),
isRTLLocale: jest.fn(),
getKeys: jest.fn(() => {
throw new Error('Initialization error');
}),
reloadTranslations: jest.fn(),
clearCache: jest.fn(),
}));
// Temporarily replace the TranslationService constructor
const originalService = require('@logistically/i18n').TranslationService;
require('@logistically/i18n').TranslationService = mockService;
const errorCore = new TranslationCore(config);
await expect(errorCore.initialize()).rejects.toThrow();
errorCore.destroy();
// Restore original service
require('@logistically/i18n').TranslationService = originalService;
});
it('should handle translation errors gracefully', async () => {
await core.initialize();
// Mock translation service to throw error
const mockService = (core as any).translationService;
mockService.translate.mockImplementationOnce(() => {
throw new Error('Translation error');
});
const result = core.translate('error.key');
expect(result).toBe('error.key');
});
it('should handle plural translation errors gracefully', async () => {
await core.initialize();
// Mock translation service to throw error
const mockService = (core as any).translationService;
mockService.translatePlural.mockImplementationOnce(() => {
throw new Error('Translation error');
});
const result = core.translatePlural('error.key', 5);
expect(result).toBe('error.key');
});
});
describe('adapter management', () => {
it('should set and get adapter', () => {
const core = new TranslationCore(config);
const mockAdapter = {
getState: jest.fn(),
getActions: jest.fn(),
subscribe: jest.fn(),
initialize: jest.fn(),
destroy: jest.fn()
};
core.setAdapter(mockAdapter);
expect(core.getAdapter()).toBe(mockAdapter);
});
it('should return undefined when no adapter is set', () => {
const core = new TranslationCore(config);
expect(core.getAdapter()).toBeUndefined();
});
});
describe('utility methods', () => {
it('should get translation service', () => {
const core = new TranslationCore(config);
const service = core.getTranslationService();
expect(service).toBeDefined();
expect(service).toBe(core['translationService']);
});
it('should get config', () => {
const core = new TranslationCore(config);
const retrievedConfig = core.getConfig();
expect(retrievedConfig).toEqual(config);
});
it('should check if initialized', () => {
const core = new TranslationCore(config);
expect(core.isInitialized()).toBe(false);
// After initialization
core['state'].isInitialized = true;
expect(core.isInitialized()).toBe(true);
});
});
describe('debug configuration', () => {
it('should handle boolean debug config', () => {
const debugConfig = { ...config, debug: true };
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translate').mockImplementation(() => {
throw new Error('Translation error');
});
core.translate('test.key');
// With boolean debug=true, logMissingKeys should be undefined, so no warning
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
it('should handle object debug config with logMissingKeys', () => {
const debugConfig = {
...config,
debug: { enabled: true, logMissingKeys: true }
};
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translate').mockImplementation(() => {
throw new Error('Translation error');
});
core.translate('test.key');
expect(consoleSpy).toHaveBeenCalledWith('Translation key not found: test.key (locale: en)');
consoleSpy.mockRestore();
});
it('should handle object debug config without logMissingKeys', () => {
const debugConfig = {
...config,
debug: { enabled: true, logMissingKeys: false }
};
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translate').mockImplementation(() => {
throw new Error('Translation error');
});
core.translate('test.key');
// Should not log warning when logMissingKeys is false
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
it('should handle object debug config with undefined logMissingKeys', () => {
const debugConfig = {
...config,
debug: { enabled: true }
};
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translate').mockImplementation(() => {
throw new Error('Translation error');
});
core.translate('test.key');
// Should not log warning when logMissingKeys is undefined
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
describe('error handling edge cases', () => {
it('should handle translatePlural with object debug config and logMissingKeys enabled', () => {
const debugConfig = {
...config,
debug: { enabled: true, logMissingKeys: true }
};
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translatePlural').mockImplementation(() => {
throw new Error('Translation error');
});
core.translatePlural('test.key', 5);
expect(consoleSpy).toHaveBeenCalledWith('Translation key not found: test.key (locale: en)');
consoleSpy.mockRestore();
});
it('should handle translatePlural with object debug config and logMissingKeys disabled', () => {
const debugConfig = {
...config,
debug: { enabled: true, logMissingKeys: false }
};
const core = new TranslationCore(debugConfig);
// Mock console.warn
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
// Mock translation service to throw error
jest.spyOn(core['translationService'], 'translatePlural').mockImplementation(() => {
throw new Error('Translation error');
});
core.translatePlural('test.key', 5);
// Should not log warning when logMissingKeys is false
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
it('should handle initialization error properly', async () => {
const core = new TranslationCore(config);
// Mock loadTranslations to throw error
jest.spyOn(core as any, 'loadTranslations').mockRejectedValue(new Error('Load error'));
await expect(core.initialize()).rejects.toThrow('Load error');
expect(core.getState().isLoading).toBe(false);
expect(core.getState().error).toBeDefined();
});
it('should handle setLocale error properly', async () => {
const core = new TranslationCore(config);
await core.initialize();
// Mock loadTranslations to throw error
jest.spyOn(core as any, 'loadTranslations').mockRejectedValue(new Error('Load error'));
await expect(core.setLocale('fr')).rejects.toThrow('Load error');
expect(core.getState().isLoading).toBe(false);
expect(core.getState().error).toBeDefined();
});
});
describe('error handling and edge cases', () => {
it('should handle translation service errors gracefully', async () => {
const mockTranslationService = {
translate: jest.fn().mockImplementation(() => {
throw new Error('Translation service error');
}),
translatePlural: jest.fn().mockImplementation(() => {
throw new Error('Translation service error');
}),
formatDateForLocale: jest.fn().mockReturnValue('formatted date'),
formatNumberForLocale: jest.fn().mockReturnValue('formatted number'),
getTextDirection: jest.fn().mockReturnValue('ltr'),
isRTLLocale: jest.fn().mockReturnValue(false),
clearCache: jest.fn(),
loadTranslationsForLocale: jest.fn().mockResolvedValue({}),
reloadTranslations: jest.fn().mockResolvedValue(undefined),
getKeys: jest.fn().mockReturnValue([])
};
const core = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: true, logMissingKeys: true }
});
// Replace the translation service with our mock
(core as any).translationService = mockTranslationService;
// Test translate with error
const result = core.translate('test.key');
expect(result).toBe('test.key'); // Should return the key on error
// Test translatePlural with error
const pluralResult = core.translatePlural('test.key', 1);
expect(pluralResult).toBe('test.key'); // Should return the key on error
});
it('should handle initialization errors properly', async () => {
const mockTranslationService = {
translate: jest.fn().mockReturnValue('translated'),
translatePlural: jest.fn().mockReturnValue('translated'),
formatDateForLocale: jest.fn().mockReturnValue('formatted date'),
formatNumberForLocale: jest.fn().mockReturnValue('formatted number'),
getTextDirection: jest.fn().mockReturnValue('ltr'),
isRTLLocale: jest.fn().mockReturnValue(false),
clearCache: jest.fn(),
loadTranslationsForLocale: jest.fn().mockResolvedValue({}),
reloadTranslations: jest.fn().mockImplementation(() => {
throw new Error('Load error');
}),
getKeys: jest.fn().mockReturnValue([])
};
const core = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: true, logMissingKeys: true }
});
// Replace the translation service with our mock
(core as any).translationService = mockTranslationService;
await expect(core.initialize('en')).rejects.toThrow('Load error');
// State should be updated to reflect the error
const state = core.getState();
expect(state.isLoading).toBe(false);
expect(state.error).toBeInstanceOf(Error);
expect(state.error?.message).toBe('Load error');
});
it('should handle setLocale errors properly', async () => {
const mockTranslationService = {
translate: jest.fn().mockReturnValue('translated'),
translatePlural: jest.fn().mockReturnValue('translated'),
formatDateForLocale: jest.fn().mockReturnValue('formatted date'),
formatNumberForLocale: jest.fn().mockReturnValue('formatted number'),
getTextDirection: jest.fn().mockReturnValue('ltr'),
isRTLLocale: jest.fn().mockReturnValue(false),
clearCache: jest.fn(),
loadTranslationsForLocale: jest.fn().mockResolvedValue({}),
reloadTranslations: jest.fn().mockImplementation(() => {
throw new Error('Load error');
}),
getKeys: jest.fn().mockReturnValue([])
};
const core = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: true, logMissingKeys: true }
});
// Replace the translation service with our mock
(core as any).translationService = mockTranslationService;
await expect(core.setLocale('es')).rejects.toThrow('Load error');
// State should be updated to reflect the error
const state = core.getState();
expect(state.isLoading).toBe(false);
expect(state.error).toBeInstanceOf(Error);
expect(state.error?.message).toBe('Load error');
});
it('should handle loadTranslations errors properly', async () => {
const mockTranslationService = {
translate: jest.fn().mockReturnValue('translated'),
translatePlural: jest.fn().mockReturnValue('translated'),
formatDateForLocale: jest.fn().mockReturnValue('formatted date'),
formatNumberForLocale: jest.fn().mockReturnValue('formatted number'),
getTextDirection: jest.fn().mockReturnValue('ltr'),
isRTLLocale: jest.fn().mockReturnValue(false),
clearCache: jest.fn(),
loadTranslationsForLocale: jest.fn().mockResolvedValue({}),
reloadTranslations: jest.fn().mockImplementation(() => {
throw new Error('Load error');
}),
getKeys: jest.fn().mockReturnValue([])
};
const core = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: true, logMissingKeys: true }
});
// Replace the translation service with our mock
(core as any).translationService = mockTranslationService;
await expect(core.loadTranslations('es')).rejects.toThrow('Load error');
// State should be updated to reflect the error
const state = core.getState();
expect(state.isLoading).toBe(false);
expect(state.error).toBeInstanceOf(Error);
expect(state.error?.message).toBe('Load error');
});
it('should handle debug configuration properly', () => {
const core = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: true, logMissingKeys: true }
});
// Test with debug enabled
const result = core.translate('missing.key');
expect(result).toBe('missing.key');
// Test with debug disabled
const coreNoDebug = new TranslationCore({
serviceName: 'test-service',
defaultLocale: 'en',
debug: { enabled: false, logMissingKeys: false }
});
const resultNoDebug = coreNoDebug.translate('missing.key');
expect(resultNoDebug).toBe('missing.key');
});
});
});