From f7df0f2b443d129684e7a8538609812eedef6a6e Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Thu, 25 Jan 2024 14:40:48 +0100 Subject: [PATCH] test: userSettings cases --- adapter/src/utils/localeUtils.js | 20 ++- adapter/src/utils/useLocale.test.js | 214 +++++++++++++++++++--------- 2 files changed, 156 insertions(+), 78 deletions(-) diff --git a/adapter/src/utils/localeUtils.js b/adapter/src/utils/localeUtils.js index eec206ea..aadab3ae 100644 --- a/adapter/src/utils/localeUtils.js +++ b/adapter/src/utils/localeUtils.js @@ -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 diff --git a/adapter/src/utils/useLocale.test.js b/adapter/src/utils/useLocale.test.js index 5fd1cb5a..f8fd5b29 100644 --- a/adapter/src/utils/useLocale.test.js +++ b/adapter/src/utils/useLocale.test.js @@ -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(), @@ -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')