From 9d6dec025966d42bb2cbcf4a831abbb96fb85829 Mon Sep 17 00:00:00 2001 From: Cataldo Mazzilli Date: Tue, 17 Oct 2023 12:47:19 +0200 Subject: [PATCH 1/2] fix: discard changes of timezone, locale and dark mode reset initial values refs: SHELL-161 --- .../dark-theme-settings-section.tsx | 22 ++-- src/settings/general-settings.test.tsx | 102 +++++++++++++++ src/settings/general-settings.tsx | 13 +- .../language-and-timezone-settings.tsx | 118 ++++++++++-------- types/account/index.d.ts | 2 +- 5 files changed, 196 insertions(+), 61 deletions(-) create mode 100644 src/settings/general-settings.test.tsx diff --git a/src/settings/components/general-settings/dark-theme-settings-section.tsx b/src/settings/components/general-settings/dark-theme-settings-section.tsx index 99f29674..ba20a1b6 100644 --- a/src/settings/components/general-settings/dark-theme-settings-section.tsx +++ b/src/settings/components/general-settings/dark-theme-settings-section.tsx @@ -4,32 +4,33 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Select, SelectItem, SingleSelectionOnChange, Text } from '@zextras/carbonio-design-system'; import { find } from 'lodash'; import type { AddMod, DarkReaderPropValues, RemoveMod } from '../../../../types'; -import { ThemeCallbacksContext } from '../../../boot/theme-provider'; import { DARK_READER_PROP_KEY, SHELL_APP_ID } from '../../../constants'; import { isDarkReaderPropValues, useDarkReaderResultValue } from '../../../dark-mode/use-dark-reader-result-value'; import { getT } from '../../../store/i18n'; +import { useReset } from '../../hooks/use-reset'; +import { SettingsSectionProps } from '../utils'; type DarkReaderSelectItem = Array; -interface DarkThemeSettingSectionProps { +interface DarkThemeSettingSectionProps extends SettingsSectionProps { addMod: AddMod; removeMod: RemoveMod; } const DarkThemeSettingSection = ({ addMod, - removeMod + removeMod, + resetRef }: DarkThemeSettingSectionProps): React.JSX.Element | null => { - const { setDarkReaderState } = useContext(ThemeCallbacksContext); const darkReaderResultValue = useDarkReaderResultValue(); const [selection, setSelection] = useState(); @@ -58,9 +59,8 @@ const DarkThemeSettingSection = ({ if (item) { setSelection(item); } - setDarkReaderState(value); }, - [items, setDarkReaderState] + [items] ); const onSelectionChange = useCallback( @@ -85,6 +85,14 @@ const DarkThemeSettingSection = ({ } }, [darkReaderResultValue, items, setSelectNewValue]); + const init = useCallback(() => { + if (darkReaderResultValue) { + setSelectNewValue(darkReaderResultValue); + } + }, [darkReaderResultValue, setSelectNewValue]); + + useReset(resetRef, init); + if (!selection) { return null; } diff --git a/src/settings/general-settings.test.tsx b/src/settings/general-settings.test.tsx new file mode 100644 index 00000000..ef9c05a5 --- /dev/null +++ b/src/settings/general-settings.test.tsx @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2023 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import 'jest-styled-components'; +import { screen, within } from '@testing-library/react'; +import { find } from 'lodash'; + +import { + LocaleDescriptorWithLabels, + localeList, + TimeZoneDescriptor, + timeZoneList +} from './components/utils'; +import GeneralSettings from './general-settings'; +import { useAccountStore } from '../store/account'; +import { useI18nStore } from '../store/i18n'; +import { TESTID_SELECTORS } from '../test/constants'; +import { setup } from '../test/utils'; + +describe('General setting', () => { + const { defaultI18n } = useI18nStore.getState(); + const timeZoneArray = timeZoneList(defaultI18n.t); + const localeArray = localeList(defaultI18n.t); + test('When timezone is changed, discard button become enabled and when clicked the initial value is restored', async () => { + const zimbraPrefTimeZoneIdValue = 'UTC'; + + useAccountStore.setState((previousState) => ({ + ...previousState, + settings: { + ...previousState.settings, + prefs: { zimbraPrefTimeZoneId: zimbraPrefTimeZoneIdValue } + } + })); + const { user } = setup(); + const match = find( + timeZoneArray, + (item) => item.value === zimbraPrefTimeZoneIdValue + ) as TimeZoneDescriptor; + expect(match).toBeDefined(); + expect(screen.getByText(match.label)).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + await user.click(screen.getByText(match.label)); + await user.click( + within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(timeZoneArray[0].label) + ); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled(); + await user.click(screen.getByRole('button', { name: /discard changes/i })); + expect(screen.getByText(match.label)).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + }); + + test('When locale is changed, discard button become enabled and when clicked the initial value is restored', async () => { + const zimbraPrefLocaleValue = 'en'; + + useAccountStore.setState((previousState) => ({ + ...previousState, + settings: { + ...previousState.settings, + prefs: { zimbraPrefLocale: zimbraPrefLocaleValue } + } + })); + const { user } = setup(); + const match = find( + localeArray, + (item) => item.value === zimbraPrefLocaleValue + ) as LocaleDescriptorWithLabels; + expect(match).toBeDefined(); + expect(screen.getByText(match.label)).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + await user.click(screen.getByText(match.label)); + await user.click( + within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(localeArray[0].label) + ); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled(); + await user.click(screen.getByRole('button', { name: /discard changes/i })); + expect(screen.getByText(match.label)).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + }); + + test('When dark mode is changed, discard button become enabled and when clicked the initial value is restored', async () => { + useAccountStore.setState((previousState) => ({ + ...previousState, + settings: { + ...previousState.settings, + props: [{ name: 'zappDarkreaderMode', zimlet: 'carbonio-shell-ui', _content: 'auto' }] + } + })); + const { user } = setup(); + expect(screen.getByText('Auto')).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + await user.click(screen.getByText('Auto')); + await user.click(within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(/disabled/i)); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled(); + await user.click(screen.getByRole('button', { name: /discard changes/i })); + expect(screen.getByText('Auto')).toBeVisible(); + expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled(); + }); +}); diff --git a/src/settings/general-settings.tsx b/src/settings/general-settings.tsx index d64e811a..dd0a3251 100644 --- a/src/settings/general-settings.tsx +++ b/src/settings/general-settings.tsx @@ -19,7 +19,7 @@ import { SearchSettings } from './components/general-settings/search-settings'; import UserQuota from './components/general-settings/user-quota'; import SettingsHeader, { SettingsHeaderProps } from './components/settings-header'; import { ResetComponentImperativeHandler } from './components/utils'; -import LanguageAndTimeZoneSettings from './language-and-timezone-settings'; +import { LanguageAndTimeZoneSettings } from './language-and-timezone-settings'; import { AccountState, AddMod, @@ -215,6 +215,8 @@ const GeneralSettings = (): React.JSX.Element => { }, [mods, setLocalStorageSettings, createSnackbar, t]); const scalingSettingSectionRef = useRef(null); + const darkThemeSettingSectionRef = useRef(null); + const languageAndTimeZoneSettingsSectionRef = useRef(null); const outOfOfficeSettingsSectionRef = useRef(null); const searchSettingsSectionRef = useRef(null); @@ -223,6 +225,8 @@ const GeneralSettings = (): React.JSX.Element => { if (size(localStorageUnAppliedChanges) > 0) { scalingSettingSectionRef.current?.reset(); } + darkThemeSettingSectionRef.current?.reset(); + languageAndTimeZoneSettingsSectionRef.current?.reset(); outOfOfficeSettingsSectionRef.current?.reset(); searchSettingsSectionRef?.current?.reset(); }, [localStorageUnAppliedChanges]); @@ -252,13 +256,18 @@ const GeneralSettings = (): React.JSX.Element => { addLocalStoreChange={addLocalStoreChange} cleanLocalStoreChange={cleanLocalStoreChange} /> - + void; - addMod: AddMod; -}> = ({ settings, addMod, open, setOpen }) => { +} + +export const LanguageAndTimeZoneSettings = ({ + settings, + addMod, + open, + setOpen, + resetRef +}: LanguageAndTimeZoneSettingsProps): React.JSX.Element => { const t = getT(); const locales = useMemo(() => localeList(t), [t]); const timezones = useMemo(() => timeZoneList(t), [t]); const sectionTitle = useMemo(() => timezoneAndLanguageSubSection(t), [t]); - const updatePrefs = useCallback( - (prefValue: PrefsMods[keyof PrefsMods], prefKey: keyof PrefsMods) => { - addMod('prefs', prefKey, prefValue); - }, - [addMod] - ); + const updatePref = useMemo(() => upsertPrefOnUnsavedChanges(addMod), [addMod]); - const defaultLocale = useMemo( - () => - (settings.prefs.zimbraPrefLocale && - find(locales, { id: `${settings.prefs.zimbraPrefLocale}` })) || - find(locales, { id: 'en' }), - [locales, settings.prefs.zimbraPrefLocale] + const [prefLocale, setPrefLocale] = useState(settings.prefs.zimbraPrefLocale ?? ''); + + const prefLocaleSelectedValue = useMemo( + () => find(locales, (item) => item.value === prefLocale) as SelectItem, + [locales, prefLocale] ); const onLocaleChange = useCallback( (value) => { - if (value && value !== settings.prefs.zimbraPrefLocale) - updatePrefs(value, 'zimbraPrefLocale'); + if (value) { + updatePref('zimbraPrefLocale', value); + setPrefLocale(value); + } }, - [settings.prefs.zimbraPrefLocale, updatePrefs] + [updatePref] ); - const defaultTimeZone = useMemo( - () => - (settings.prefs.zimbraPrefTimeZoneId && - find(timezones, { value: `${settings.prefs.zimbraPrefTimeZoneId}` })) || - find(timezones, { value: 'UTC' }), - [timezones, settings.prefs.zimbraPrefTimeZoneId] + const [prefTimeZoneId, setPrefTimeZoneId] = useState( + settings.prefs.zimbraPrefTimeZoneId ?? '' + ); + + const prefTimeZoneIdSelectedValue = useMemo( + () => find(timezones, (item) => item.value === prefTimeZoneId) as SelectItem, + [timezones, prefTimeZoneId] ); const onTimeZoneChange = useCallback( (value) => { - if (value && value !== settings.prefs.zimbraPrefTimeZoneId) - updatePrefs(value, 'zimbraPrefTimeZoneId'); + if (value) { + updatePref('zimbraPrefTimeZoneId', value); + setPrefTimeZoneId(value); + } }, - [settings.prefs.zimbraPrefTimeZoneId, updatePrefs] + [updatePref] ); + const init = useCallback(() => { + setPrefLocale(settings.prefs.zimbraPrefLocale ?? ''); + setPrefTimeZoneId(settings.prefs.zimbraPrefTimeZoneId ?? ''); + }, [settings.prefs.zimbraPrefLocale, settings.prefs.zimbraPrefTimeZoneId]); + + useReset(resetRef, init); + return ( - {Object.keys(settings.prefs).length > 0 && ( + - )} - - {Object.keys(settings.prefs).length > 0 && timezones && defaultTimeZone && ( - - - -