Add registration & verification tests

next-interactions
Justin 2 years ago
parent ed47cf5f09
commit dcc90bc99a

@ -0,0 +1,100 @@
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { __stub } from '../../../__mocks__/api';
import { render, screen } from '../../../jest/test-helpers';
import Verification from '../index';
const TestableComponent = () => (
<Switch>
<Route path='/auth/verify' exact><Verification /></Route>
<Route path='/' exact><span data-testid='home'>Homepage</span></Route>
</Switch>
);
const renderComponent = (store) => render(
<TestableComponent />,
{},
store,
{ initialEntries: ['/auth/verify'] },
);
describe('<Verification />', () => {
let store;
beforeEach(() => {
store = {
verification: ImmutableMap({
instance: {
isReady: true,
registrations: true,
},
isComplete: false,
}),
};
__stub(mock => {
mock.onGet('/api/v1/pepe/instance')
.reply(200, {
age_minimum: 18,
approval_required: true,
challenges: ['age', 'email', 'sms'],
});
mock.onPost('/api/v1/pepe/registrations')
.reply(200, {
access_token: 'N-dZmNqNSmTutJLsGjZ5AnJL4sLw_y-N3pn2acSqJY8',
});
});
});
describe('When registration is closed', () => {
it('successfully redirects to the homepage', () => {
const verification = store.verification.setIn(['instance', 'registrations'], false);
store.verification = verification;
renderComponent(store);
expect(screen.getByTestId('home')).toHaveTextContent('Homepage');
});
});
describe('When verification is complete', () => {
it('successfully renders the Registration component', () => {
const verification = store.verification.set('isComplete', true);
store.verification = verification;
renderComponent(store);
expect(screen.getByRole('heading')).toHaveTextContent('Register your account');
});
});
describe('Switching verification steps', () => {
it('successfully renders the Birthday step', () => {
const verification = store.verification.set('currentChallenge', 'age');
store.verification = verification;
renderComponent(store);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your birth date');
});
it('successfully renders the Email step', () => {
const verification = store.verification.set('currentChallenge', 'email');
store.verification = verification;
renderComponent(store);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your email address');
});
it('successfully renders the SMS step', () => {
const verification = store.verification.set('currentChallenge', 'sms');
store.verification = verification;
renderComponent(store);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your phone number');
});
});
});

@ -1,50 +0,0 @@
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { fetchVerificationConfig } from 'soapbox/actions/verification';
import Registration from './registration';
import AgeVerification from './steps/age_verification';
import EmailVerification from './steps/email_verification';
import SmsVerification from './steps/sms_verification';
const verificationSteps = {
email: EmailVerification,
sms: SmsVerification,
age: AgeVerification,
};
const Verification = () => {
const dispatch = useDispatch();
const isInstanceReady = useSelector((state) => state.getIn(['verification', 'instance', 'isReady'], false) === true);
const isRegistrationOpen = useSelector(state => state.getIn(['verification', 'instance', 'registrations'], false) === true);
const currentChallenge = useSelector((state) => state.getIn(['verification', 'currentChallenge']));
const isVerificationComplete = useSelector((state) => state.getIn(['verification', 'isComplete']));
const StepToRender = verificationSteps[currentChallenge];
React.useEffect(() => {
dispatch(fetchVerificationConfig());
}, []);
if (isInstanceReady && !isRegistrationOpen) {
return <Redirect to='/' />;
}
if (isVerificationComplete) {
return (
<Registration />
);
}
if (!currentChallenge) {
return null;
}
return (
<StepToRender />
);
};
export default Verification;

@ -0,0 +1,58 @@
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { fetchVerificationConfig } from 'soapbox/actions/verification';
import { useAppSelector } from 'soapbox/hooks';
import Registration from './registration';
import AgeVerification from './steps/age-verification';
import EmailVerification from './steps/email-verification';
import SmsVerification from './steps/sms-verification';
// eslint-disable-next-line no-unused-vars
enum ChallengeTypes {
EMAIL = 'email', // eslint-disable-line no-unused-vars
SMS = 'sms', // eslint-disable-line no-unused-vars
AGE = 'age', // eslint-disable-line no-unused-vars
}
const verificationSteps = {
email: EmailVerification,
sms: SmsVerification,
age: AgeVerification,
};
const Verification = () => {
const dispatch = useDispatch();
const isInstanceReady = useAppSelector((state) => state.verification.getIn(['instance', 'isReady'], false) === true);
const isRegistrationOpen = useAppSelector(state => state.verification.getIn(['instance', 'registrations'], false) === true);
const currentChallenge = useAppSelector((state) => state.verification.getIn(['currentChallenge']) as ChallengeTypes);
const isVerificationComplete = useAppSelector((state) => state.verification.get('isComplete'));
const StepToRender = verificationSteps[currentChallenge];
React.useEffect(() => {
dispatch(fetchVerificationConfig());
}, []);
if (isInstanceReady && !isRegistrationOpen) {
return <Redirect to='/' />;
}
if (isVerificationComplete) {
return (
<Registration />
);
}
if (!currentChallenge) {
return null;
}
return (
<StepToRender />
);
};
export default Verification;

