Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into add/sync-package.js…
Browse files Browse the repository at this point in the history
…on-engines
  • Loading branch information
anomiex committed May 6, 2021
2 parents 53cc60c + 5a27b8a commit 3f857c7
Show file tree
Hide file tree
Showing 75 changed files with 3,938 additions and 1,174 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"@babel/preset-env": "7.12.13",
"@babel/preset-react": "7.12.10",
"@babel/register": "7.12.10",
"@wordpress/components": "9.2.6",
"@wordpress/eslint-plugin": "7.4.0",
"@wordpress/i18n": "3.9.0",
"babel-eslint": "10.1.0",
Expand Down
44 changes: 44 additions & 0 deletions projects/js-packages/connection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,50 @@ Connection Package

The package encapsulates the Connection functionality.

## Component `Main`
Contains the whole connection flow, including site registration and user authorization.

### Properties
- *authorizationUrl* - string, the authorization URL.
- *connectLabel* - string, the "Connect" button label.
- *inPlaceTitle* - string, the title for the In-Place Connection component.
- *forceCalypsoFlow* - boolean, whether to go straight to Calypso flow, skipping the In-Place flow.
- *apiRoot* - string (required), API root URL.
- *apiNonce* - string (required), API Nonce.
- *registrationNonce* - string (required), registration nonce.
- *isRegistered* - boolean, whether the site is registered (has blog token).
- *isUserConnected* - boolean, whether the current user is connected (has user token).
- *hasConnectedOwner* - boolean, whether the site has connection owner.
- *onRegistered* - callback, to be called upon registration success.
- *onUserConnected* - callback, to be called when the connection is fully established.
- *redirectFunc* - callback, the redirect function (`window.location.assign()` by default).
- *from* - string, custom string parameter to identify where the request is coming from.
- *redirectUrl* - string, wp-admin URI so the user to get redirected there after Calypso connection flow.

### Usage
```jsx
import React, { useCallback } from 'react';
import { JetpackConnection } from '@automattic/jetpack-connection';

const onRegistered = useCallback( () => alert( 'Site registered' ) );
const onUserConnected = useCallback( () => alert( 'User Connected' ) );

<JetpackConnection
apiRoot="https://example.org/wp-json/"
apiNonce="12345"
registrationNonce="54321"
authorizationUrl="https://jetpack.wordpress.com/jetpack.authorize/1/?..."
isRegistered={ false }
isUserConnected={ false }
hasConnectedOwner={ false }
forceCalypsoFlow={ false }
onRegistered={ onRegistered }
onUserConnected={ onUserConnected }
from="connection-ui"
redirectUri="tools.php?page=wpcom-connection-manager"
/>
```

## Component `InPlaceConnection`
It includes:
- the `iframe` HTML element
Expand Down
4 changes: 4 additions & 0 deletions projects/js-packages/connection/changelog/add-rna-connection
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: major
Type: added

Add connection components.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ShallowRenderer from 'react-test-renderer/shallow';

