Skip to content

Commit

Permalink
feat: handle password change return value HP-2501 (#376)
Browse files Browse the repository at this point in the history
* feat: handle password change return value

* feat: add playwright test
  • Loading branch information
Riippi authored Aug 22, 2024
1 parent e2f1679 commit 4866ee4
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 53 deletions.
21 changes: 19 additions & 2 deletions e2e/tests/modify-profile-info.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { test, expect } from '@playwright/test';

import { USER_PASSWORD } from '../utils/constants';
import { loginToProfileWithSuomiFi } from '../utils/utils';

const TEST_SSN = '210281-9988';
const SAVE_SUCCESS = 'Tallennus onnistui';

test.describe.configure({ mode: 'serial' });

Expand All @@ -18,7 +20,7 @@ test('Modify phonenumber', async ({ page }) => {
await page.getByLabel('Muokkaa puhelinnumeroa').click();
await page.getByLabel('Kirjoita Puhelinnumero. Tämä').fill(randomPhoneNumber);
await page.getByTestId('phones-0-save-button').click();
await expect(page.getByText('Tallennus onnistui')).toBeVisible();
await expect(page.getByText(SAVE_SUCCESS)).toBeVisible();
await expect(page.getByTestId('phones-0-value')).toContainText(
randomPhoneNumber
);
Expand All @@ -36,7 +38,7 @@ test('Modify address', async ({ page }) => {
await page.getByLabel('Kirjoita Postinumero').fill(randomZipCode);
await page.getByLabel('Kirjoita Kaupunki').fill(randomCity);
await page.getByTestId('addresses-0-save-button').click();
await expect(page.getByText('Tallennus onnistui')).toBeVisible();
await expect(page.getByText(SAVE_SUCCESS)).toBeVisible();

// Check if new values are visible
await expect(page.getByTestId('addresses-0-address-value')).toContainText(
Expand Down Expand Up @@ -70,3 +72,18 @@ test.skip('Change language and verify notification', async ({ page }) => {
await page.getByRole('option', { name: 'Suomi' }).click();
await expect(notificationElement).toBeVisible();
});

test('Change password', async ({ page }) => {
await expect(page.getByTestId('change-password-button')).toBeVisible();
await page.getByTestId('change-password-button').click();
await page.getByLabel('Uusi salasana').click();
await page.getByLabel('Uusi salasana').fill(USER_PASSWORD);
await page.getByLabel('Vahvista salasana').click();
await page.getByLabel('Vahvista salasana').fill(USER_PASSWORD);
await expect(
page.getByRole('button', { name: 'Vaihda salasana' })
).toBeVisible();
await page.getByRole('button', { name: 'Vaihda salasana' }).click();
await expect(page.getByLabel('Profiilivalikko')).toBeVisible();
await expect(page.getByText(SAVE_SUCCESS)).toBeVisible();
});
2 changes: 1 addition & 1 deletion e2e/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const loginToExampleApp = async (page: Page, ssn: string) => {
.getByRole('banner')
.getByRole('button', { name: 'Kirjaudu sisään' })
.click();
await page.getByRole('link', { name: 'Suomi.fi-tunnistautuminen' }).click();
await page.getByRole('link', { name: 'Suomi.fi identification' }).click();
await page.getByRole('link', { name: 'Test IdP' }).click();
await fillSSNAndContinue(page, ssn);
await page.getByRole('button', { name: 'Continue to service' }).click();
Expand Down
5 changes: 5 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import CookieConsentPage from './cookieConsents/CookieConsentPage';
import LoginSSO from './auth/components/loginsso/LoginSSO';
import MatomoTracker from './common/matomo/MatomoTracker';
import { MatomoProvider } from './common/matomo/matomo-context';
import PasswordChangeCallback from './passwordChange/PasswordChangeCallback';

countries.registerLocale(fi);
countries.registerLocale(en);
Expand Down Expand Up @@ -54,6 +55,10 @@ function App(): React.ReactElement {
<Route path="/gdpr-callback">
<GdprAuthorizationCodeManagerCallback />
</Route>
<Route
path="/password-change-callback"
component={PasswordChangeCallback}
></Route>
<Route path="/login">
<Login />
</Route>
Expand Down
1 change: 1 addition & 0 deletions src/auth/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export class AuthService {
await this.userManager
.signinRedirect({
ui_locales: i18n.language,
redirect_uri: `${origin}/password-change-callback`,
extraQueryParams: {
kc_action: 'UPDATE_PASSWORD',
},
Expand Down
11 changes: 11 additions & 0 deletions src/common/cssHelpers/form.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
border-bottom: 1px solid var(--color-black-30);
}

.password-container {
border-top: 1px solid var(--color-black-30);
padding-top: var(--spacing-layout-s);
}

.editor-description-container h2,
.editor-description-container h3 {
margin-top: 0;
Expand Down Expand Up @@ -85,6 +90,12 @@ h3.label-size {
margin-top: 0;
}

h3.subtitle-size {
font-size: var(--fontsize-body-l);
font-weight: 500;
margin-top: 0;
}

.form-field {
padding-bottom: var(--spacing-m);
}
Expand Down
8 changes: 8 additions & 0 deletions src/passwordChange/PasswordChangeCallback.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.wrapper {
display: flex;
flex-direction: column;
max-width: 300px;
margin: var(--spacing-l) auto;
align-items: center;
}

36 changes: 36 additions & 0 deletions src/passwordChange/PasswordChangeCallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useEffect, useContext } from 'react';
import { RouteChildrenProps, useLocation } from 'react-router';
import { useTranslation } from 'react-i18next';
import { LoadingSpinner } from 'hds-react';

import styles from './PasswordChangeCallback.module.css';
import { getLinkRedirectState } from '../profile/hooks/useHistoryListener';
import { ProfileContext } from '../profile/context/ProfileContext';

function PasswordChangeCallback({
history,
}: RouteChildrenProps): React.ReactElement | null {
const { t } = useTranslation();

const { setPasswordUpdateState } = useContext(ProfileContext);
const location = useLocation();

useEffect(() => {
const params = new URLSearchParams(location.search);
const stat = params.get('kc_action_status');

if (stat === 'success') {
setPasswordUpdateState(true);
}

history.replace('/', getLinkRedirectState());
}, [history, location, setPasswordUpdateState]);

return (
<div className={styles.wrapper}>
<LoadingSpinner small />
<p>{t('loading')}</p>
</div>
);
}
export default PasswordChangeCallback;
56 changes: 56 additions & 0 deletions src/passwordChange/__tests__/PasswordChangeCallback.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { BrowserRouter, RouteChildrenProps } from 'react-router-dom';
import { act, render } from '@testing-library/react';

import PasswordChangeCallback from '../PasswordChangeCallback';

const mockedDefaultProps = {
history: {
replace: vi.fn(),
},
};

const renderComponent = () =>
render(
<BrowserRouter>
<PasswordChangeCallback
{...((mockedDefaultProps as unknown) as RouteChildrenProps)}
/>
</BrowserRouter>
);

const getHistoryReplaceCallArgument = () =>
mockedDefaultProps.history.replace.mock.calls[0][0];

vi.mock('react-router-dom', async () => {
const module = await vi.importActual('react-router-dom');

return {
...module,
useHistory: vi.fn().mockImplementation(() => mockedDefaultProps.history),
};
});

describe('<PasswordChangeCallback />', () => {
afterEach(() => {
mockedDefaultProps.history.replace.mockReset();
});

it('render without error', async () => {
renderComponent();

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
});

it('should redirect user', async () => {
renderComponent();

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});

expect(getHistoryReplaceCallArgument()).toBe('/');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EditDataType } from '../../helpers/editData';

type Props = {
content: NotificationContent;
dataType: EditDataType;
dataType: EditDataType | 'password';
bottomSpacing?: boolean;
noSpacing?: boolean;
bottomSpacingDesktop?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment, useContext } from 'react';
import React, { Fragment, useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, IconLinkExternal } from 'hds-react';
import classNames from 'classnames';
Expand All @@ -9,16 +9,28 @@ import { getAmrStatic, hasPasswordLogin } from './authenticationProviderUtil';
import ProfileSection from '../../../common/profileSection/ProfileSection';
import commonFormStyles from '../../../common/cssHelpers/form.module.css';
import { ProfileContext } from '../../context/ProfileContext';
import useNotificationContent from '../editingNotifications/useNotificationContent';
import EditingNotifications from '../editingNotifications/EditingNotifications';

function AuthenticationProviderInformation(): React.ReactElement | null {
const { t } = useTranslation();
const { profile } = useProfile();

const { data } = useContext(ProfileContext);
const { data, passwordUpdateState, setPasswordUpdateState } = useContext(
ProfileContext
);

const hasPassword = hasPasswordLogin(data);

const amr = getAmrStatic(profile);
const showSuccess = passwordUpdateState;
const { content, setSuccessMessage } = useNotificationContent();

useEffect(() => {
if (showSuccess) {
setSuccessMessage('save');
setPasswordUpdateState(false);
}
}, [showSuccess, setPasswordUpdateState, setSuccessMessage]);

if (!amr) {
return null;
Expand All @@ -40,25 +52,38 @@ function AuthenticationProviderInformation(): React.ReactElement | null {

{hasPassword && (
<Fragment>
<hr />
<div className={classNames(commonFormStyles['flex-box-rows'])}>
<div className={commonFormStyles['editor-title-and-value']}>
<h3 className={commonFormStyles['label-size']}>
<div
className={classNames(
commonFormStyles['responsive-flex-box-columns-rows'],
commonFormStyles['password-container']
)}
>
<div
className={classNames(
commonFormStyles['editor-title-and-value'],
commonFormStyles['responsive-flex-box-columns-rows']
)}
>
<h3 className={commonFormStyles['subtitle-size']}>
{t('profileInformation.password')}
</h3>
</div>
<div className={commonFormStyles['edit-buttons-container']}>
<Button
iconLeft={<IconLinkExternal />}
data-testid={'change-password-button'}
onClick={() => {
authService.changePassword();
}}
>
{t('profileInformation.changePassword')}
</Button>
<div className={commonFormStyles['edit-buttons']}>
<div className={commonFormStyles['edit-buttons-container']}>
<Button
iconLeft={<IconLinkExternal />}
data-testid={'change-password-button'}
onClick={() => {
authService.changePassword();
}}
>
{t('profileInformation.changePassword')}
</Button>
</div>
</div>
</div>

<EditingNotifications content={content} dataType={'password'} />
</Fragment>
)}
</div>
Expand Down
Loading

0 comments on commit 4866ee4

Please sign in to comment.