Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"framer-motion": "^10.16.4",
"graphql": "^16.8.1",
"mdi-react": "^9.3.0",
"next": "^14.1.3",
"next": "^14.2.3",
"polished": "^4.2.2",
"prop-types": "^15.8.1",
"rc-notification": "^5.3.0",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/login/_components/LogInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const LogInForm = ({ onSubmit, error = '' }: LogInFormProps) => {
defaultValue=""
/>
<AccountForgotPassword>
<Link href="login">Forgot a password?</Link>
<Link href="resetpasswor">Forgot a password?</Link>
</AccountForgotPassword>
</FormGroupField>
</FormGroup>
Expand Down
142 changes: 142 additions & 0 deletions src/app/(auth)/resetpasswor/_components/form.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { act } from 'react-dom/test-utils';
import { screen, render, fireEvent } from '@testing-library/react';
import { MemoryRouter as Router } from 'react-router-dom';
import ResetPasswordForm from './form';

interface PasswordFieldProps {
input: {
name: string;
value: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
meta: {
touched: boolean;
error?: string;
};
placeholder: string;
keyIcon: boolean;
}

jest.mock('@/shared/components/form/Password', () => {
const PasswordFieldMock: React.FC<PasswordFieldProps> = ({
input,
meta,
placeholder,
keyIcon,
}) => (
<div>
<label htmlFor={input.name}>{placeholder}</label>
<input
id={input.name}
type="password"
name={input.name}
value={input.value}
onChange={input.onChange}
placeholder={placeholder}
/>
{meta.touched && meta.error && <span>{meta.error}</span>}
<div>Mocked Eye Icon</div>
</div>
);

return {
__esModule: true,
default: PasswordFieldMock,
};
});

describe('ResetPasswordForm', () => {
const mockOnSuccess = jest.fn();

it('should render the form and show password fields', () => {
render(
<Router>
<ResetPasswordForm onSuccess={mockOnSuccess} />
</Router>
);

const passwordInput = screen.getByPlaceholderText('Password');
const confirmPasswordInput = screen.getByPlaceholderText('Confirm Password');

expect(passwordInput).toBeInTheDocument();
expect(confirmPasswordInput).toBeInTheDocument();
});

it('should allow user to enter and confirm password', async () => {
render(
<Router>
<ResetPasswordForm onSuccess={mockOnSuccess} />
</Router>
);

const passwordInput = screen.getByPlaceholderText('Password');
const confirmPasswordInput = screen.getByPlaceholderText('Confirm Password');

fireEvent.change(passwordInput, { target: { value: 'Password@123' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'Password@123' } });

expect(passwordInput).toHaveValue('Password@123');
expect(confirmPasswordInput).toHaveValue('Password@123');
});

it('should show error if passwords do not match', async () => {
render(
<Router>
<ResetPasswordForm onSuccess={mockOnSuccess} />
</Router>
);

const passwordInput = screen.getByPlaceholderText('Password');
const confirmPasswordInput = screen.getByPlaceholderText('Confirm Password');
const submitButton = screen.getByRole('button', { name: 'Submit' });

await act(async () => {
fireEvent.change(passwordInput, { target: { value: 'Password@123' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'DifferentPassword@123' } });
fireEvent.click(submitButton);
});

const alert = screen.queryByText(/Passwords must match/i);
expect(alert).toBeInTheDocument();
});

it('should not call onSuccess if the passwords do not match', async () => {
render(
<Router>
<ResetPasswordForm onSuccess={mockOnSuccess} />
</Router>
);

const passwordInput = screen.getByPlaceholderText('Password');
const confirmPasswordInput = screen.getByPlaceholderText('Confirm Password');
const submitButton = screen.getByRole('button', { name: 'Submit' });

await act(async () => {
fireEvent.change(passwordInput, { target: { value: 'Password@123' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'DifferentPassword@123' } });
fireEvent.click(submitButton);
});

expect(mockOnSuccess).not.toHaveBeenCalled();
});

it('should call onSuccess if the passwords match', async () => {
render(
<Router>
<ResetPasswordForm onSuccess={mockOnSuccess} />
</Router>
);

const passwordInput = screen.getByPlaceholderText('Password');
const confirmPasswordInput = screen.getByPlaceholderText('Confirm Password');
const submitButton = screen.getByRole('button', { name: 'Submit' });

await act(async () => {
fireEvent.change(passwordInput, { target: { value: 'Password@123' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'Password@123' } });
fireEvent.click(submitButton);
});

expect(mockOnSuccess).toHaveBeenCalled();
});
});
103 changes: 103 additions & 0 deletions src/app/(auth)/resetpasswor/_components/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { passwordPatten } from '@/shared/utils/helpers';
import PasswordField from '@/shared/components/form/Password';
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import { FormGroup, FormGroupField, FormGroupLabel } from '@/shared/components/form/FormElements';
import { AccountButton } from '@/shared/components/account/AccountElements';
import Link from 'next/link';

interface FormValues {
password: string;
confirmPassword: string;
}

interface ResetPasswordFormProps {
onSuccess: () => void;
}

const ResetPasswordForm: React.FC<ResetPasswordFormProps> = ({ onSuccess }) => {
const { handleSubmit, control, watch } = useForm<FormValues>({
mode: 'onSubmit',
});

const onSubmit: SubmitHandler<FormValues> = (data) => {
if (data.password !== data.confirmPassword) {
alert('Passwords do not match.');
return;
}
onSuccess();
};

return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup>
<FormGroupLabel>Password</FormGroupLabel>
<FormGroupField>
<Controller
name="password"
control={control}
rules={{
required: 'This field is required',
pattern: {
value: passwordPatten,
message:
'Password must contain letters, numbers, and at least one special character',
},
}}
render={({ field, fieldState }) => (
<PasswordField
input={field}
meta={{
touched: !!fieldState.error,
error: fieldState.error?.message,
}}
placeholder="Password"
keyIcon
isAboveError
/>
)}
defaultValue=""
/>
</FormGroupField>
</FormGroup>
<FormGroup>
<FormGroupLabel>Confirm Password</FormGroupLabel>
<FormGroupField>
<Controller
name="confirmPassword"
control={control}
rules={{
required: 'Confirming password is required',
validate: {
matchesPreviousPassword: (value) => {
const { password } = watch();
return password === value || 'Passwords must match';
},
},
}}
render={({ field, fieldState }) => (
<PasswordField
input={field}
meta={{
touched: !!fieldState.error,
error: fieldState.error?.message,
}}
placeholder="Confirm Password"
keyIcon
isAboveError
/>
)}
defaultValue=""
/>
</FormGroupField>
</FormGroup>
<AccountButton type="submit" variant="primary">
Submit
</AccountButton>
<AccountButton variant="outline-primary">
<Link href="/login">Back to Login</Link>
</AccountButton>
</form>
);
};

export default ResetPasswordForm;
34 changes: 34 additions & 0 deletions src/app/(auth)/resetpasswor/_components/success.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen } from '@testing-library/react';
import ResetPasswordSuccess from '../_components/success';
import React, { ReactNode } from 'react';

