diff --git a/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx b/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx index eb78c379f8..5f58dd09f0 100644 --- a/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx +++ b/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx @@ -19,7 +19,7 @@ interface State { honeyPotValue: boolean; } -const messages = defineMessages({ +export const messages = defineMessages({ lengthLimit: { id: 'anonymousLoginForm.length-limit', defaultMessage: 'Username must be at least 3 characters long', diff --git a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx index 97cddf638a..73bdbce005 100644 --- a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx +++ b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx @@ -1,53 +1,52 @@ /** @jsx createElement */ import { createElement } from 'preact'; -import { mount } from 'enzyme'; -import { EmailLoginForm, Props, State } from './auth-panel__email-login-form'; +import { mount, ReactWrapper } from 'enzyme'; +import { EmailLoginFormConnected as EmailLoginForm, Props, State } from './auth-panel__email-login-form'; import { User } from '@app/common/types'; import { sleep } from '@app/utils/sleep'; import { validToken } from '@app/testUtils/mocks/jwt'; -import { createIntl } from 'react-intl'; +import { sendEmailVerificationRequest } from '@app/common/api'; +import { IntlProvider } from 'react-intl'; import enMessages from '../../../locales/en.json'; jest.mock('@app/utils/jwt', () => ({ isJwtExpired: jest .fn() + .mockImplementationOnce(() => true) .mockImplementationOnce(() => false) .mockImplementationOnce(() => true), })); -const intl = createIntl({ - locale: `en`, - messages: enMessages, -}); +jest.mock('@app/common/api'); + +function simulateInput(input: ReactWrapper, value: string) { + input.getDOMNode().value = value; + input.simulate('input'); +} describe('EmailLoginForm', () => { const testUser = ({} as any) as User; const onSuccess = jest.fn(async () => {}); const onSignIn = jest.fn(async () => testUser); - it('works', async () => { - const sendEmailVerification = jest.fn(async () => {}); + beforeEach(() => { + (sendEmailVerificationRequest as any).mockReset(); + }); + it('works', async () => { + (sendEmailVerificationRequest as any).mockResolvedValueOnce({}); const el = mount( - + + + ); - - await new Promise(resolve => - el.setState({ usernameValue: 'someone', addressValue: 'someone@example.com' } as State, resolve) - ); - + simulateInput(el.find(`input[name="email"]`), 'someone@example.com'); + simulateInput(el.find(`input[name="username"]`), 'someone'); el.find('form').simulate('submit'); await sleep(100); - expect(sendEmailVerification).toBeCalledWith('someone', 'someone@example.com'); - expect(el.state().verificationSent).toBe(true); - - await new Promise(resolve => el.setState({ tokenValue: 'abcd' } as State, resolve)); + expect(sendEmailVerificationRequest).toBeCalledWith('someone', 'someone@example.com'); + el.update(); + simulateInput(el.find(`textarea[name="token"]`), 'abcd'); el.find('form').simulate('submit'); await sleep(100); @@ -56,47 +55,36 @@ describe('EmailLoginForm', () => { }); it('should send form by pasting token', async () => { - const sendEmailVerification = jest.fn(async () => {}); + (sendEmailVerificationRequest as any).mockResolvedValueOnce({}); const onSignIn = jest.fn(async () => testUser); const wrapper = mount( - - ); - await new Promise(resolve => - wrapper.setState({ usernameValue: 'someone', addressValue: 'someone@example.com' } as State, resolve) + + + ); + simulateInput(wrapper.find(`input[name="email"]`), 'someone@example.com'); + simulateInput(wrapper.find(`input[name="username"]`), 'someone'); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); - - wrapper.find('textarea').getDOMNode().value = validToken; - wrapper.find('textarea').simulate('input'); - + simulateInput(wrapper.find(`textarea[name="token"]`), validToken); + await sleep(100); + wrapper.update(); expect(onSignIn).toBeCalledWith(validToken); }); it('should show error "Token is expired" on paste', async () => { - const sendEmailVerification = jest.fn(async () => {}); + (sendEmailVerificationRequest as any).mockResolvedValueOnce({}); const onSignIn = jest.fn(async () => testUser); const wrapper = mount( - - ); - await new Promise(resolve => - wrapper.setState({ usernameValue: 'someone', addressValue: 'someone@example.com' } as State, resolve) + + + ); + simulateInput(wrapper.find(`input[name="email"]`), 'someone@example.com'); + simulateInput(wrapper.find(`input[name="username"]`), 'someone'); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); diff --git a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx index d62a34df74..f6a1d0e79d 100644 --- a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx +++ b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx @@ -2,7 +2,6 @@ import { createElement, Component, createRef } from 'preact'; import { forwardRef } from 'preact/compat'; import b from 'bem-react-helper'; -import { useSelector } from 'react-redux'; import { Theme, User } from '@app/common/types'; import { sendEmailVerificationRequest } from '@app/common/api'; import { extractErrorMessageFromResponse } from '@app/utils/errorUtils'; @@ -12,11 +11,9 @@ import TextareaAutosize from '@app/components/comment-form/textarea-autosize'; import { Input } from '@app/components/input'; import { Button } from '@app/components/button'; import { isJwtExpired } from '@app/utils/jwt'; -import { IntlShape, useIntl } from 'react-intl'; +import { defineMessages, IntlShape, useIntl, FormattedMessage } from 'react-intl'; -const mapStateToProps = () => ({ - sendEmailVerification: sendEmailVerificationRequest, -}); +import { messages as loginForm } from '../__anonymous-login-form/auth-panel__anonymous-login-form'; interface OwnProps { onSignIn(token: string): Promise; @@ -25,7 +22,7 @@ interface OwnProps { className?: string; } -export type Props = OwnProps & ReturnType & { intl: IntlShape }; +export type Props = OwnProps & { intl: IntlShape; sendEmailVerification: typeof sendEmailVerificationRequest }; export interface State { usernameValue: string; @@ -36,6 +33,37 @@ export interface State { error: string | null; } +const messages = defineMessages({ + expiredToken: { + id: 'emailLoginForm.expiredToken', + defaultMessage: 'Token is expired', + }, + userNotFound: { + id: 'emailLoginForm.user-not-found', + defaultMessage: 'No user was found', + }, + loading: { + id: 'emailLoginForm.loading', + defaultMessage: 'Loading...', + }, + invalidEmail: { + id: 'emailLoginForm.invalid-email', + defaultMessage: 'Address should be valid email address', + }, + emptyToken: { + id: 'emailLoginForm.empty-token', + defaultMessage: 'Token field must not be empty', + }, + emailAddress: { + id: 'emailLoginForm.email-address', + defaultMessage: 'Email Address', + }, + token: { + id: 'emailLoginForm.token', + defaultMessage: 'Token', + }, +}); + export class EmailLoginForm extends Component { static usernameRegex = /^[a-zA-Z][\w ]+$/; static emailRegex = /[^@]+@[^.]+\..+/; @@ -78,15 +106,18 @@ export class EmailLoginForm extends Component { }; async sendForm(token: string = this.state.tokenValue) { + const intl = this.props.intl; try { this.setState({ loading: true }); const user = await this.props.onSignIn(token); if (!user) { - this.setState({ error: 'No user was found' }); + this.setState({ error: intl.formatMessage(messages.userNotFound) }); return; } this.setState({ verificationSent: false, tokenValue: '' }); - this.props.onSuccess && this.props.onSuccess(user); + if (this.props.onSuccess) { + await this.props.onSuccess(user); + } } catch (e) { this.setState({ error: extractErrorMessageFromResponse(e, this.props.intl) }); } finally { @@ -108,13 +139,14 @@ export class EmailLoginForm extends Component { }; onTokenChange = (e: Event) => { + const intl = this.props.intl; const { value } = e.target as HTMLInputElement; this.setState({ error: null, tokenValue: value }); try { if (value.length > 0 && isJwtExpired(value)) { - this.setState({ error: 'Token is expired' }); + this.setState({ error: intl.formatMessage(messages.expiredToken) }); return; } this.sendForm(value); @@ -141,22 +173,24 @@ export class EmailLoginForm extends Component { }; getForm1InvalidReason(): string | null { - if (this.state.loading) return 'Loading...'; + const intl = this.props.intl; + if (this.state.loading) return intl.formatMessage(messages.loading); const username = this.state.usernameValue; - if (username.length < 3) return 'Username must be at least 3 characters long'; - if (!EmailLoginForm.usernameRegex.test(username)) - return 'Username must start from the letter and contain only latin letters, numbers, underscores, and spaces'; - if (!EmailLoginForm.emailRegex.test(this.state.addressValue)) return 'Address should be valid email address'; + if (username.length < 3) return intl.formatMessage(loginForm.lengthLimit); + if (!EmailLoginForm.usernameRegex.test(username)) return intl.formatMessage(loginForm.symbolLimit); + if (!EmailLoginForm.emailRegex.test(this.state.addressValue)) return intl.formatMessage(messages.invalidEmail); return null; } getForm2InvalidReason(): string | null { - if (this.state.loading) return 'Loading...'; - if (this.state.tokenValue.length === 0) return 'Token field must not be empty'; + const intl = this.props.intl; + if (this.state.loading) return intl.formatMessage(messages.loading); + if (this.state.tokenValue.length === 0) return intl.formatMessage(messages.emptyToken); return null; } render(props: Props) { + const intl = props.intl; // TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname let className = b('auth-panel-email-login-form', {}, { theme: props.theme }); if (props.className) { @@ -170,16 +204,18 @@ export class EmailLoginForm extends Component {
@@ -192,7 +228,7 @@ export class EmailLoginForm extends Component { title={form1InvalidReason || ''} disabled={form1InvalidReason !== null} > - Send Verification + ); @@ -202,13 +238,14 @@ export class EmailLoginForm extends Component { return (
{ title={form2InvalidReason || ''} disabled={form2InvalidReason !== null} > - Confirm + ); @@ -233,7 +270,6 @@ export class EmailLoginForm extends Component { export type EmailLoginFormRef = EmailLoginForm; export const EmailLoginFormConnected = forwardRef((props, ref) => { - const connectedProps = useSelector(mapStateToProps); const intl = useIntl(); - return ; + return ; }); diff --git a/frontend/app/locales/en.json b/frontend/app/locales/en.json index 01fd59b7bb..3bb5a6cffb 100644 --- a/frontend/app/locales/en.json +++ b/frontend/app/locales/en.json @@ -75,6 +75,16 @@ "commentsSort.oldest": "Oldest", "commentsSort.recently-updated": "Recently updated", "commentsSort.worst": "Worst", + "emailLoginForm.back": "Back", + "emailLoginForm.confirm": "Confirm", + "emailLoginForm.email-address": "Email Address", + "emailLoginForm.empty-token": "Token field must not be empty", + "emailLoginForm.expiredToken": "Token is expired", + "emailLoginForm.invalid-email": "Address should be valid email address", + "emailLoginForm.loading": "Loading...", + "emailLoginForm.send-verification": "Send Verification", + "emailLoginForm.token": "Token", + "emailLoginForm.user-not-found": "No user was found", "errors.0": "Something went wrong. Please try again a bit later.", "errors.1": "Comment cannot be found. Please refresh the page and try again.", "errors.10": "It is too late to edit the comment.", diff --git a/frontend/app/locales/ru.json b/frontend/app/locales/ru.json index 891bbf7ab8..77c39dd7d1 100644 --- a/frontend/app/locales/ru.json +++ b/frontend/app/locales/ru.json @@ -75,6 +75,16 @@ "commentsSort.oldest": "Старые", "commentsSort.recently-updated": "Недавно обновленные", "commentsSort.worst": "Худшие", + "emailLoginForm.back": "Back", + "emailLoginForm.confirm": "Confirm", + "emailLoginForm.email-address": "Email Address", + "emailLoginForm.empty-token": "Token field must not be empty", + "emailLoginForm.expiredToken": "Token is expired", + "emailLoginForm.invalid-email": "Address should be valid email address", + "emailLoginForm.loading": "Loading...", + "emailLoginForm.send-verification": "Send Verification", + "emailLoginForm.token": "Token", + "emailLoginForm.user-not-found": "No user was found", "errors.0": "Something went wrong. Please try again a bit later.", "errors.1": "Comment cannot be found. Please refresh the page and try again.", "errors.10": "It is too late to edit the comment.",