/**
* Internal dependencies
Expand All @@ -20,15 +21,19 @@ describe( 'InPlaceConnection', () => {
};

describe( 'Loading state', () => {
const wrapper = shallow( <InPlaceConnection { ...testProps } isLoading={ true } /> );
const renderer = new ShallowRenderer();
renderer.render( <InPlaceConnection { ...testProps } isLoading={ true } /> );
const wrapper = shallow( renderer.getRenderOutput() );

it( 'renders a "loading..." message', () => {
expect( wrapper.find( 'p' ).text() ).to.be.equal( 'Loading…' );
} );
} );

describe( 'When the connect url is fetched', () => {
const wrapper = shallow( <InPlaceConnection { ...testProps } /> );
const renderer = new ShallowRenderer();
renderer.render( <InPlaceConnection { ...testProps } /> );
const wrapper = shallow( renderer.getRenderOutput() );

it( 'has a link to jetpack.wordpress.com', () => {
expect( wrapper.find( 'iframe' ).props().src ).to.be.equal(
Expand All @@ -46,7 +51,9 @@ describe( 'InPlaceConnection', () => {
} );

describe( 'Secondary user, add "tos" flag to URL', () => {
const wrapper = shallow( <InPlaceConnection { ...testProps } displayTOS={ true } /> );
const renderer = new ShallowRenderer();
renderer.render( <InPlaceConnection { ...testProps } displayTOS={ true } /> );
const wrapper = shallow( renderer.getRenderOutput() );

it( 'has a link to jetpack.wordpress.com', () => {
expect( wrapper.find( 'iframe' ).props().src ).to.be.contain( '&display-tos' );
Expand Down
189 changes: 189 additions & 0 deletions projects/js-packages/connection/components/main/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* External dependencies
*/
import React, { useEffect, useCallback, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import PropTypes from 'prop-types';

/**
* Internal dependencies
*/
import InPlaceConnection from '../in-place-connection';
import restApi from '../../tools/jetpack-rest-api-client';

/**
* The in-place connection component.
*
* @param {object} props -- The properties.
* @param {string} props.authorizationUrl -- The authorization URL.
* @param {string} props.connectLabel -- The "Connect" button label.
* @param {string} props.inPlaceTitle -- The title for the In-Place Connection component.
* @param {boolean} props.forceCalypsoFlow -- Whether to go straight to Calypso flow, skipping the In-Place flow.
* @param {string} props.apiRoot -- API root URL, required.
* @param {string} props.apiNonce -- API Nonce, required.
* @param {string} props.registrationNonce -- Separate registration nonce, required.
* @param {boolean} props.isRegistered -- Whether the site is registered (has blog token), required.
* @param {boolean} props.isUserConnected -- Whether the current user is connected (has user token), required.
* @param {boolean} props.hasConnectedOwner -- Whether the site has connection owner, required.
* @param {Function} props.onRegistered -- The callback to be called upon registration success.
* @param {Function} props.onUserConnected -- The callback to be called when the connection is fully established.
* @param {Function} props.redirectFunc -- The redirect function (`window.location.assign()` by default).
*
* @returns {React.Component} The in-place connection component.
*/
const Main = props => {
const [ isRegistering, setIsRegistering ] = useState( false );
const [ isUserConnecting, setIsUserConnecting ] = useState( false );

const {
apiRoot,
apiNonce,
connectLabel,
authorizationUrl,
forceCalypsoFlow,
isRegistered,
isUserConnected,
onRegistered,
onUserConnected,
registrationNonce,
redirectFunc,
from,
redirectUri,
} = props;

/**
* Initialize the REST API.
*/
useEffect( () => {
restApi.setApiRoot( apiRoot );
restApi.setApiNonce( apiNonce );
}, [ apiRoot, apiNonce ] );

/**
* Initialize the user connection process.
*/
const connectUser = useCallback(
url => {
url = url || authorizationUrl;

if ( ! url.includes( '?' ) ) {
url += '?';
}

if ( from ) {
url += '&from=' + encodeURIComponent( from );
}

if ( ! url ) {
throw new Error( 'Authorization URL is required' );
}

if ( forceCalypsoFlow ) {
redirectFunc( url );
return;
}

setIsUserConnecting( true );
},
[ authorizationUrl, forceCalypsoFlow, setIsUserConnecting, redirectFunc, from ]
);

/**
* Callback for the user connection success.
*/
const onUserConnectedCallback = useCallback( () => {
setIsUserConnecting( false );

if ( onUserConnected ) {
onUserConnected();
}
}, [ setIsUserConnecting, onUserConnected ] );

/**
* Initialize the site registration process.
*/
const registerSite = useCallback(
e => {
e && e.preventDefault();

if ( isRegistered ) {
connectUser();
return;
}

setIsRegistering( true );

restApi
.registerSite( registrationNonce, redirectUri )
.then( response => {
setIsRegistering( false );

if ( onRegistered ) {
onRegistered( response );
}

connectUser( response.authorizeUrl );
} )
.catch( error => {
throw error;
} );
},
[ setIsRegistering, isRegistered, onRegistered, connectUser, registrationNonce, redirectUri ]
);

if ( isRegistered && isUserConnected ) {
return null;
}

return (
<div className="jp-connection-main">
{ ! isUserConnecting && (
<Button
label={ connectLabel }
onClick={ registerSite }
isPrimary
disabled={ isRegistering || isUserConnecting }
>
{ connectLabel }
</Button>
) }

{ isUserConnecting && (
<InPlaceConnection
connectUrl={ authorizationUrl }
title={ props.inPlaceTitle }
onComplete={ onUserConnectedCallback }
displayTOS={ props.hasConnectedOwner || isRegistered }
/>
) }
</div>
);
};

Main.propTypes = {
authorizationUrl: PropTypes.string.isRequired,
connectLabel: PropTypes.string,
inPlaceTitle: PropTypes.string,
forceCalypsoFlow: PropTypes.bool,
apiRoot: PropTypes.string.isRequired,
apiNonce: PropTypes.string.isRequired,
isRegistered: PropTypes.bool.isRequired,
isUserConnected: PropTypes.bool.isRequired,
hasConnectedOwner: PropTypes.bool.isRequired,
onRegistered: PropTypes.func,
onUserConnected: PropTypes.func,
registrationNonce: PropTypes.string.isRequired,
redirectFunc: PropTypes.func,
from: PropTypes.string,
redirectUri: PropTypes.string,
};

Main.defaultProps = {
inPlaceTitle: __( 'Connect your WordPress.com account', 'jetpack' ),
forceCalypsoFlow: false,
connectLabel: __( 'Connect', 'jetpack' ),
redirectFunc: url => window.location.assign( url ),
};

export default Main;
75 changes: 75 additions & 0 deletions projects/js-packages/connection/components/main/test/component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* External dependencies
*/
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ShallowRenderer from 'react-test-renderer/shallow';

/**
* Internal dependencies
*/
import Main from '../index';

describe( 'Main', () => {
const testProps = {
apiNonce: 'test',
apiRoot: 'https://example.org/wp-json/',
authorizationUrl: 'https://jetpack.wordpress.com/jetpack.authorize/1/?response_type=code',
hasConnectedOwner: false,
isRegistered: false,
isUserConnected: false,
registrationNonce: 'test2',
};

describe( 'Render the Main component', () => {
const renderer = new ShallowRenderer();
renderer.render( <Main { ...testProps } /> );

const wrapper = shallow( renderer.getRenderOutput() );

it( 'component exists', () => {
expect( wrapper.find( 'Main' ) ).to.exist;
} );

const button = wrapper.find( 'ForwardRef(Button)' );

it( 'renders the register button', () => {
expect( button.text() ).to.be.equal( 'Connect' );
} );
} );

describe( 'Render the user connection - iframe', () => {
const renderer = new ShallowRenderer();
renderer.render( <Main { ...testProps } isRegistered={ true } /> );

shallow( renderer.getRenderOutput() ).find( 'ForwardRef(Button)' ).simulate( 'click' );
const wrapper = shallow( renderer.getRenderOutput() );

it( 'renders the InPlaceConnection', () => {
expect( wrapper.find( 'InPlaceConnection' ).props().connectUrl ).to.be.equal(
testProps.authorizationUrl
);
} );
} );

describe( 'Render the user connection - calypso', () => {
let redirectUrl = null;
const redirectFunc = url => ( redirectUrl = url );

const renderer = new ShallowRenderer();
renderer.render(
<Main
{ ...testProps }
isRegistered={ true }
forceCalypsoFlow={ true }
redirectFunc={ redirectFunc } // eslint-disable-line react/jsx-no-bind
/>
);
shallow( renderer.getRenderOutput() ).find( 'ForwardRef(Button)' ).simulate( 'click' );

it( 'the redirect happened', () => {
expect( redirectUrl ).to.be.equal( testProps.authorizationUrl );
} );
} );
} );
1 change: 1 addition & 0 deletions projects/js-packages/connection/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Components.
*/
export { default as JetpackConnection } from './components/main';
export { default as InPlaceConnection } from './components/in-place-connection';

/**
Expand Down
Loading

0 comments on commit 3f857c7

Please sign in to comment.