jest.mock('styled-theming', () => ({
theme: jest.fn().mockImplementation((_, values) => values.mode),
}));

jest.mock('@/shared/components/account/AccountElements', () => ({
AccountContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
AccountHead: ({ children }: { children: ReactNode }) => <div>{children}</div>,
AccountLogo: ({ children }: { children: ReactNode }) => <div>{children}</div>,
AccountLogoAccent: ({ children }: { children: ReactNode }) => (
<div style={{ color: 'blue' }}>{children}</div>
),
AccountTitle: ({ children }: { children: ReactNode }) => <div>{children}</div>,
AccountButton: ({ children, ...props }: { children: ReactNode }) => (
<button {...props}>{children}</button>
),
}));

jest.mock('next/link', () => {
return ({ children, ...props }: { children: ReactNode }) => <a {...props}>{children}</a>;
});

describe('PasswordResetSuccess', () => {
it('navigates back to login on button click', () => {
render(<ResetPasswordSuccess />);

// Find the link by text and check its href attribute
const backButtonLink = screen.getByRole('link', { name: /back to login/i });
expect(backButtonLink).toHaveAttribute('href', '/login');
});
});
36 changes: 36 additions & 0 deletions src/app/(auth)/resetpasswor/_components/success.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
AccountContent,
AccountHead,
AccountLogo,
AccountLogoAccent,
AccountTitle,
AccountButton,
} from '@/shared/components/account/AccountElements';
import successImage from '@/shared/img/success.png';
import Link from 'next/link';

const ResetPasswordSuccess = () => {
return (
<div>
<img src={successImage.src} alt="Success" />
<br />
<AccountContent>
<AccountHead>
<AccountTitle>
<AccountLogo>
<AccountLogoAccent>Cool!</AccountLogoAccent>
</AccountLogo>
<br />
Your password has been reset
</AccountTitle>
</AccountHead>
</AccountContent>

<AccountButton variant="outline-primary">
<Link href="/login">Back to Login</Link>
</AccountButton>
</div>
);
};

export default ResetPasswordSuccess;
Loading