src/i18n/__tests__/I18n.spec.js
import MockApi from '../../api/Api'
import I18n from '../I18n'
jest.mock('../../api/Api')
describe('Internationalisation (I18n)', () => {
let mockApi
let i18n
const mockTranslations = {
general_settings: 'General settings',
yes: 'Yup',
no: 'Nope',
system_settings_in_french: 'Paramètres du système',
// 'escapes': 'Characters may be escaped! Even\nnewlines?!?',
string_with_variable: 'Some times $$variable$$ are useful',
}
const mockUnicode = 'Param\\u00e8tres du syst\\u00e8me'
const mockEscape =
'Characters\\ may \\b\\e \\e\\s\\c\\a\\p\\e\\d\\!\\\\ Even\\\nnewline\\s\\?\\!\\?'
const mockPropsFile =
`${
'general_settings=General settings\n' +
'yes=Yup\n' +
'no=Nope\n\n# Blank lines and commends - ignored?\n#\n\n' +
'system_settings_in_french='
}${mockUnicode}\n` + `escapes=${mockEscape}\n`
beforeEach(() => {
mockApi = MockApi.getApi()
i18n = new I18n()
})
afterEach(() => {
MockApi.mockReset()
})
it('should not be allowed to be called without new', () => {
expect(() => I18n()).toThrowErrorMatchingSnapshot()
})
it('should set an instance of Api onto the SystemConfiguration instance', () => {
expect(i18n.api).toBe(mockApi)
})
it('addSource() should be a function', () => {
expect(i18n.addSource).toBeInstanceOf(Function)
})
it('addStrings() should be a function', () => {
expect(i18n.addStrings).toBeInstanceOf(Function)
})
it('load() should be a function', () => {
expect(i18n.load).toBeInstanceOf(Function)
})
it('should set the passed sources onto the object', () => {
const sources = ['translation_18n']
i18n = new I18n(sources)
expect(i18n.sources).toBe(sources)
})
it('should use the passed Api object', () => {
mockApi = jest.fn()
i18n = new I18n([], mockApi)
expect(i18n.api).toBe(mockApi)
})
it("getTranslations() should throw an error is translations haven't been loaded yet", () => {
expect.assertions(1)
try {
i18n.getTranslation('some_string')
} catch (err) {
expect(err.message).toMatchSnapshot()
}
})
describe('getI18n', () => {
it('should be a function on the I18n class', () => {
expect(typeof I18n.getI18n).toBe('function')
})
it('should return a new instanceof I18n', () => {
expect(I18n.getI18n()).toBeInstanceOf(I18n)
})
})
describe('addStrings()', () => {
it('accepts a single string', () => {
i18n.addStrings('yes')
const strings = Array.from(i18n.strings)
expect(strings).toContain('yes')
expect(strings.length).toBe(1)
})
it('accepts an array of strings', () => {
i18n.addStrings(['yes', 'no', 'maybe'])
const strings = Array.from(i18n.strings)
expect(strings).toContain('yes')
expect(strings).toContain('no')
expect(strings).toContain('maybe')
expect(strings.length).toBe(3)
})
it('handles consequtive calls', () => {
i18n.addStrings(['yes', 'no'])
i18n.addStrings('maybe')
i18n.addStrings('probably')
const strings = Array.from(i18n.strings)
expect(strings).toContain('yes')
expect(strings).toContain('no')
expect(strings).toContain('maybe')
expect(strings).toContain('probably')
expect(strings.length).toBe(4)
})
it("doesn't add duplicates", () => {
i18n.addStrings(['yes', 'no'])
i18n.addStrings(['no', 'maybe'])
i18n.addStrings(['maybe', 'probably', 'yes'])
const strings = Array.from(i18n.strings)
expect(strings).toContain('yes')
expect(strings).toContain('no')
expect(strings).toContain('maybe')
expect(strings).toContain('probably')
expect(strings.length).toBe(4)
})
it('should not add empty strings', () => {
jest.spyOn(i18n.strings, 'add')
i18n.addStrings(['yes', '', ' '])
expect(i18n.strings.add).toHaveBeenCalledTimes(1)
})
})
describe('load()', () => {
let apiGet
let apiPost
let apiReq
beforeEach(() => {
apiGet = mockApi.get.mockReturnValueOnce(
Promise.resolve(mockTranslations)
)
apiPost = mockApi.post.mockReturnValueOnce(
Promise.resolve(mockTranslations)
)
apiReq = mockApi.request.mockReturnValueOnce(
Promise.resolve(mockPropsFile)
)
i18n.addStrings(['yes', 'no'])
})
it('should return a promise', () => {
expect(i18n.load()).toBeInstanceOf(Promise)
})
it('should POST to get untranslated strings', () => {
expect.assertions(4)
return i18n.load().then(() => {
expect(apiGet).toHaveBeenCalledTimes(0)
expect(apiPost).toHaveBeenCalledTimes(1)
expect(apiReq).toHaveBeenCalledTimes(0)
expect(i18n.getTranslation('yes')).toEqual(mockTranslations.yes)
})
})
it('should load props files first', () => {
i18n.addSource('props_file_name')
expect.assertions(3)
return i18n.load().then(() => {
expect(apiGet).toHaveBeenCalledTimes(0)
expect(apiPost).toHaveBeenCalledTimes(0)
expect(apiReq).toHaveBeenCalledTimes(1)
})
})
it('keeps going if one props file fails', () => {
i18n.addSource('props_file_one')
i18n.addSource('props_file_two')
i18n.addSource('props_file_three')
apiReq.mockReset()
apiReq
.mockReturnValueOnce(Promise.resolve(mockPropsFile))
.mockReturnValueOnce(Promise.reject('404 Fail or something'))
.mockReturnValueOnce(Promise.resolve(''))
expect.assertions(3)
return i18n.load().then(() => {
expect(apiGet).toHaveBeenCalledTimes(0)
expect(apiPost).toHaveBeenCalledTimes(0)
expect(apiReq).toHaveBeenCalledTimes(3)
})
})
it('chooses strings based on source order', () => {
i18n.addSource('slow_props_file')
i18n.addSource('fast_props_file')
apiReq.mockReset()
apiReq
.mockReturnValueOnce(
new Promise((resolve) => {
setTimeout(() => {
resolve('result=first priority file\n')
})
})
)
.mockReturnValueOnce(
Promise.resolve('result=first file to load\n')
)
expect.assertions(1)
return i18n.load().then(() => {
expect(i18n.getTranslation('result')).toEqual(
'first priority file'
)
})
})
it('should not add the strings if no responses were returned', () => {
i18n.addStrings(['string_that_has_no_translation'])
mockApi.post.mockReturnValueOnce(
Promise.resolve({
string_that_has_no_translation:
'string_that_has_no_translation',
})
)
return i18n
.load()
.then(() =>
expect(
i18n.translations.string_that_has_no_translation
).toBeUndefined()
)
})
})
describe('async API', () => {
beforeEach(() => {
i18n.api.get.mockReturnValue(Promise.resolve(mockTranslations))
i18n.api.request.mockReturnValue(Promise.resolve(mockPropsFile))
i18n.api.post.mockReturnValue(Promise.resolve(mockTranslations))
i18n.addSource('mockPropsFile')
i18n.addStrings(Object.keys(mockTranslations))
})
describe('getTranslation()', () => {
it('returns the correct translations', () => {
expect.assertions(5)
return i18n.load().then(() => {
Object.keys(mockTranslations).forEach((key) => {
expect(i18n.getTranslation(key)).toEqual(
mockTranslations[key]
)
})
})
})
it('decodes unicode entities from properties files', () =>
i18n.load().then(() => {
expect(mockApi.get).toHaveBeenCalledTimes(0)
expect(mockApi.post).toHaveBeenCalledTimes(1)
expect(mockApi.request).toHaveBeenCalledTimes(1)
expect(
i18n.getTranslation('system_settings_in_french')
).toEqual(mockTranslations.system_settings_in_french)
expect(
i18n.getTranslation('system_settings_in_french')
).not.toEqual(mockUnicode)
}))
it('returns ** string ** for unknown strings', () => {
expect.assertions(1)
return i18n.load().then(() => {
expect(i18n.getTranslation('string')).toEqual(
'** string **'
)
})
})
it('replaces $$variable$$ in translations', () => {
expect.assertions(2)
return i18n.load().then(() => {
const sub1 = i18n.getTranslation('string_with_variable', {
variable: 'tests',
})
const sub2 = i18n.getTranslation('string_with_variable', {
variable: 'FUNNY TRANSLATIONS',
})
expect(sub1).toBe('Some times tests are useful')
expect(sub2).toBe(
'Some times FUNNY TRANSLATIONS are useful'
)
})
})
})
describe('isTranslated()', () => {
it('returns true for translated strings', () => {
expect.assertions(5)
return i18n.load().then(() => {
Object.keys(mockTranslations).forEach((key) => {
expect(i18n.isTranslated(key)).toEqual(true)
})
})
})
it('returns false for untranslated strings', () => {
expect.assertions(1)
return i18n.load().then(() => {
expect(i18n.isTranslated('string')).toEqual(false)
})
})
it("totally tilts out if translations haven't been loaded yet", () => {
expect(() =>
i18n.isTranslated('some random string')
).toThrowErrorMatchingSnapshot()
})
})
describe('getUntranslatedStrings()', () => {
it("returns undefined if translations haven't been loaded yet", () => {
expect(i18n.getUntranslatedStrings()).toEqual(undefined)
})
it('returns an array', () => {
expect.assertions(1)
return i18n.load().then(() => {
expect(i18n.getUntranslatedStrings()).toBeInstanceOf(Array)
})
})
it("doesn't return translated strings", () => {
i18n.addStrings('string')
return i18n.load().then(() => {
const str = i18n.getUntranslatedStrings()
expect(str).toContain('string')
expect(str).not.toContain('yes')
expect(str).not.toContain('some_random_string')
})
})
})
})
})