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

Auth: Run codemods; use enzyme, remove mixin injection from tests #18472

Merged
merged 11 commits into from
Oct 5, 2017
127 changes: 60 additions & 67 deletions client/auth/login.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/** @format */
/**
* External dependencies
*/
import ReactDom from 'react-dom';
import React from 'react';
import createReactClass from 'create-react-class';
import { localize } from 'i18n-calypso';
import LinkedStateMixin from 'react-addons-linked-state-mixin';
import Gridicon from 'gridicons';

Expand All @@ -22,43 +24,10 @@ import * as AuthActions from 'lib/oauth-store/actions';
import eventRecorder from 'me/event-recorder';
import WordPressLogo from 'components/wordpress-logo';
import AuthCodeButton from './auth-code-button';
import { addLocaleToWpcomUrl, getLocaleSlug } from 'lib/i18n-utils';
import SelfHostedInstructions from './self-hosted-instructions';
import LostPassword from './lost-password';

const LostPassword = React.createClass( {
render: function() {
const url = addLocaleToWpcomUrl( 'https://wordpress.com/wp-login.php?action=lostpassword', getLocaleSlug() );
return (
<p className="auth__lost-password">
<a href={ url } target="_blank" rel="noopener noreferrer">
{ this.translate( 'Lost your password?' ) }
</a>
</p>
);
}
} );

const SelfHostedInstructions = React.createClass( {

render: function() {
return (
<div className="auth__self-hosted-instructions">
<a href="#" onClick={ this.props.onClickClose } className="auth__self-hosted-instructions-close"><Gridicon icon="cross" size={ 24 } /></a>

<h2>{ this.translate( 'Add self-hosted site' ) }</h2>
<p>{ this.translate( 'By default when you sign into the WordPress.com app, you can edit blogs and sites hosted at WordPress.com' ) }</p>
<p>{ this.translate( 'If you\'d like to edit your self-hosted WordPress blog or site, you can do that by following these instructions:' ) }</p>

<ol>
<li><strong>{ this.translate( 'Install the Jetpack plugin.' ) }</strong><br /><a href="http://jetpack.me/install/">{ this.translate( 'Please follow these instructions to install Jetpack' ) }</a>.</li>
<li>{ this.translate( 'Connect Jetpack to WordPress.com.' ) }</li>
<li>{ this.translate( 'Now you can sign in to the app using the WordPress.com account Jetpack is connected to, and you can find your self-hosted site under the "My Sites" section.' ) }</li>
</ol>
</div>
);
}
} );