@ -20,7 +20,7 @@ const Registration = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const intl = useIntl(); const intl = useIntl();
const isLoading = useSelector((state) => state.getIn(['verification', 'isLoading'])); const isLoading = useSelector((state) => state.verification.get('isLoading'));
const siteTitle = useSelector((state) => state.instance.title); const siteTitle = useSelector((state) => state.instance.title);
const [state, setState] = React.useState(initialState); const [state, setState] = React.useState(initialState);
@ -89,7 +89,7 @@ const Registration = () => {
</div> </div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto space-y-4'> <div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto space-y-4'>
<Form onSubmit={handleSubmit} disabled={isLoading}> <Form onSubmit={handleSubmit}>
<FormGroup labelText='Your username'> <FormGroup labelText='Your username'>
<Input <Input
name='username' name='username'

@ -0,0 +1,51 @@
import userEvent from '@testing-library/user-event';
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import '@testing-library/jest-dom';
import { __stub } from 'soapbox/api';
import { fireEvent, render, screen } from 'soapbox/jest/test-helpers';
import AgeVerification from '../age-verification';
describe('<AgeVerification />', () => {
let store;
beforeEach(() => {
store = {
verification: ImmutableMap({
ageMinimum: 13,
}),
};
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_age/confirm')
.reply(200, {});
});
});
it('successfully renders the Birthday step', async() => {
render(
<AgeVerification />,
{},
store,
);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your birth date');
});
it('selects a date', async() => {
render(
<AgeVerification />,
{},
store,
);
await userEvent.type(screen.getByLabelText('Birth Date'), '{enter}');
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
});

@ -0,0 +1,67 @@
import userEvent from '@testing-library/user-event';
import React from 'react';
import '@testing-library/jest-dom';
import { __stub } from 'soapbox/api';
import { fireEvent, render, screen, waitFor } from 'soapbox/jest/test-helpers';
import EmailVerification from '../email-verification';
describe('<EmailVerification />', () => {
it('successfully renders the Email step', async() => {
render(<EmailVerification />);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your email address');
});
describe('with valid data', () => {
beforeEach(() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_email/request')
.reply(200, {});
});
});
it('successfully submits', async() => {
render(<EmailVerification />);
await userEvent.type(screen.getByLabelText('Email Address'), 'foo@bar.com{enter}');
await waitFor(() => {
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
expect(screen.getByRole('button')).toHaveTextContent('Resend verification email');
});
});
describe('with invalid data', () => {
beforeEach(() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_email/request')
.reply(422, {
error: 'email_taken',
});
});
});
it('renders errors', async() => {
render(<EmailVerification />);
await userEvent.type(screen.getByLabelText('Email Address'), 'foo@bar.com{enter}');
await waitFor(() => {
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
expect(screen.getByTestId('form-group-error')).toHaveTextContent('is taken');
});
});
});

