start
This commit is contained in:
49
src/components/register/__tests__/mobile-form.test.tsx
Normal file
49
src/components/register/__tests__/mobile-form.test.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { renderWithClient } from '../../../utils/test/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { mockFailureResponse } from '../../../mocks/server';
|
||||
import { mockedMobile } from '../../../mocks/data';
|
||||
import * as tempUserAPI from '../../../api/temp-user';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import MobileForm from '../mobile-form';
|
||||
|
||||
describe('MobileForm component', () => {
|
||||
const mockedHandleSubmit = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('successful render component', async () => {
|
||||
mockFailureResponse();
|
||||
|
||||
const result = renderWithClient(<MobileForm onSubmit={mockedHandleSubmit} />, MemoryRouter);
|
||||
expect(await result.findByTestId('mobile-form')).toBeInTheDocument();
|
||||
expect(await result.findByText('تایید')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('call register user function with correct arguments', async () => {
|
||||
const createTempUser = jest.spyOn(tempUserAPI, 'createTempUser');
|
||||
|
||||
const result = renderWithClient(<MobileForm onSubmit={mockedHandleSubmit} />, MemoryRouter);
|
||||
const user = userEvent.setup();
|
||||
await user.type(result.getByTestId('mobile-field'), mockedMobile);
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
await waitFor(() => expect(createTempUser).toHaveBeenCalledWith({ mobile: mockedMobile }));
|
||||
await waitFor(() => expect(mockedHandleSubmit).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('form errors', async () => {
|
||||
const result = renderWithClient(<MobileForm onSubmit={mockedHandleSubmit} />, MemoryRouter);
|
||||
const mobileField = await result.getByTestId('mobile-field');
|
||||
const user = userEvent.setup();
|
||||
await user.type(mobileField, mockedMobile.substring(2));
|
||||
await user.tab();
|
||||
|
||||
expect(await result.getByTestId('mobile-field-error')).toHaveTextContent(
|
||||
'لطفا یک شماره تلفن همراه معتبر وارد نمایید.'
|
||||
);
|
||||
});
|
||||
});
|
||||
72
src/components/register/__tests__/register-content.test.tsx
Normal file
72
src/components/register/__tests__/register-content.test.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { renderWithClient } from '../../../utils/test/react-query';
|
||||
import RegisterContent from '../register-content';
|
||||
import {
|
||||
mockedAuthKey,
|
||||
mockedMobile,
|
||||
mockedRestrictedMobile,
|
||||
mockedSmsKey
|
||||
} from '../../../mocks/data';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { act } from '@testing-library/react-hooks';
|
||||
import { DEFAULT_TIME_LEFT } from '../sms-key/resend-view';
|
||||
|
||||
describe('RegisterContent component', () => {
|
||||
test('successful render component step 1', async () => {
|
||||
const result = renderWithClient(<RegisterContent />, MemoryRouter);
|
||||
expect(await result.findByTestId('mobile-form')).toBeInTheDocument();
|
||||
expect(await result.findByText('تایید')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('successful render component step 2', async () => {
|
||||
const result = renderWithClient(<RegisterContent startStep={1} defaultMobile={mockedMobile} />);
|
||||
expect(await result.findByTestId('smskey-content')).toBeInTheDocument();
|
||||
expect(await result.findByText('تایید')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('successful render component step 3', async () => {
|
||||
const result = renderWithClient(
|
||||
<RegisterContent startStep={2} defaultMobile={mockedMobile} defaultAuthKey={mockedAuthKey} />,
|
||||
MemoryRouter
|
||||
);
|
||||
expect(await result.findByTestId('register-form')).toBeInTheDocument();
|
||||
expect(await result.findByText('تایید')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('call checkSmsKeyRequest function with error 403', async () => {
|
||||
const data = { mobile: mockedRestrictedMobile, smsKey: mockedSmsKey };
|
||||
|
||||
const result = renderWithClient(<RegisterContent startStep={1} defaultMobile={data.mobile} />);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(result.getByTestId('smsKey-field'), data.smsKey);
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
await waitFor(() =>
|
||||
expect(result.getByRole('alert')).toHaveTextContent(
|
||||
'محدودیت وارد کردن کد پیامکی اشتباه برای شما به پایان رسیده است.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('call sendSmsKeyRequest function with error 403', async () => {
|
||||
jest.useFakeTimers();
|
||||
const data = { mobile: mockedRestrictedMobile };
|
||||
|
||||
const result = renderWithClient(
|
||||
<RegisterContent startStep={1} defaultMobile={data.mobile} />,
|
||||
MemoryRouter
|
||||
);
|
||||
|
||||
act(() => jest.advanceTimersToNextTimer(DEFAULT_TIME_LEFT));
|
||||
jest.useRealTimers();
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(await result.getByRole('button', { name: /ارسال مجدد کد/i }));
|
||||
await waitFor(() =>
|
||||
expect(result.getByRole('alert')).toHaveTextContent(
|
||||
'محدودیت ارسال کد پیامکی برای شما به پایان رسیده است.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
121
src/components/register/__tests__/register-form.test.tsx
Normal file
121
src/components/register/__tests__/register-form.test.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { renderWithClient } from '../../../utils/test/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import RegisterForm from '../register-form';
|
||||
import { mockedAuthKey, mockedMobile, mockedRegisterUserData } from '../../../mocks/data';
|
||||
import * as userAPI from '../../../api/user';
|
||||
import * as introducerAPI from '../../../api/introducer';
|
||||
import * as authAPI from '../../../api/auth';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
describe('RegisterForm component', () => {
|
||||
const data = mockedRegisterUserData;
|
||||
const { iban, ...userData } = data;
|
||||
const { mobile, password } = userData;
|
||||
|
||||
const mockedHandleReset = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(
|
||||
<RegisterForm mobile={mockedMobile} authKey={mockedAuthKey} reset={mockedHandleReset} />,
|
||||
MemoryRouter
|
||||
);
|
||||
expect(await result.findByTestId('register-form')).toBeInTheDocument();
|
||||
expect(await result.findByText(mockedMobile)).toBeInTheDocument();
|
||||
expect(await result.findByText('تایید')).toBeInTheDocument();
|
||||
mockedHandleReset.mockRestore();
|
||||
});
|
||||
|
||||
test('call registerUser functions with correct arguments', async () => {
|
||||
const createUserSpy = jest.spyOn(userAPI, 'createUser');
|
||||
const createIntroducerSpy = jest.spyOn(introducerAPI, 'createIntroducer');
|
||||
const fetchTokenSpy = jest.spyOn(authAPI, 'fetchToken');
|
||||
|
||||
const result = renderWithClient(
|
||||
<RegisterForm mobile={mockedMobile} authKey={mockedAuthKey} reset={mockedHandleReset} />,
|
||||
MemoryRouter
|
||||
);
|
||||
await user.type(result.getByTestId('firstName-field'), data.firstName);
|
||||
await user.type(result.getByTestId('lastName-field'), data.lastName);
|
||||
await user.selectOptions(await result.getByTestId('gender-field'), data.gender);
|
||||
await user.type(result.getByTestId('password-field'), data.password);
|
||||
await user.type(result.getByTestId('rePassword-field'), data.password);
|
||||
if (data.iban) await user.type(result.getByTestId('iban-field'), data.iban);
|
||||
await user.click(await result.getByTestId('termsAndConditions-field'));
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
await waitFor(() => expect(createUserSpy).toHaveBeenCalledWith(userData));
|
||||
await waitFor(() => expect(createIntroducerSpy).toHaveBeenCalledWith({ iban }));
|
||||
await waitFor(() => expect(fetchTokenSpy).toHaveBeenCalledWith({ mobile, password }));
|
||||
await waitFor(() => expect(mockedHandleReset).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('form errors', async () => {
|
||||
const result = renderWithClient(
|
||||
<RegisterForm mobile={mockedMobile} authKey={mockedAuthKey} reset={mockedHandleReset} />,
|
||||
MemoryRouter
|
||||
);
|
||||
user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
expect(mockedHandleReset).not.toHaveBeenCalled();
|
||||
|
||||
for (const fieldName of [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'gender',
|
||||
'password',
|
||||
'rePassword',
|
||||
'termsAndConditions'
|
||||
]) {
|
||||
await waitFor(() =>
|
||||
expect(result.getByTestId(`${fieldName}-field-error`)).toHaveTextContent(
|
||||
fieldName === 'termsAndConditions'
|
||||
? 'پذیرفتن شرایط و قوانین الزامی است.'
|
||||
: 'وارد کردن این فیلد الزامی است.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await user.selectOptions(await result.getByTestId('gender-field'), '');
|
||||
await user.tab();
|
||||
expect(await result.getByTestId('gender-field-error')).toHaveTextContent(
|
||||
'وارد کردن این فیلد الزامی است.'
|
||||
);
|
||||
});
|
||||
|
||||
test('form verification', async () => {
|
||||
const createUserSpy = jest.spyOn(userAPI, 'createUser');
|
||||
const createIntroducerSpy = jest.spyOn(introducerAPI, 'createIntroducer');
|
||||
const fetchTokenSpy = jest.spyOn(authAPI, 'fetchToken');
|
||||
|
||||
const result = renderWithClient(
|
||||
<RegisterForm mobile={mockedMobile} authKey={mockedAuthKey} reset={mockedHandleReset} />,
|
||||
MemoryRouter
|
||||
);
|
||||
await user.type(result.getByTestId('firstName-field'), data.firstName);
|
||||
await user.type(result.getByTestId('lastName-field'), data.lastName);
|
||||
await user.selectOptions(result.getByTestId('gender-field'), data.gender);
|
||||
await user.type(result.getByTestId('password-field'), data.password);
|
||||
await user.type(result.getByTestId('rePassword-field'), data.password.substring(1));
|
||||
if (data.iban) await user.type(result.getByTestId('iban-field'), data.iban);
|
||||
await user.click(result.getByTestId('termsAndConditions-field'));
|
||||
await user.click(result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.getByTestId(`rePassword-field-error`)).toHaveTextContent(
|
||||
'رمزهای عبور غیر یکسان هستند.'
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => expect(createUserSpy).not.toHaveBeenCalled());
|
||||
await waitFor(() => expect(createIntroducerSpy).not.toHaveBeenCalled());
|
||||
await waitFor(() => expect(fetchTokenSpy).not.toHaveBeenCalled());
|
||||
await waitFor(() => expect(mockedHandleReset).not.toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
13
src/components/register/__tests__/register-page.test.tsx
Normal file
13
src/components/register/__tests__/register-page.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { renderWithClient } from '../../../utils/test/react-query';
|
||||
import { expectPageTestIds, expectPageTitle } from '../../../utils/test/expects';
|
||||
import RegisterPage, { REGISTER_PAGE_TITLE } from '../index';
|
||||
|
||||
describe('RegisterPage component', () => {
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(<RegisterPage />, MemoryRouter);
|
||||
expectPageTitle(REGISTER_PAGE_TITLE);
|
||||
await expectPageTestIds(result);
|
||||
expect(await result.findByTestId('register')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
24
src/components/register/index.tsx
Normal file
24
src/components/register/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import MainLayout from '../layouts/main';
|
||||
import { useUser } from '../../hooks/react-query/use-user';
|
||||
import { getNextRedirectURL } from '../utils/url';
|
||||
import RegisterContent from './register-content';
|
||||
|
||||
function RegisterPage() {
|
||||
const { isAuthenticated } = useUser();
|
||||
if (isAuthenticated) return <Redirect to={getNextRedirectURL()} />;
|
||||
|
||||
return (
|
||||
<MainLayout loginRequired={false} title={REGISTER_PAGE_TITLE}>
|
||||
<div data-testid='register' className='flex justify-center'>
|
||||
<div className='w-full w-full sm:w-3/4 lg:w-1/2'>
|
||||
<RegisterContent />
|
||||
</div>
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export const REGISTER_PAGE_TITLE = 'ثبت نام';
|
||||
|
||||
export default RegisterPage;
|
||||
61
src/components/register/mobile-form.tsx
Normal file
61
src/components/register/mobile-form.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Field, Form, Formik, FormikHelpers } from 'formik';
|
||||
import Segment from '../shared/segment';
|
||||
import Alert from '../shared/alert';
|
||||
import FormInput from '../shared/forms/input';
|
||||
import Button from '../shared/buttons/button';
|
||||
import { mobile } from '../shared/forms/validations';
|
||||
import { useCreateTempUserMutation } from '../../hooks/react-query/use-temp-user';
|
||||
import { CreateTempUserDataType } from '../../api/temp-user';
|
||||
import { useFormErrorMessage } from '../../hooks/index/use-form-error-message';
|
||||
|
||||
export type MobileFormData = CreateTempUserDataType;
|
||||
export type MobileFormProps = { onSubmit: (data: MobileFormData) => void };
|
||||
|
||||
function MobileForm({ onSubmit }: MobileFormProps) {
|
||||
const { formErrorMessage, setFormError } = useFormErrorMessage();
|
||||
const { mutate: createTempUser } = useCreateTempUserMutation();
|
||||
|
||||
async function submit(values: MobileFormData, { setErrors }: FormikHelpers<MobileFormData>) {
|
||||
createTempUser(values, {
|
||||
onSuccess: onSubmit,
|
||||
onError: error => {
|
||||
setFormError(error);
|
||||
setErrors({ mobile: error.data });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Segment dataTestId='mobile-form'>
|
||||
<Formik initialValues={{ mobile: '' }} onSubmit={submit}>
|
||||
{({ dirty, isSubmitting }) => (
|
||||
<>
|
||||
{dirty && formErrorMessage && (
|
||||
<Alert content={formErrorMessage} className='mt-3 mb-5' />
|
||||
)}
|
||||
<Form noValidate className='grid grid-cols-1 gap-5 py-3'>
|
||||
<Field
|
||||
required
|
||||
name='mobile'
|
||||
label='شماره تلفن همراه'
|
||||
placeholder='09121234567'
|
||||
component={FormInput}
|
||||
type='tel'
|
||||
maxLength={11}
|
||||
validate={mobile}
|
||||
/>
|
||||
<Button
|
||||
type='submit'
|
||||
color='green'
|
||||
content='تایید'
|
||||
disabled={!dirty || isSubmitting}
|
||||
/>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</Segment>
|
||||
);
|
||||
}
|
||||
|
||||
export default MobileForm;
|
||||
90
src/components/register/register-content.tsx
Normal file
90
src/components/register/register-content.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import MobileForm, { MobileFormData } from './mobile-form';
|
||||
import SmsKey from './sms-key';
|
||||
import RegisterForm from './register-form';
|
||||
import { CheckSmsKeyRequestReturnType } from '../../api/temp-user';
|
||||
import { useFormErrorMessage } from '../../hooks/index/use-form-error-message';
|
||||
import Alert from '../shared/alert';
|
||||
|
||||
export type RegisterContentPropsType = {
|
||||
startStep?: number;
|
||||
defaultMobile?: string | null;
|
||||
defaultAuthKey?: string | null;
|
||||
};
|
||||
|
||||
function RegisterContent({
|
||||
startStep = 0,
|
||||
defaultMobile = null,
|
||||
defaultAuthKey = null
|
||||
}: RegisterContentPropsType) {
|
||||
const [step, setStep] = useState<number>(startStep);
|
||||
const [mobile, setMobile] = useState<string | null>(defaultMobile);
|
||||
const [authKey, setAuthKey] = useState<string | null>(defaultAuthKey);
|
||||
const { formErrorMessage: errorMessage, setFormError: setError } = useFormErrorMessage();
|
||||
|
||||
if (errorMessage) return <Alert content={errorMessage} />;
|
||||
|
||||
if (step === MOBILE || !mobile) {
|
||||
return (
|
||||
<>
|
||||
<MobileForm
|
||||
onSubmit={({ mobile }: MobileFormData) => {
|
||||
setMobile(mobile);
|
||||
setStep(SMS_KEY);
|
||||
}}
|
||||
/>
|
||||
<p className='m-6 text-center'>
|
||||
<span className='font-medium text-red-500'>توجه: </span>
|
||||
<span> اگر هم اکنون به عنوان </span>
|
||||
<span className='font-medium'>داوطلب</span>
|
||||
<span> عضو سایت </span>
|
||||
<span className='font-medium'>فلش کارتهای مدمشاور</span>
|
||||
<span> هستید نیاز به ثبتنام مجدد ندارید و با استفاده از دکمه </span>
|
||||
<Link to='/login' className='font-medium text-indigo-500'>
|
||||
ورود
|
||||
</Link>
|
||||
<span> وارد شوید و در غیر این صورت با استفاده از فرم بالا ثبتنام کنید.</span>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === SMS_KEY || !authKey) {
|
||||
return (
|
||||
<SmsKey
|
||||
mobile={mobile}
|
||||
onSubmit={({ authKey }: CheckSmsKeyRequestReturnType) => {
|
||||
setAuthKey(authKey);
|
||||
setStep(REGISTER);
|
||||
}}
|
||||
onBack={() => setStep(MOBILE)}
|
||||
onError={error => {
|
||||
setError(error);
|
||||
setMobile(null);
|
||||
setStep(MOBILE);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === REGISTER) {
|
||||
return (
|
||||
<RegisterForm
|
||||
mobile={mobile}
|
||||
authKey={authKey}
|
||||
reset={() => {
|
||||
setStep(MOBILE);
|
||||
setMobile(null);
|
||||
setAuthKey(null);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const [MOBILE, SMS_KEY, REGISTER] = [0, 1, 2];
|
||||
|
||||
export default RegisterContent;
|
||||
152
src/components/register/register-form.tsx
Normal file
152
src/components/register/register-form.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import Segment from '../shared/segment';
|
||||
import {
|
||||
gender,
|
||||
password,
|
||||
required,
|
||||
requiredTermsAndConditions
|
||||
} from '../shared/forms/validations';
|
||||
import FormInput from '../shared/forms/input';
|
||||
import FormSelect from '../shared/forms/select';
|
||||
import Button from '../shared/buttons/button';
|
||||
import Alert from '../shared/alert';
|
||||
import ButtonGroup from '../shared/buttons/button-group';
|
||||
import { useFormErrorMessage } from '../../hooks/index/use-form-error-message';
|
||||
import { useRegisterMutation, UseRegisterMutationDataType } from '../../hooks/react-query/use-auth';
|
||||
import FormCheckbox from '../shared/forms/checkbox';
|
||||
|
||||
type RegisterFormDataType = UseRegisterMutationDataType & {
|
||||
rePassword: string;
|
||||
termsAndConditions: boolean;
|
||||
};
|
||||
type RegisterFormPropsType = {
|
||||
mobile: string;
|
||||
authKey: string;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
function RegisterForm({ mobile, authKey, reset }: RegisterFormPropsType) {
|
||||
const { formErrorMessage, setFormError } = useFormErrorMessage();
|
||||
const { mutate: registerUser } = useRegisterMutation();
|
||||
|
||||
function validate({ password, rePassword }: RegisterFormDataType) {
|
||||
const errors: { rePassword?: string } = {};
|
||||
if (password !== rePassword) errors.rePassword = 'رمزهای عبور غیر یکسان هستند.';
|
||||
return errors;
|
||||
}
|
||||
|
||||
async function submit(data: RegisterFormDataType) {
|
||||
const { rePassword, termsAndConditions, ...registerData } = data;
|
||||
await registerUser(registerData, {
|
||||
onSuccess: reset,
|
||||
onError: setFormError
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Segment dataTestId='register-form'>
|
||||
<p className='my-4 text-center font-normal'>
|
||||
<span>شماره تلفن همراه: </span>
|
||||
<span>{mobile}</span>
|
||||
</p>
|
||||
<hr className='my-4' />
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
mobile,
|
||||
authKey,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
gender: '',
|
||||
password: '',
|
||||
rePassword: '',
|
||||
iban: '',
|
||||
termsAndConditions: false
|
||||
}}
|
||||
onSubmit={submit}
|
||||
validate={validate}
|
||||
>
|
||||
{({ dirty }: { dirty: boolean }) => (
|
||||
<>
|
||||
{dirty && formErrorMessage && (
|
||||
<Alert content={formErrorMessage} className='mt-3 mb-5' />
|
||||
)}
|
||||
|
||||
<Form noValidate data-testid='login-form' className='grid grid-cols-1 gap-5 py-3'>
|
||||
<Field id='username' name='mobile' component={FormInput} type='hidden' />
|
||||
<Field name='authKey' component={FormInput} type='hidden' />
|
||||
|
||||
<Field
|
||||
required
|
||||
name='firstName'
|
||||
label='نام'
|
||||
component={FormInput}
|
||||
validate={required}
|
||||
/>
|
||||
<Field
|
||||
required
|
||||
name='lastName'
|
||||
label='نام خانوادگی'
|
||||
component={FormInput}
|
||||
validate={required}
|
||||
/>
|
||||
<Field
|
||||
required
|
||||
name='gender'
|
||||
label='جنسیت'
|
||||
component={FormSelect}
|
||||
options={GENDER_OPTIONS}
|
||||
validate={gender}
|
||||
/>
|
||||
<Field
|
||||
required
|
||||
name='password'
|
||||
label='رمز عبور'
|
||||
type='password'
|
||||
component={FormInput}
|
||||
validate={password}
|
||||
/>
|
||||
<Field
|
||||
required
|
||||
name='rePassword'
|
||||
label='تائید رمز عبور'
|
||||
type='password'
|
||||
component={FormInput}
|
||||
validate={required}
|
||||
autoComplete='off'
|
||||
/>
|
||||
<Field name='iban' component={FormInput} type='hidden' />
|
||||
<Field
|
||||
required
|
||||
name='termsAndConditions'
|
||||
label={
|
||||
<>
|
||||
<Link to='/terms-and-conditions' target='_blank' className='text-indigo-600'>
|
||||
شرایط و قوانین{' '}
|
||||
</Link>
|
||||
<span>را مطالعه کردهام و آنها را میپذیرم.</span>
|
||||
</>
|
||||
}
|
||||
component={FormCheckbox}
|
||||
validate={requiredTermsAndConditions}
|
||||
/>
|
||||
<ButtonGroup float>
|
||||
<Button type='submit' color='green' content='تایید' />
|
||||
<Button type='submit' content='بازگشت' onClick={reset} />
|
||||
</ButtonGroup>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</Segment>
|
||||
);
|
||||
}
|
||||
|
||||
const GENDER_OPTIONS = [
|
||||
{ name: 'جنسیت', text: 'جنسیت', value: '' },
|
||||
{ name: 'مرد', text: 'مرد', value: 'M' },
|
||||
{ name: 'زن', text: 'زن', value: 'F' }
|
||||
];
|
||||
|
||||
export default RegisterForm;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { mockedMobile } from '../../../../mocks/data';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import MobileView from '../mobile-view';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
describe('MobileView component', () => {
|
||||
const mockedHandleBack = jest.fn();
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
test('successful render component', async () => {
|
||||
const result = render(<MobileView mobile={mockedMobile} onBack={mockedHandleBack} />);
|
||||
expect(await result.findByTestId('smskey-mobile-view')).toBeInTheDocument();
|
||||
expect(await result.findByText('شماره تلفن همراه:')).toBeInTheDocument();
|
||||
expect(await result.findByText(mockedMobile)).toBeInTheDocument();
|
||||
expect(
|
||||
await result.findByRole('button', { name: 'تغییر شماره تلفن همراه' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('call onBack function with correct arguments', async () => {
|
||||
const result = render(<MobileView mobile={mockedMobile} onBack={mockedHandleBack} />);
|
||||
const actionButton = await result.findByRole('button', { name: 'تغییر شماره تلفن همراه' });
|
||||
expect(actionButton).toBeInTheDocument();
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(actionButton);
|
||||
expect(mockedHandleBack).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { mockedMobile, mockedRestrictedMobile } from '../../../../mocks/data';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import ResendView, { DEFAULT_TIME_LEFT } from '../resend-view';
|
||||
import { act } from '@testing-library/react-hooks';
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import * as tempUserAPI from '../../../../api/temp-user';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
describe('ResendView component', () => {
|
||||
const mockedHandleError = jest.fn();
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(
|
||||
<ResendView mobile={mockedMobile} onError={mockedHandleError} />
|
||||
);
|
||||
expect(await result.findByTestId('smskey-resend-view')).toBeInTheDocument();
|
||||
expect(await result.findByText('زمان انتظار برای ارسال مجدد:')).toBeInTheDocument();
|
||||
expect(await result.findByText(DEFAULT_TIME_LEFT)).toBeInTheDocument();
|
||||
expect(await result.findByText('ثانیه')).toBeInTheDocument();
|
||||
|
||||
act(() => jest.advanceTimersToNextTimer(DEFAULT_TIME_LEFT));
|
||||
expect(await result.findByRole('button', { name: 'ارسال مجدد کد' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('call sendSmsKey function with correct arguments', async () => {
|
||||
const mockSendSmsKeyRequest = jest.spyOn(tempUserAPI, 'sendSmsKeyRequest');
|
||||
|
||||
const data = { mobile: mockedMobile };
|
||||
const result = renderWithClient(
|
||||
<ResendView mobile={data.mobile} onError={mockedHandleError} />
|
||||
);
|
||||
|
||||
act(() => jest.advanceTimersToNextTimer(DEFAULT_TIME_LEFT));
|
||||
const actionButton = await result.findByRole('button', { name: 'ارسال مجدد کد' });
|
||||
expect(actionButton).toBeInTheDocument();
|
||||
|
||||
jest.useRealTimers();
|
||||
const user = userEvent.setup();
|
||||
await user.click(actionButton);
|
||||
await expect(mockSendSmsKeyRequest).toHaveBeenCalledWith(data);
|
||||
await waitFor(() => expect(mockedHandleError).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('call onError function with correct arguments', async () => {
|
||||
const mockSendSmsKeyRequest = jest.spyOn(tempUserAPI, 'sendSmsKeyRequest');
|
||||
|
||||
const data = { mobile: mockedRestrictedMobile };
|
||||
const result = renderWithClient(
|
||||
<ResendView mobile={data.mobile} onError={mockedHandleError} />
|
||||
);
|
||||
|
||||
act(() => jest.advanceTimersToNextTimer(DEFAULT_TIME_LEFT));
|
||||
const actionButton = await result.findByRole('button', { name: 'ارسال مجدد کد' });
|
||||
expect(actionButton).toBeInTheDocument();
|
||||
|
||||
jest.useRealTimers();
|
||||
const user = userEvent.setup();
|
||||
await user.click(actionButton);
|
||||
await expect(mockSendSmsKeyRequest).toHaveBeenCalledWith(data);
|
||||
await waitFor(() => expect(mockedHandleError).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
124
src/components/register/sms-key/__tests__/sms-key-form.test.tsx
Normal file
124
src/components/register/sms-key/__tests__/sms-key-form.test.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import {
|
||||
mockedCheckSmsKeyReturnData,
|
||||
mockedMobile,
|
||||
mockedRestrictedMobile,
|
||||
mockedSmsKey,
|
||||
mockedWrongSmsKey
|
||||
} from '../../../../mocks/data';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import SmsKeyForm from '../sms-key-form';
|
||||
import * as tempUserAPI from '../../../../api/temp-user';
|
||||
|
||||
describe('SmsKeyForm component', () => {
|
||||
const mockedHandleSubmit = jest.fn();
|
||||
const mockedHandleError = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(
|
||||
<SmsKeyForm mobile={mockedMobile} onSubmit={mockedHandleSubmit} onError={mockedHandleError} />
|
||||
);
|
||||
expect(await result.findByTestId('smskey-form')).toBeInTheDocument();
|
||||
const mobileInput = await result.findByTestId('mobile-field');
|
||||
expect(mobileInput).toBeInTheDocument();
|
||||
expect(mobileInput).toHaveValue(mockedMobile);
|
||||
expect(
|
||||
await result.findByText('یک کد 6 رقمی برای شما ارسال شد، لطفا کد ارسال شده را وارد نمایید.')
|
||||
).toBeInTheDocument();
|
||||
expect(await result.findByText(/کد احراز هویت/)).toBeInTheDocument();
|
||||
expect(await result.findByRole('button', { name: 'تایید' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('form errors', async () => {
|
||||
const result = renderWithClient(
|
||||
<SmsKeyForm mobile={mockedMobile} onSubmit={mockedHandleSubmit} onError={mockedHandleError} />
|
||||
);
|
||||
const smsKeyField = await result.getByTestId('smsKey-field');
|
||||
const user = userEvent.setup();
|
||||
await user.type(smsKeyField, '12345');
|
||||
await user.tab();
|
||||
|
||||
expect(await result.getByTestId('smsKey-field-error')).toHaveTextContent(
|
||||
'لطفا کد 6 رقمی پیامک شده به شماره خود را وارد نمایید.'
|
||||
);
|
||||
});
|
||||
|
||||
describe('call checkSmsKeyRequest function', () => {
|
||||
test('correct arguments', async () => {
|
||||
const mockCheckSmsKeyRequest = jest.spyOn(tempUserAPI, 'checkSmsKeyRequest');
|
||||
const data = { mobile: mockedMobile, smsKey: mockedSmsKey };
|
||||
|
||||
const result = renderWithClient(
|
||||
<SmsKeyForm
|
||||
mobile={data.mobile}
|
||||
onSubmit={mockedHandleSubmit}
|
||||
onError={mockedHandleError}
|
||||
/>
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(result.getByTestId('smsKey-field'), data.smsKey);
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
await waitFor(() => expect(mockCheckSmsKeyRequest).toHaveBeenCalledWith(data));
|
||||
await waitFor(() =>
|
||||
expect(mockedHandleSubmit).toHaveBeenCalledWith(
|
||||
mockedCheckSmsKeyReturnData,
|
||||
data,
|
||||
undefined
|
||||
)
|
||||
);
|
||||
await waitFor(() => expect(mockedHandleError).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('error 403', async () => {
|
||||
const mockCheckSmsKeyRequest = jest.spyOn(tempUserAPI, 'checkSmsKeyRequest');
|
||||
const data = { mobile: mockedRestrictedMobile, smsKey: mockedSmsKey };
|
||||
|
||||
const result = renderWithClient(
|
||||
<SmsKeyForm
|
||||
mobile={data.mobile}
|
||||
onSubmit={mockedHandleSubmit}
|
||||
onError={mockedHandleError}
|
||||
/>
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(result.getByTestId('smsKey-field'), data.smsKey);
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
|
||||
await waitFor(() => expect(mockCheckSmsKeyRequest).toHaveBeenCalledWith(data));
|
||||
await waitFor(() => expect(mockedHandleSubmit).not.toHaveBeenCalled());
|
||||
await waitFor(() => expect(mockedHandleError).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('other errors', async () => {
|
||||
const mockCheckSmsKeyRequest = jest.spyOn(tempUserAPI, 'checkSmsKeyRequest');
|
||||
const data = { mobile: mockedMobile, smsKey: mockedWrongSmsKey };
|
||||
|
||||
const result = renderWithClient(
|
||||
<SmsKeyForm
|
||||
mobile={data.mobile}
|
||||
onSubmit={mockedHandleSubmit}
|
||||
onError={mockedHandleError}
|
||||
/>
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(result.getByTestId('smsKey-field'), data.smsKey);
|
||||
await user.click(await result.getByRole('button', { name: /تایید/i }));
|
||||
await waitFor(() => expect(mockCheckSmsKeyRequest).toHaveBeenCalledWith(data));
|
||||
await waitFor(() => expect(mockedHandleSubmit).not.toHaveBeenCalled());
|
||||
await waitFor(() => expect(mockedHandleError).not.toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(result.getByRole('alert')).toHaveTextContent('کد وارد شده اشتباه است.')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
17
src/components/register/sms-key/__tests__/sms-key.test.tsx
Normal file
17
src/components/register/sms-key/__tests__/sms-key.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import { mockedMobile } from '../../../../mocks/data';
|
||||
import SmsKey from '../index';
|
||||
|
||||
describe('SmsKey component', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(
|
||||
<SmsKey mobile={mockedMobile} onSubmit={jest.fn()} onError={jest.fn()} onBack={jest.fn()} />
|
||||
);
|
||||
expect(await result.findByTestId('smskey-content')).toBeInTheDocument();
|
||||
expect(await result.findByTestId('smskey-mobile-view')).toBeInTheDocument();
|
||||
expect(await result.findByTestId('smskey-form')).toBeInTheDocument();
|
||||
expect(await result.findByTestId('smskey-resend-view')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
30
src/components/register/sms-key/index.tsx
Normal file
30
src/components/register/sms-key/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import Segment from '../../shared/segment';
|
||||
import { CheckSmsKeyRequestDataType, CheckSmsKeyRequestReturnType } from '../../../api/temp-user';
|
||||
import { APIErrorType } from '../../../api';
|
||||
import MobileView from './mobile-view';
|
||||
import ResendView from './resend-view';
|
||||
import SmsKeyForm from './sms-key-form';
|
||||
|
||||
export type SmsKeyFormData = CheckSmsKeyRequestDataType;
|
||||
export type SmsKeyFormPropsType = {
|
||||
mobile: string;
|
||||
onSubmit: (data: CheckSmsKeyRequestReturnType) => void;
|
||||
onBack: () => void;
|
||||
onError: (error: APIErrorType) => void;
|
||||
};
|
||||
|
||||
function SmsKey({ mobile, onSubmit, onBack, onError }: SmsKeyFormPropsType) {
|
||||
return (
|
||||
<Segment dataTestId='smskey-content'>
|
||||
<p className='my-6 text-center font-medium'>فرم ارسال کد احراز هویت</p>
|
||||
<hr />
|
||||
<MobileView mobile={mobile} onBack={onBack} className='text-large text-center' />
|
||||
<hr />
|
||||
<SmsKeyForm mobile={mobile} onSubmit={onSubmit} onError={onError} />
|
||||
<hr />
|
||||
<ResendView mobile={mobile} onError={onError} className='text-large mt-4 text-center' />
|
||||
</Segment>
|
||||
);
|
||||
}
|
||||
|
||||
export default SmsKey;
|
||||
23
src/components/register/sms-key/mobile-view.tsx
Normal file
23
src/components/register/sms-key/mobile-view.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Button from '../../shared/buttons/button';
|
||||
import { CheckSmsKeyRequestDataType } from '../../../api/temp-user';
|
||||
|
||||
export type MobileData = CheckSmsKeyRequestDataType;
|
||||
export type MobilePropsType = {
|
||||
mobile: string;
|
||||
onBack: () => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function MobileView({ mobile, onBack, className }: MobilePropsType) {
|
||||
return (
|
||||
<div data-testid='smskey-mobile-view' className={className}>
|
||||
<p className='my-6 mt-8'>
|
||||
<span>شماره تلفن همراه: </span>
|
||||
<span>{mobile}</span>
|
||||
</p>
|
||||
<Button outline content='تغییر شماره تلفن همراه' onClick={onBack} className='mx-auto my-4' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MobileView;
|
||||
50
src/components/register/sms-key/resend-view.tsx
Normal file
50
src/components/register/sms-key/resend-view.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import Button from '../../shared/buttons/button';
|
||||
import { useSendSmsKeyMutation } from '../../../hooks/react-query/use-temp-user';
|
||||
import { useTimer } from '../../../hooks/index/use-timer';
|
||||
import { APIErrorType } from '../../../api';
|
||||
|
||||
export type ResendSmsKeyPropsType = {
|
||||
mobile: string;
|
||||
onError: (error: APIErrorType) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ResendView({ mobile, onError, className }: ResendSmsKeyPropsType) {
|
||||
const { timeLeft, resetTimeLeft } = useTimer(DEFAULT_TIME_LEFT);
|
||||
const { mutate: sendSmsKeyRequest } = useSendSmsKeyMutation();
|
||||
|
||||
async function resend() {
|
||||
await sendSmsKeyRequest(
|
||||
{ mobile },
|
||||
{
|
||||
onSuccess: resetTimeLeft,
|
||||
onError: onError
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid='smskey-resend-view' className={className}>
|
||||
{timeLeft > 0 ? (
|
||||
<p>
|
||||
<span>زمان انتظار برای ارسال مجدد: </span>
|
||||
<span>{timeLeft}</span>
|
||||
<span> ثانیه</span>
|
||||
</p>
|
||||
) : (
|
||||
<Button
|
||||
outline
|
||||
color='yellow'
|
||||
disabled={timeLeft > 0}
|
||||
content='ارسال مجدد کد'
|
||||
onClick={resend}
|
||||
className='mx-auto'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const DEFAULT_TIME_LEFT = 60;
|
||||
|
||||
export default ResendView;
|
||||
78
src/components/register/sms-key/sms-key-form.tsx
Normal file
78
src/components/register/sms-key/sms-key-form.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Field, Form, Formik, FormikHelpers } from 'formik';
|
||||
import Button from '../../shared/buttons/button';
|
||||
import FormInput from '../../shared/forms/input';
|
||||
import { smsKey } from '../../shared/forms/validations';
|
||||
import { useCheckSmsKeyMutation } from '../../../hooks/react-query/use-temp-user';
|
||||
import { CheckSmsKeyRequestDataType, CheckSmsKeyRequestReturnType } from '../../../api/temp-user';
|
||||
import { useFormErrorMessage } from '../../../hooks/index/use-form-error-message';
|
||||
import Alert from '../../shared/alert';
|
||||
import { APIErrorType } from '../../../api';
|
||||
|
||||
export type SmsKeyFormPropsType = {
|
||||
mobile: string;
|
||||
onSubmit: (data: CheckSmsKeyRequestReturnType) => void;
|
||||
onError: (error: APIErrorType) => void;
|
||||
};
|
||||
|
||||
function SmsKeyForm({ mobile, onSubmit, onError }: SmsKeyFormPropsType) {
|
||||
const { formErrorMessage, setFormErrorMessage } = useFormErrorMessage();
|
||||
const { mutate: checkSmsKeyRequest } = useCheckSmsKeyMutation();
|
||||
|
||||
async function submit(
|
||||
values: CheckSmsKeyRequestDataType,
|
||||
{ setErrors }: FormikHelpers<CheckSmsKeyRequestDataType>
|
||||
) {
|
||||
await checkSmsKeyRequest(values, {
|
||||
onSuccess: onSubmit,
|
||||
onError: error => {
|
||||
if (error.status === 403) {
|
||||
onError(error);
|
||||
} else {
|
||||
setFormErrorMessage(error.data.smsKey);
|
||||
setErrors(error.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid='smskey-form'>
|
||||
<p className='my-4 text-center'>
|
||||
یک کد 6 رقمی برای شما ارسال شد، لطفا کد ارسال شده را وارد نمایید.
|
||||
</p>
|
||||
<Formik initialValues={{ mobile, smsKey: '' }} onSubmit={submit}>
|
||||
{({ dirty, isSubmitting }) => (
|
||||
<>
|
||||
{dirty && formErrorMessage && (
|
||||
<Alert content={formErrorMessage} className='mt-3 mb-5' />
|
||||
)}
|
||||
|
||||
{/* form */}
|
||||
<Form noValidate>
|
||||
<Field id='username' name='mobile' component={FormInput} type='hidden' />
|
||||
<Field
|
||||
required
|
||||
name='smsKey'
|
||||
label='کد احراز هویت'
|
||||
placeholder='کد ارسال شده به شماره همراه'
|
||||
component={FormInput}
|
||||
type='tel'
|
||||
maxLength={6}
|
||||
validate={smsKey}
|
||||
/>
|
||||
<Button
|
||||
color='green'
|
||||
type='submit'
|
||||
disabled={!dirty || isSubmitting}
|
||||
content='تایید'
|
||||
className='my-2 mr-auto'
|
||||
/>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SmsKeyForm;
|
||||
Reference in New Issue
Block a user