module.exports = React.createClass( {
export const Login = createReactClass( {
displayName: 'Auth',

mixins: [ LinkedStateMixin, eventRecorder ],
Expand All @@ -75,18 +44,21 @@ module.exports = React.createClass( {
this.setState( AuthStore.get() );
},

componentDidUpdate() {
focusInput( input ) {
if ( this.state.requires2fa && this.state.inProgress === false ) {
ReactDom.findDOMNode( this.refs.auth_code ).focus();
input.focus();
}
},

getInitialState: function() {
return Object.assign( {
login: '',
password: '',
auth_code: ''
}, AuthStore.get() );
return Object.assign(
{
login: '',
password: '',
auth_code: '',
},
AuthStore.get()
);
},

submitForm: function( event ) {
Expand Down Expand Up @@ -119,12 +91,13 @@ module.exports = React.createClass( {
return this.hasLoginDetails();
},

toggleSelfHostedInstructions: function () {
var isShowing = !this.state.showInstructions;
toggleSelfHostedInstructions: function() {
const isShowing = ! this.state.showInstructions;
this.setState( { showInstructions: isShowing } );
},

render: function() {
const { translate } = this.props;
const { requires2fa, inProgress, errorMessage, errorLevel, showInstructions } = this.state;

return (
Expand All @@ -134,59 +107,79 @@ module.exports = React.createClass( {
<form className="auth__form" onSubmit={ this.submitForm }>
<FormFieldset>
<div className="auth__input-wrapper">
<Gridicon icon="user"/>
<Gridicon icon="user" />
<FormTextInput
name="login"
ref="login"
disabled={ requires2fa || inProgress }
placeholder={ this.translate( 'Username or email address' ) }
placeholder={ translate( 'Username or email address' ) }
onFocus={ this.recordFocusEvent( 'Username or email address' ) }
valueLink={ this.linkState( 'login' ) } />
valueLink={ this.linkState( 'login' ) }
/>
</div>
<div className="auth__input-wrapper">
<Gridicon icon="lock" />
<FormPasswordInput
name="password"
ref="password"
disabled={ requires2fa || inProgress }
placeholder={ this.translate( 'Password' ) }
placeholder={ translate( 'Password' ) }
onFocus={ this.recordFocusEvent( 'Password' ) }
hideToggle={ requires2fa }
submitting={ inProgress }
valueLink={ this.linkState( 'password' ) } />
valueLink={ this.linkState( 'password' ) }
/>
</div>
{ requires2fa &&
{ requires2fa && (
<FormFieldset>
<FormTextInput
name="auth_code"
type="number"
ref="auth_code"
ref={ this.focusInput }
disabled={ inProgress }
placeholder={ this.translate( 'Verification code' ) }
placeholder={ translate( 'Verification code' ) }
onFocus={ this.recordFocusEvent( 'Verification code' ) }
valueLink={ this.linkState( 'auth_code' ) } />
valueLink={ this.linkState( 'auth_code' ) }
/>
</FormFieldset>
}
) }
</FormFieldset>
<FormButtonsBar>
<FormButton disabled={ ! this.canSubmitForm() } onClick={ this.recordClickEvent( 'Sign in' ) } >
{ requires2fa ? this.translate( 'Verify' ) : this.translate( 'Sign in' ) }
<FormButton
disabled={ ! this.canSubmitForm() }
onClick={ this.recordClickEvent( 'Sign in' ) }
>
{ requires2fa ? translate( 'Verify' ) : translate( 'Sign in' ) }
</FormButton>
</FormButtonsBar>
{ ! requires2fa && <LostPassword /> }
{ errorMessage && <Notice text={ errorMessage } status={ errorLevel } showDismiss={ false } /> }
{ requires2fa && <AuthCodeButton username={ this.state.login } password={ this.state.password } /> }
{ errorMessage && (
<Notice text={ errorMessage } status={ errorLevel } showDismiss={ false } />
) }
{ requires2fa && (
<AuthCodeButton username={ this.state.login } password={ this.state.password } />
) }
</form>
<a className="auth__help" target="_blank" rel="noopener noreferrer" title={ this.translate( 'Visit the WordPress.com support site for help' ) } href="https://en.support.wordpress.com/">
<a
className="auth__help"
target="_blank"
rel="noopener noreferrer"
title={ translate( 'Visit the WordPress.com support site for help' ) }
href="https://en.support.wordpress.com/"
>
<Gridicon icon="help" />
</a>
<div className="auth__links">
<a href="#" onClick={ this.toggleSelfHostedInstructions }>{ this.translate( 'Add self-hosted site' ) }</a>
<a href={ config( 'signup_url' ) }>{ this.translate( 'Create account' ) }</a>
<a href="#" onClick={ this.toggleSelfHostedInstructions }>
{ translate( 'Add self-hosted site' ) }
</a>
<a href={ config( 'signup_url' ) }>{ translate( 'Create account' ) }</a>
</div>
{ showInstructions && <SelfHostedInstructions onClickClose={ this.toggleSelfHostedInstructions } /> }
{ showInstructions && (
<SelfHostedInstructions onClickClose={ this.toggleSelfHostedInstructions } />
) }
</div>
</Main>
);
}
},
} );

export default localize( Login );
26 changes: 26 additions & 0 deletions client/auth/lost-password.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* External dependencies
*/
import React from 'react';
import { localize } from 'i18n-calypso';

/**
* Internal dependencies
*/
import { addLocaleToWpcomUrl, getLocaleSlug } from 'lib/i18n-utils';

const LostPassword = ( { translate } ) => {
const url = addLocaleToWpcomUrl(
'https://wordpress.com/wp-login.php?action=lostpassword',
getLocaleSlug()
);
return (
<p className="auth__lost-password">
<a href={ url } target="_blank" rel="noopener noreferrer">
{ translate( 'Lost your password?' ) }
</a>
</p>
);
};

export default localize( LostPassword );
45 changes: 45 additions & 0 deletions client/auth/self-hosted-instructions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* External dependencies
*/
import React from 'react';
import { localize } from 'i18n-calypso';
import Gridicon from 'gridicons';

const SelfHostedInstructions = ( { onClickClose, translate } ) => (
<div className="auth__self-hosted-instructions">
<a href="#" onClick={ onClickClose } className="auth__self-hosted-instructions-close">
<Gridicon icon="cross" size={ 24 } />
</a>

<h2>{ translate( 'Add self-hosted site' ) }</h2>
<p>
{ translate(
'By default when you sign into the WordPress.com app, you can edit blogs and sites hosted at WordPress.com'
) }
</p>
<p>
{ translate(
"If you'd like to edit your self-hosted WordPress blog or site, you can do that by following these instructions:"
) }
</p>

<ol>
<li>
<strong>{ translate( 'Install the Jetpack plugin.' ) }</strong>
<br />
<a href="http://jetpack.me/install/">
{ translate( 'Please follow these instructions to install Jetpack' ) }
</a>.
</li>
<li>{ translate( 'Connect Jetpack to WordPress.com.' ) }</li>
<li>
{ translate(
'Now you can sign in to the app using the WordPress.com account Jetpack is connected to, ' +
'and you can find your self-hosted site under the "My Sites" section.'
) }
</li>
</ol>
</div>
);

export default localize( SelfHostedInstructions );
37 changes: 13 additions & 24 deletions client/auth/test/login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
/**
* External dependencies
*/
import React from 'react';
import { expect } from 'chai';
import { identity } from 'lodash';
import { shallow } from 'enzyme';
import { identity, noop } from 'lodash';

/**
* Internal dependencies
*/
import { login as loginStub } from 'lib/oauth-store/actions';
import { Login } from '../login.jsx';
import FormButton from 'components/forms/form-button';

jest.mock( 'lib/oauth-store/actions', () => ( {
login: require( 'sinon' ).stub(),
Expand All @@ -24,56 +28,41 @@ jest.mock( 'lib/analytics', () => ( {
} ) );

describe( 'LoginTest', function() {
let Login, page, React, ReactDom, ReactClass, TestUtils;

before( () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We finally are getting rid of those require statements in before block :)

React = require( 'react' );
ReactDom = require( 'react-dom' );
ReactClass = require( 'react/lib/ReactClass' );
TestUtils = require( 'react-addons-test-utils' );
ReactClass.injection.injectMixin( { translate: identity } );
Login = require( '../login.jsx' );

const container = document.createElement( 'div' );
page = ReactDom.render( <Login />, container );
} );
const page = shallow( <Login translate={ identity } /> );

it( 'OTP is not present on first render', function( done ) {
page.setState( { requires2fa: false }, function() {
expect( page.refs.auth_code ).to.be.undefined;
expect( page.find( { name: 'auth_code' } ) ).to.have.length( 0 );
done();
} );
} );

it( 'cannot submit until login details entered', function( done ) {
const submit = TestUtils.findRenderedDOMComponentWithTag( page, 'button' );

expect( page.find( FormButton ).props().disabled ).to.be.true;
page.setState( { login: 'test', password: 'test', inProgress: false }, function() {
expect( submit.disabled ).to.be.false;
expect( page.find( FormButton ).props().disabled ).to.be.false;
done();
} );
} );

it( 'shows OTP box with valid login', function( done ) {
page.setState( { login: 'test', password: 'test', requires2fa: true }, function() {
expect( page.refs.auth_code ).to.not.be.undefined;
expect( page.find( { name: 'auth_code' } ) ).to.have.length( 1 );
done();
} );
} );

it( 'prevents change of login when asking for OTP', function( done ) {
page.setState( { login: 'test', password: 'test', requires2fa: true }, function() {
expect( page.refs.login.props.disabled ).to.be.true;
expect( page.refs.password.props.disabled ).to.be.true;
expect( page.find( { name: 'login' } ).props().disabled ).to.be.true;
expect( page.find( { name: 'password' } ).props().disabled ).to.be.true;
done();
} );
} );

it( 'submits login form', function( done ) {
const submit = TestUtils.findRenderedDOMComponentWithTag( page, 'form' );

page.setState( { login: 'user', password: 'pass', auth_code: 'otp' }, function() {
TestUtils.Simulate.submit( submit );
page.find( 'form' ).simulate( 'submit', { preventDefault: noop, stopPropagation: noop } );

expect( loginStub ).to.have.been.calledOnce;
expect( loginStub.calledWith( 'user', 'pass', 'otp' ) ).to.be.true;
Expand Down
Loading