@ -0,0 +1,103 @@
import userEvent from '@testing-library/user-event';
import React from 'react';
import '@testing-library/jest-dom';
import { __stub } from 'soapbox/api';
import { fireEvent, render, screen, waitFor } from 'soapbox/jest/test-helpers';
import SmsVerification from '../sms-verification';
describe('<SmsVerification />', () => {
it('successfully renders the SMS step', async() => {
render(<SmsVerification />);
expect(screen.getByRole('heading')).toHaveTextContent('Enter your phone number');
});
describe('with valid data', () => {
beforeEach(() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_sms/request').reply(200, {});
});
});
it('successfully submits', async() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_sms/confirm').reply(200, {});
});
render(<SmsVerification />);
await userEvent.type(screen.getByLabelText('Phone Number'), '+1 (555) 555-5555');
await waitFor(() => {
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1');
await userEvent.type(screen.getByLabelText('Digit 2'), '2');
await userEvent.type(screen.getByLabelText('Digit 3'), '3');
await userEvent.type(screen.getByLabelText('Digit 4'), '4');
await userEvent.type(screen.getByLabelText('Digit 5'), '5');
await userEvent.type(screen.getByLabelText('Digit 6'), '6');
});
it('handle expired tokens', async() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_sms/confirm').reply(422, {});
});
render(<SmsVerification />);
await userEvent.type(screen.getByLabelText('Phone Number'), '+1 (555) 555-5555');
await waitFor(() => {
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1');
await userEvent.type(screen.getByLabelText('Digit 2'), '2');
await userEvent.type(screen.getByLabelText('Digit 3'), '3');
await userEvent.type(screen.getByLabelText('Digit 4'), '4');
await userEvent.type(screen.getByLabelText('Digit 5'), '5');
await userEvent.type(screen.getByLabelText('Digit 6'), '6');
expect(screen.getByTestId('toast')).toHaveTextContent('Your SMS token has expired.');
});
});
describe('with invalid data', () => {
beforeEach(() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/verify_sms/request')
.reply(422, {});
});
});
it('renders errors', async() => {
render(<SmsVerification />);
await userEvent.type(screen.getByLabelText('Phone Number'), '+1 (555) 555-5555');
await waitFor(() => {
fireEvent.submit(
screen.getByRole('button'), {
preventDefault: () => {},
},
);
});
expect(screen.getByTestId('toast')).toHaveTextContent('Failed to send SMS message to your phone number.');
});
});
});

@ -28,9 +28,9 @@ const AgeVerification = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useDispatch(); const dispatch = useDispatch();
const isLoading = useSelector((state) => state.getIn(['verification', 'isLoading'])); const isLoading = useSelector((state) => state.verification.get('isLoading'));
const ageMinimum = useSelector((state) => state.getIn(['verification', 'ageMinimum'])); const ageMinimum = useSelector((state) => state.verification.get('ageMinimum'));
const siteTitle = useSelector((state) => state.instance.title); const siteTitle = useSelector((state) => state.instance.get('title'));
const [date, setDate] = React.useState(''); const [date, setDate] = React.useState('');
const isValid = typeof date === 'object'; const isValid = typeof date === 'object';
@ -65,7 +65,7 @@ const AgeVerification = () => {
</div> </div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'> <div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={handleSubmit} disabled={isLoading}> <Form onSubmit={handleSubmit}>
<FormGroup labelText='Birth Date'> <FormGroup labelText='Birth Date'>
<DatePicker <DatePicker
selected={date} selected={date}

@ -50,7 +50,7 @@ const EmailVerification = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useDispatch(); const dispatch = useDispatch();
const isLoading = useSelector((state) => state.getIn(['verification', 'isLoading'])); const isLoading = useSelector((state) => state.verification.get('isLoading'));
const [email, setEmail] = React.useState(''); const [email, setEmail] = React.useState('');
const [status, setStatus] = React.useState(Statuses.IDLE); const [status, setStatus] = React.useState(Statuses.IDLE);
@ -110,7 +110,7 @@ const EmailVerification = () => {
</div> </div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'> <div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={handleSubmit} disabled={isLoading}> <Form onSubmit={handleSubmit}>
<FormGroup labelText='Email Address' errors={errors}> <FormGroup labelText='Email Address' errors={errors}>
<Input <Input
type='email' type='email'

@ -21,7 +21,7 @@ const SmsVerification = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useDispatch(); const dispatch = useDispatch();
const isLoading = useSelector((state) => state.getIn(['verification', 'isLoading'])); const isLoading = useSelector((state) => state.verification.get('isLoading'));
const [phone, setPhone] = React.useState(''); const [phone, setPhone] = React.useState('');
const [status, setStatus] = React.useState(Statuses.IDLE); const [status, setStatus] = React.useState(Statuses.IDLE);
@ -147,7 +147,7 @@ const SmsVerification = () => {
</div> </div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'> <div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={handleSubmit} disabled={isLoading}> <Form onSubmit={handleSubmit}>
<FormGroup labelText='Phone Number'> <FormGroup labelText='Phone Number'>
<Input <Input
type='text' type='text'
Loading…
Cancel
Save