Skip to content

Commit

Permalink
test: userSettings cases
Browse files Browse the repository at this point in the history
  • Loading branch information
KaiVandivier committed Jan 25, 2024
1 parent d4bd3e4 commit f7df0f2
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 78 deletions.
20 changes: 13 additions & 7 deletions adapter/src/utils/localeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ const parseJavaLocale = (locale) => {
* @returns Intl.Locale
*/
export const parseLocale = (userSettings) => {
// proposed property
if (userSettings.keyUiLanguageTag) {
return new Intl.Locale(userSettings.keyUiLanguageTag)
}
// legacy property
if (userSettings.keyUiLocale) {
return parseJavaLocale(userSettings.keyUiLocale)
try {
// proposed property
if (userSettings.keyUiLanguageTag) {
return new Intl.Locale(userSettings.keyUiLanguageTag)
}
// legacy property
if (userSettings.keyUiLocale) {
return parseJavaLocale(userSettings.keyUiLocale)
}
} catch (err) {
console.error('Unable to parse locale from user settings:', {
userSettings,
})
}

// worst-case fallback
Expand Down
214 changes: 143 additions & 71 deletions adapter/src/utils/useLocale.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { useLocale } from './useLocale.js'
// Make sure i18n locale either has translations or is reasonable
// ^ (should it be 'en'? wondering about maintanance_tl_keys)

// NOTE ABOUT MOCKS:
// Luckily, `await import(`moment/locale/${locale}`)` as used in
// `setMomentLocale` in `localeUtils.js` works the same in the Jest environment
// as in the real world, so it doesn't need mocking

jest.mock('@dhis2/d2-i18n', () => {
return {
setDefaultNamespace: jest.fn(),
Expand Down Expand Up @@ -69,88 +74,155 @@ test('happy path initial load with en language', () => {
expect(moment.locale).not.toHaveBeenCalled()
})

// For pt_BR (Portuguese in Brazil), before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. The Moment locale didn't work, because it uses another format
test('pt_BR locale', async () => {
const userSettings = { keyUiLocale: 'pt_BR' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)
describe('formerly problematic locales', () => {
// For pt_BR (Portuguese in Brazil), before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. The Moment locale didn't work, because it uses another format
test('pt_BR locale', async () => {
const userSettings = { keyUiLocale: 'pt_BR' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('ltr')
// Notice different locale formats
expect(result.current.locale.baseName).toBe('pt-BR')
expect(i18n.changeLanguage).toHaveBeenCalledWith('pt_BR')
// Dynamic imports of Moment locales is asynchronous
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('pt-br')
expect(result.current.direction).toBe('ltr')
// Notice different locale formats
expect(result.current.locale.baseName).toBe('pt-BR')
expect(i18n.changeLanguage).toHaveBeenCalledWith('pt_BR')
// Dynamic imports of Moment locales is asynchronous
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('pt-br')
})
})
})

// For ar_EG (Arabic in Egypt), before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. Setting the i18next language didn't work because there are not translation
// files for it (as of now, Jan 2024). This behavior is mocked above with
// `i18n.hasResourceBundle()`
// [Recent fixes allow for a fallback to simpler locales, e.g. 'ar',
// for much better support]
// 3. The Moment locale didn't work, both because of formatting and failing to
// fall back to simpler locales
test('ar_EG locale', async () => {
const userSettings = { keyUiLocale: 'ar_EG' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
// For ar_EG (Arabic in Egypt), before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. Setting the i18next language didn't work because there are not translation
// files for it (as of now, Jan 2024). This behavior is mocked above with
// `i18n.hasResourceBundle()`
// [Recent fixes allow for a fallback to simpler locales, e.g. 'ar',
// for much better support]
// 3. The Moment locale didn't work, both because of formatting and failing to
// fall back to simpler locales
test('ar_EG locale', async () => {
const userSettings = { keyUiLocale: 'ar_EG' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('rtl')
expect(result.current.locale.baseName).toBe('ar-EG')
// Notice fallbacks
expect(i18n.changeLanguage).toHaveBeenCalledWith('ar')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('ar')
})
)

expect(result.current.direction).toBe('rtl')
expect(result.current.locale.baseName).toBe('ar-EG')
// Notice fallbacks
expect(i18n.changeLanguage).toHaveBeenCalledWith('ar')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('ar')
})
})

// for uz_UZ_Cyrl before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. Moment locales didn't work due to formatting and lack of fallback
test('uz_UZ_Cyrl locale', async () => {
const userSettings = { keyUiLocale: 'uz_UZ_Cyrl' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
// for uz_UZ_Cyrl before fixes:
// 1. i18n.dir didn't work because it needs a BCP47-formatted string
// 2. Moment locales didn't work due to formatting and lack of fallback
test('uz_UZ_Cyrl locale', async () => {
const userSettings = { keyUiLocale: 'uz_UZ_Cyrl' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('ltr')
expect(result.current.locale.baseName).toBe('uz-Cyrl-UZ')
expect(i18n.changeLanguage).toHaveBeenCalledWith('uz_UZ_Cyrl')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('uz')
})
)
})
// Similar for UZ Latin -- notice difference in the Moment locale
test('uz_UZ_Latn locale', async () => {
const userSettings = { keyUiLocale: 'uz_UZ_Latn' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('ltr')
expect(result.current.locale.baseName).toBe('uz-Cyrl-UZ')
expect(i18n.changeLanguage).toHaveBeenCalledWith('uz_UZ_Cyrl')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('uz')
expect(result.current.direction).toBe('ltr')
expect(result.current.locale.baseName).toBe('uz-Latn-UZ')
expect(i18n.changeLanguage).toHaveBeenCalledWith('uz_UZ_Latn')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('uz-latn')
})
})
})
// Similar for UZ Latin -- notice difference in the Moment locale
test('uz_UZ_Latn locale', async () => {
const userSettings = { keyUiLocale: 'uz_UZ_Latn' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,

describe('other userSettings cases', () => {
beforeEach(() => {
// Mock browser language
jest.spyOn(window.navigator, 'language', 'get').mockImplementation(
() => 'ar-EG'
)
})

test('proposed keyUiLanguageTag property is used (preferrentially)', async () => {
const userSettings = {
keyUiLocale: 'en',
keyUiLanguageTag: 'pt-BR',
}
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('ltr')
expect(result.current.locale.baseName).toBe('pt-BR')
expect(i18n.changeLanguage).toHaveBeenCalledWith('pt_BR')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('pt-br')
})
)
})

expect(result.current.direction).toBe('ltr')
expect(result.current.locale.baseName).toBe('uz-Latn-UZ')
expect(i18n.changeLanguage).toHaveBeenCalledWith('uz_UZ_Latn')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('uz-latn')
test('keyUiLocale is missing from user settings for some reason (should fall back to browser language)', async () => {
const userSettings = {}
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('rtl')
expect(result.current.locale.baseName).toBe('ar-EG')
expect(i18n.changeLanguage).toHaveBeenCalledWith('ar')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('ar')
})
})

test('keyUiLocale is nonsense (should fall back to browser language)', async () => {
const userSettings = { keyUiLocale: 'shouldCauseError' }
const { result, waitFor } = renderHook(() =>
useLocale({
userSettings,
configDirection: undefined,
})
)

expect(result.current.direction).toBe('rtl')
expect(result.current.locale.baseName).toBe('ar-EG')
expect(i18n.changeLanguage).toHaveBeenCalledWith('ar')
await waitFor(() => {
expect(moment.locale).toHaveBeenCalledWith('ar')
})
})
})

test.todo('document direction is set by config direction')

0 comments on commit f7df0f2

Please sign in to comment.