Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset Passoword: Fix showSuccessScreen, slotProps, slots props #480

Merged
merged 10 commits into from
Sep 27, 2023
1 change: 1 addition & 0 deletions login-workflow/shared-auth
Submodule shared-auth added at 792a79
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const ForgotPasswordScreen: React.FC<ForgotPasswordScreenProps> = (props)
onNext: (): void => {
navigate(routeConfig.LOGIN);
if (slotProps.SuccessScreen.WorkflowCardActionsProps)
slotProps.SuccessScreen.WorkflowCardActionsProps.onNext();
slotProps.SuccessScreen.WorkflowCardActionsProps?.onNext?.();
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import '@testing-library/jest-dom';
import { cleanup, render, screen, fireEvent, RenderResult } from '@testing-library/react';
import { cleanup, render, screen, fireEvent, RenderResult, waitFor } from '@testing-library/react';
import { ResetPasswordScreen } from './ResetPasswordScreen';
import { AuthContextProvider } from '../../contexts';
import { ResetPasswordScreenProps } from './types';
Expand Down Expand Up @@ -33,30 +33,44 @@ describe('Reset Password Screen', () => {

it('renders without crashing', () => {
renderer();

expect(screen.getByText('Reset Password')).toBeInTheDocument();
});

it('should update values when passed as props', () => {
renderer({ WorkflowCardHeaderProps: { title: 'Test Title' } });

expect(screen.queryByText('Reset Password')).toBeNull();
expect(screen.getByText('Test Title')).toBeInTheDocument();
});

it('should show success screen, when showSuccessScreen prop is true', () => {
renderer({ showSuccessScreen: true });
it('should show success screen, when okay button is clicked', async () => {
const { getByLabelText } = renderer({
showSuccessScreen: true,
PasswordProps: {
newPasswordLabel: 'New Password',
confirmPasswordLabel: 'Confirm New Password',
onPasswordChange: jest.fn(),
passwordRequirements: [],
},
WorkflowCardActionsProps: {
canGoNext: true,
nextLabel: 'Next',
},
});

expect(screen.getByText('Your password was successfully reset.')).toBeInTheDocument();
const newPasswordInput = getByLabelText('New Password');
const confirmPasswordInput = getByLabelText('Confirm New Password');
fireEvent.change(newPasswordInput, { target: { value: 'Abc@1234' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'Abc@1234' } });
fireEvent.click(screen.getByText('Next'));
await waitFor(() => expect(screen.getByText('Your password was successfully reset.')));
});

it('should show loader, when loading prop is passed to WorkflowCardBaseProps', () => {
renderer({ WorkflowCardBaseProps: { loading: true } });

expect(screen.getByTestId('blui-spinner')).toBeInTheDocument();
});

it('should call onNext, when Next button clicked', () => {
it('should call onNext, when Next button clicked', async () => {
const { getByLabelText } = renderer({
WorkflowCardActionsProps: {
onNext: mockOnNext(),
Expand All @@ -67,17 +81,15 @@ describe('Reset Password Screen', () => {

const passwordField = getByLabelText('New Password');
const confirmPasswordField = getByLabelText('Confirm New Password');

fireEvent.change(passwordField, { target: { value: 'Abcd@123' } });
fireEvent.blur(passwordField);
fireEvent.change(confirmPasswordField, { target: { value: 'Abcd@123' } });
fireEvent.blur(confirmPasswordField);

const nextButton = screen.getByText('Next');
expect(nextButton).toBeInTheDocument();
expect(screen.getByText(/Next/i)).toBeEnabled();
fireEvent.click(nextButton);
expect(mockOnNext).toHaveBeenCalled();
await waitFor(() => expect(mockOnNext).toHaveBeenCalled());
});

it('should call onPrevious, when Back button clicked', () => {
Expand All @@ -95,25 +107,4 @@ describe('Reset Password Screen', () => {
fireEvent.click(backButton);
expect(mockOnPrevious).toHaveBeenCalled();
});

it('should call onNext, when Done button is clicked on success screen', () => {
renderer({
showSuccessScreen: true,
slotProps: {
SuccessScreen: {
messageTitle: 'Success',
WorkflowCardActionsProps: {
showPrevious: false,
fullWidthButton: true,
showNext: true,
nextLabel: 'Done',
onNext: mockOnNext(),
},
},
},
});

expect(screen.getByText('Done')).toBeInTheDocument();
expect(mockOnNext).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,24 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
const { t } = useLanguageLocale();
const passwordRef = useRef(null);
const confirmRef = useRef(null);
const [passwordInput, setPasswordInput] = useState('');
const [confirmInput, setConfirmInput] = useState('');
const { triggerError, errorManagerConfig } = useErrorManager();

const {
WorkflowCardBaseProps,
WorkflowCardHeaderProps,
WorkflowCardInstructionProps,
WorkflowCardActionsProps,
PasswordProps,
errorDisplayConfig = errorManagerConfig,
slots,
slotProps,
} = props;

const [passwordInput, setPasswordInput] = useState(PasswordProps?.initialNewPasswordValue ?? '');
const [confirmInput, setConfirmInput] = useState(PasswordProps?.initialConfirmPasswordValue ?? '');
const [hasVerifyCodeError, setHasVerifyCodeError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [showSuccessScreen, setShowSuccessScreen] = useState(props.showSuccessScreen);
const { triggerError, errorManagerConfig } = useErrorManager();
const [showSuccessScreen, setShowSuccessScreen] = useState(false);

const { code, email } = parseQueryString(window.location.search);

Expand All @@ -60,20 +72,27 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
try {
setIsLoading(true);
await actions.setPassword(code, passwordInput, email);
setShowSuccessScreen(true);
if (props.showSuccessScreen === false) {
navigate(routeConfig.LOGIN);
} else {
setShowSuccessScreen(true);
}
} catch (_error) {
triggerError(_error as Error);
} finally {
setIsLoading(false);
}
}, [actions, code, passwordInput, email, triggerError]);
}, [actions, code, passwordInput, email, triggerError, props.showSuccessScreen, navigate, routeConfig]);

const areValidMatchingPasswords = useCallback((): boolean => {
if (PasswordProps?.passwordRequirements?.length === 0) {
return confirmInput === passwordInput;
}
for (let i = 0; i < passwordRequirements.length; i++) {
if (!new RegExp(passwordRequirements[i].regex).test(passwordInput)) return false;
}
return confirmInput === passwordInput;
}, [passwordRequirements, passwordInput, confirmInput]);
}, [PasswordProps?.passwordRequirements?.length, passwordRequirements, passwordInput, confirmInput]);

const updateFields = useCallback(
(fields: { password: string; confirm: string }) => {
Expand All @@ -89,15 +108,6 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const {
WorkflowCardBaseProps,
WorkflowCardHeaderProps,
WorkflowCardInstructionProps,
WorkflowCardActionsProps,
PasswordProps,
errorDisplayConfig = errorManagerConfig,
} = props;

const workflowCardBaseProps = {
loading: isLoading,
...WorkflowCardBaseProps,
Expand All @@ -118,7 +128,7 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
showPrevious: true,
nextLabel: t('bluiCommon:ACTIONS.NEXT'),
previousLabel: t('bluiCommon:ACTIONS.BACK'),
canGoNext: passwordInput !== '' && confirmInput !== '' && passwordInput === confirmInput,
canGoNext: passwordInput !== '' && confirmInput !== '' && areValidMatchingPasswords(),
...WorkflowCardActionsProps,
onNext: (): void => {
void handleOnNext();
Expand All @@ -134,7 +144,7 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
newPasswordLabel: t('bluiAuth:CHANGE_PASSWORD.NEW_PASSWORD'),
confirmPasswordLabel: t('bluiAuth:CHANGE_PASSWORD.CONFIRM_NEW_PASSWORD'),
passwordNotMatchError: t('bluiCommon:FORMS.PASS_MATCH_ERROR'),
passwordRequirements: passwordRequirements,
passwordRequirements: PasswordProps?.passwordRequirements ?? passwordRequirements,
passwordRef,
confirmRef,
...PasswordProps,
Expand All @@ -158,14 +168,13 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
WorkflowCardInstructionProps={workflowCardInstructionProps}
WorkflowCardActionsProps={workflowCardActionsProps}
PasswordProps={passwordProps}
showSuccessScreen={showSuccessScreen}
slots={slots}
slotProps={{
SuccessScreen: {
icon: <CheckCircle color="primary" sx={{ fontSize: 100 }} />,
messageTitle: t('bluiAuth:PASSWORD_RESET.SUCCESS_MESSAGE'),
message: t('bluiAuth:CHANGE_PASSWORD.SUCCESS_MESSAGE'),
onDismiss: (): void => {
navigate(routeConfig.LOGIN);
},
WorkflowCardActionsProps: {
showPrevious: false,
fullWidthButton: true,
Expand All @@ -176,8 +185,8 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = (props) =
},
},
},
...slotProps,
}}
showSuccessScreen={showSuccessScreen}
errorDisplayConfig={{
...errorDisplayConfig,
onClose: hasVerifyCodeError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
SetPassword,
WorkflowCardActions,
} from '../../components';
import { SuccessScreenBase } from '../SuccessScreen/SuccessScreenBase';
import { SuccessScreenProps } from '../SuccessScreen';
import ErrorManager from '../../components/Error/ErrorManager';
import { SuccessScreenBase, SuccessScreenProps } from '../SuccessScreen';

/**
* Component that renders a ResetPassword screen that allows a user to reset their password and shows a success message upon a successful password reset..
Expand All @@ -37,25 +36,29 @@ export const ResetPasswordScreenBase: React.FC<React.PropsWithChildren<ResetPass
const instructionsProps = props.WorkflowCardInstructionProps || {};
const actionsProps = props.WorkflowCardActionsProps || {};
const passwordProps = props.PasswordProps || { onPasswordChange: () => ({}) };
const { showSuccessScreen, slots, slotProps, errorDisplayConfig } = props;
const { showSuccessScreen, slots, slotProps = {}, errorDisplayConfig } = props;

const getSuccessScreen = (
_props: SuccessScreenProps,
SuccessScreen?: (props: SuccessScreenProps) => JSX.Element
): JSX.Element => (SuccessScreen ? SuccessScreen(_props) : <SuccessScreenBase {..._props} />);

return showSuccessScreen ? (
getSuccessScreen(slotProps?.SuccessScreen, slots?.SuccessScreen)
) : (
<WorkflowCard {...cardBaseProps}>
<WorkflowCardHeader {...headerProps} />
<WorkflowCardInstructions {...instructionsProps} />
<WorkflowCardBody>
<ErrorManager {...errorDisplayConfig}>
<SetPassword {...passwordProps} />
</ErrorManager>
</WorkflowCardBody>
<WorkflowCardActions {...actionsProps} />
</WorkflowCard>
return (
<>
{showSuccessScreen ? (
getSuccessScreen(slotProps?.SuccessScreen, slots?.SuccessScreen)
) : (
<WorkflowCard {...cardBaseProps}>
<WorkflowCardHeader {...headerProps} />
<WorkflowCardInstructions {...instructionsProps} divider />
<WorkflowCardBody>
<ErrorManager {...errorDisplayConfig}>
<SetPassword {...passwordProps} />
</ErrorManager>
</WorkflowCardBody>
<WorkflowCardActions {...actionsProps} divider />
</WorkflowCard>
)}
</>
);
};