diff --git a/frontend/src/container/MySettings/Password/index.tsx b/frontend/src/container/MySettings/Password/index.tsx index 0bc9513c4e..cec2c7d9fc 100644 --- a/frontend/src/container/MySettings/Password/index.tsx +++ b/frontend/src/container/MySettings/Password/index.tsx @@ -90,18 +90,23 @@ function PasswordContainer(): JSX.Element { return ( - + {t('change_password', { ns: 'settings', })} - + {t('current_password', { ns: 'settings', })} { @@ -111,12 +116,13 @@ function PasswordContainer(): JSX.Element { /> - + {t('new_password', { ns: 'settings', })} { @@ -129,6 +135,7 @@ function PasswordContainer(): JSX.Element { {isPasswordPolicyError && ( - {' '} + {' '} {t('change_password', { ns: 'settings', })} diff --git a/frontend/src/container/MySettings/UserInfo/index.tsx b/frontend/src/container/MySettings/UserInfo/index.tsx index 01118b152b..0b9eb3bec4 100644 --- a/frontend/src/container/MySettings/UserInfo/index.tsx +++ b/frontend/src/container/MySettings/UserInfo/index.tsx @@ -86,8 +86,11 @@ function UserInfo(): JSX.Element { - Name + + Name + { setChangedName(event.target.value); @@ -102,6 +105,7 @@ function UserInfo(): JSX.Element { loading={loading} disabled={loading} onClick={onClickUpdateHandler} + data-testid="update-name-button" type="primary" > Update @@ -109,13 +113,29 @@ function UserInfo(): JSX.Element { - Email - + + {' '} + Email{' '} + + - Role - + + {' '} + Role{' '} + + diff --git a/frontend/src/container/MySettings/__tests__/MySettings.test.tsx b/frontend/src/container/MySettings/__tests__/MySettings.test.tsx new file mode 100644 index 0000000000..a3193b90a3 --- /dev/null +++ b/frontend/src/container/MySettings/__tests__/MySettings.test.tsx @@ -0,0 +1,219 @@ +import MySettingsContainer from 'container/MySettings'; +import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils'; + +const toggleThemeFunction = jest.fn(); + +jest.mock('hooks/useDarkMode', () => ({ + __esModule: true, + useIsDarkMode: jest.fn(() => ({ + toggleTheme: toggleThemeFunction, + })), + default: jest.fn(() => ({ + toggleTheme: toggleThemeFunction, + })), +})); + +const errorNotification = jest.fn(); +const successNotification = jest.fn(); +jest.mock('hooks/useNotifications', () => ({ + __esModule: true, + useNotifications: jest.fn(() => ({ + notifications: { + error: errorNotification, + success: successNotification, + }, + })), +})); + +enum ThemeOptions { + Dark = 'Dark', + Light = 'Light', +} + +describe('MySettings Flows', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render(); + }); + + describe('Dark/Light Theme Switch', () => { + it('Should display Dark and Light theme buttons properly', async () => { + expect(screen.getByText('Dark')).toBeInTheDocument(); + + const darkThemeIcon = screen.getByTestId('dark-theme-icon'); + expect(darkThemeIcon).toBeInTheDocument(); + expect(darkThemeIcon.tagName).toBe('svg'); + + expect(screen.getByText('Light')).toBeInTheDocument(); + const lightThemeIcon = screen.getByTestId('light-theme-icon'); + expect(lightThemeIcon).toBeInTheDocument(); + expect(lightThemeIcon.tagName).toBe('svg'); + }); + + it('Should activate Dark and Light buttons on click', async () => { + const initialSelectedOption = screen.getByRole('radio', { + name: ThemeOptions.Dark, + }); + expect(initialSelectedOption).toBeChecked(); + + const newThemeOption = screen.getByRole('radio', { + name: ThemeOptions.Light, + }); + fireEvent.click(newThemeOption); + + expect(newThemeOption).toBeChecked(); + }); + + it('Should switch the them on clicking Light theme', async () => { + const lightThemeOption = screen.getByRole('radio', { + name: /light/i, + }); + fireEvent.click(lightThemeOption); + + await waitFor(() => { + expect(toggleThemeFunction).toBeCalled(); + }); + }); + }); + + describe('User Details Form', () => { + it('Should properly display the User Details Form', () => { + const userDetailsHeader = screen.getByRole('heading', { + name: /user details/i, + }); + const nameLabel = screen.getByTestId('name-label'); + const nameTextbox = screen.getByTestId('name-textbox'); + const updateNameButton = screen.getByTestId('update-name-button'); + const emailLabel = screen.getByTestId('email-label'); + const emailTextbox = screen.getByTestId('email-textbox'); + const roleLabel = screen.getByTestId('role-label'); + const roleTextbox = screen.getByTestId('role-textbox'); + + expect(userDetailsHeader).toBeInTheDocument(); + expect(nameLabel).toBeInTheDocument(); + expect(nameTextbox).toBeInTheDocument(); + expect(updateNameButton).toBeInTheDocument(); + expect(emailLabel).toBeInTheDocument(); + expect(emailTextbox).toBeInTheDocument(); + expect(roleLabel).toBeInTheDocument(); + expect(roleTextbox).toBeInTheDocument(); + }); + + it('Should update the name on clicking Update button', async () => { + const nameTextbox = screen.getByTestId('name-textbox'); + const updateNameButton = screen.getByTestId('update-name-button'); + + act(() => { + fireEvent.change(nameTextbox, { target: { value: 'New Name' } }); + }); + + fireEvent.click(updateNameButton); + + await waitFor(() => + expect(successNotification).toHaveBeenCalledWith({ + message: 'success', + }), + ); + }); + }); + + describe('Reset password', () => { + let currentPasswordTextbox: Node | Window; + let newPasswordTextbox: Node | Window; + let submitButtonElement: HTMLElement; + + beforeEach(() => { + currentPasswordTextbox = screen.getByTestId('current-password-textbox'); + newPasswordTextbox = screen.getByTestId('new-password-textbox'); + submitButtonElement = screen.getByTestId('update-password-button'); + }); + + it('Should properly display the Password Reset Form', () => { + const passwordResetHeader = screen.getByTestId('change-password-header'); + expect(passwordResetHeader).toBeInTheDocument(); + + const currentPasswordLabel = screen.getByTestId('current-password-label'); + expect(currentPasswordLabel).toBeInTheDocument(); + + expect(currentPasswordTextbox).toBeInTheDocument(); + + const newPasswordLabel = screen.getByTestId('new-password-label'); + expect(newPasswordLabel).toBeInTheDocument(); + + expect(newPasswordTextbox).toBeInTheDocument(); + expect(submitButtonElement).toBeInTheDocument(); + + const savePasswordIcon = screen.getByTestId('update-password-icon'); + expect(savePasswordIcon).toBeInTheDocument(); + expect(savePasswordIcon.tagName).toBe('svg'); + }); + + it('Should display validation error if password is less than 8 characters', async () => { + const currentPasswordTextbox = screen.getByTestId( + 'current-password-textbox', + ); + act(() => { + fireEvent.change(currentPasswordTextbox, { target: { value: '123' } }); + }); + const validationMessage = await screen.findByTestId('validation-message'); + + await waitFor(() => { + expect(validationMessage).toHaveTextContent( + 'Password must a have minimum of 8 characters', + ); + }); + }); + + test("Should display 'inavlid credentials' error if different current and new passwords are provided", async () => { + act(() => { + fireEvent.change(currentPasswordTextbox, { + target: { value: '123456879' }, + }); + + fireEvent.change(newPasswordTextbox, { target: { value: '123456789' } }); + }); + + fireEvent.click(submitButtonElement); + + await waitFor(() => expect(errorNotification).toHaveBeenCalled()); + }); + + it('Should check if the "Change Password" button is disabled in case current / new password is less than 8 characters', () => { + act(() => { + fireEvent.change(currentPasswordTextbox, { + target: { value: '123' }, + }); + fireEvent.change(newPasswordTextbox, { target: { value: '123' } }); + }); + + expect(submitButtonElement).toBeDisabled(); + }); + + test("Should check if 'Change Password' button is enabled when password is at least 8 characters ", async () => { + expect(submitButtonElement).toBeDisabled(); + + act(() => { + fireEvent.change(currentPasswordTextbox, { + target: { value: '123456789' }, + }); + fireEvent.change(newPasswordTextbox, { target: { value: '1234567890' } }); + }); + + expect(submitButtonElement).toBeEnabled(); + }); + + test("Should check if 'Change Password' button is disabled when current and new passwords are the same ", async () => { + expect(submitButtonElement).toBeDisabled(); + + act(() => { + fireEvent.change(currentPasswordTextbox, { + target: { value: '123456789' }, + }); + fireEvent.change(newPasswordTextbox, { target: { value: '123456789' } }); + }); + + expect(submitButtonElement).toBeDisabled(); + }); + }); +}); diff --git a/frontend/src/container/MySettings/index.tsx b/frontend/src/container/MySettings/index.tsx index 4bc3ffdb3a..aa049ca7c8 100644 --- a/frontend/src/container/MySettings/index.tsx +++ b/frontend/src/container/MySettings/index.tsx @@ -17,7 +17,7 @@ function MySettings(): JSX.Element { { label: (
- Dark{' '} + Dark{' '}
), value: 'dark', @@ -25,7 +25,7 @@ function MySettings(): JSX.Element { { label: (
- Light{' '} + Light{' '}
), value: 'light', @@ -63,6 +63,7 @@ function MySettings(): JSX.Element { value={theme} optionType="button" buttonStyle="solid" + data-testid="theme-selector" /> @@ -74,7 +75,12 @@ function MySettings(): JSX.Element { - diff --git a/frontend/src/mocks-server/handlers.ts b/frontend/src/mocks-server/handlers.ts index 49f27cf2a3..dae41a06b0 100644 --- a/frontend/src/mocks-server/handlers.ts +++ b/frontend/src/mocks-server/handlers.ts @@ -155,6 +155,24 @@ export const handlers = [ rest.post('http://localhost/api/v1/invite', (_, res, ctx) => res(ctx.status(200), ctx.json(inviteUser)), ), + rest.put('http://localhost/api/v1/user/:id', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + data: 'user updated successfully', + }), + ), + ), + rest.post('http://localhost/api/v1/changePassword', (_, res, ctx) => + res( + ctx.status(403), + ctx.json({ + status: 'error', + errorType: 'forbidden', + error: 'invalid credentials', + }), + ), + ), rest.get( 'http://localhost/api/v3/autocomplete/aggregate_attributes',