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

Update wizard UI and improve e2e tests #4980

Merged
merged 29 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c0907e6
Remove close button
johnwatkins0 Jul 2, 2020
1bb45cd
Improve e2e tests and fix bugs found
johnwatkins0 Jul 2, 2020
8e5f742
Improve e2e tests and bug fix
johnwatkins0 Jul 2, 2020
bb518c3
Harden onboarding wizard e2e tests
johnwatkins0 Jul 2, 2020
01c76ec
(#4972) Update done screen design
johnwatkins0 Jul 3, 2020
14672ae
Update e2e util function name
johnwatkins0 Jul 3, 2020
52f742c
Override reader mode selection when reader theme matches active theme
johnwatkins0 Jul 3, 2020
b02822d
Update transitional override text
johnwatkins0 Jul 3, 2020
a1087df
Jest: Provide CURRENT_THEME mock
johnwatkins0 Jul 3, 2020
4b3b0da
Fix customize button text
johnwatkins0 Jul 3, 2020
716309c
Remove unnecessary change to existing test
johnwatkins0 Jul 3, 2020
0b1e112
Add tests for case where active theme is not a reader theme
johnwatkins0 Jul 3, 2020
42de59c
Comment typo fix
johnwatkins0 Jul 5, 2020
9a84460
Break up long import statement into multiple lines
johnwatkins0 Jul 5, 2020
6cd0600
Determine is_reader_theme in PHP instead of JS
johnwatkins0 Jul 5, 2020
3595465
Merge branch 'feature/4974-remove-close-on-finish-screen' of https://…
johnwatkins0 Jul 5, 2020
b2661a8
Update Browse/Customize AMP text and placement on done screen
johnwatkins0 Jul 5, 2020
1673aa0
Change currentThemeIsReaderTheme to currentThemeIsAmongReaderThemes
johnwatkins0 Jul 5, 2020
b25157f
Fix lint issues
johnwatkins0 Jul 5, 2020
bd9c505
Merge remote-tracking branch 'origin/develop' into feature/4974-remov…
johnwatkins0 Jul 6, 2020
d7dd247
Prevent wpfooter from preventing clicks
westonruter Jul 6, 2020
7f997eb
Persist default mobile_redirect setting when it is not interacted with
johnwatkins0 Jul 6, 2020
11d6d6b
Merge branch 'feature/4974-remove-close-on-finish-screen' of https://…
johnwatkins0 Jul 6, 2020
436a87d
Onboarding wizard close link: return to previous location
johnwatkins0 Jul 6, 2020
0062c44
Fix Jest tests
johnwatkins0 Jul 6, 2020
fdfa19b
Recommend transitional/reader mode to technical users with amp-compat…
johnwatkins0 Jul 6, 2020
467e6af
Run astra theme e2e tests separately so theme can be activated/remove…
johnwatkins0 Jul 6, 2020
435dce7
Set mobile_redirect to true from `originalOptions` when value is unch…
johnwatkins0 Jul 6, 2020
c5ad7a4
Fix lint issue
johnwatkins0 Jul 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion assets/src/setup/__mocks__/amp-setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module.exports = {
APP_ROOT_ID: 'amp-setup',
EXIT_LINK: 'http://site.test/wp-admin/?page=amp-options',
CLOSE_LINK: 'http://site.test/wp-admin/?page=amp-options',
CURRENT_THEME: {
name: 'Twenty Twenty',
},
FINISH_LINK: 'http://site.test/wp-admin/?page=amp-options',
OPTIONS_REST_ENDPOINT: 'http://site.test/wp-json/amp/v1/options',
READER_THEMES_REST_ENDPOINT: 'http://site.test/wp-json/amp/v1/reader-themes',
UPDATES_NONCE: '',
Expand Down
8 changes: 4 additions & 4 deletions assets/src/setup/components/error-screen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import './style.css';
*
* @param {Object} props Component props.
* @param {Object} props.error Error object containing a message string.
* @param {string} props.exitLink The link to return to the admin.
* @param {string} props.finishLink The link to return to the admin.
*/
export function ErrorScreen( { error, exitLink } ) {
export function ErrorScreen( { error, finishLink } ) {
return (
<div className="error-screen-container">
<Panel className="error-screen">
Expand All @@ -34,7 +34,7 @@ export function ErrorScreen( { error, exitLink } ) {
dangerouslySetInnerHTML={ { __html: error.message || __( 'There was an error loading the setup wizard.', 'amp' ) } }
/>
{ ' ' }
<a href={ exitLink }>
<a href={ finishLink }>
{ __( 'Return to AMP settings.', 'amp' ) }
</a>
</p>
Expand All @@ -47,5 +47,5 @@ ErrorScreen.propTypes = {
error: PropTypes.shape( {
message: PropTypes.string,
} ).isRequired,
exitLink: PropTypes.string.isRequired,
finishLink: PropTypes.string.isRequired,
};
2 changes: 1 addition & 1 deletion assets/src/setup/components/error-screen/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ErrorScreen } from '..';
describe( 'ErrorScreen', () => {
it( 'matches snapshot', () => {
const wrapper = create(
<ErrorScreen exitLink={ 'http://my-exit-link.com' } error={ { message: 'The application failed' } } />,
<ErrorScreen finishLink={ 'http://my-exit-link.com' } error={ { message: 'The application failed' } } />,
);
expect( wrapper.toJSON() ).toMatchSnapshot();
} );
Expand Down
34 changes: 19 additions & 15 deletions assets/src/setup/components/nav/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import { User } from '../user-context-provider';
* Navigation component.
*
* @param {Object} props Component props.
* @param {string} props.exitLink Link to exit the application.
* @param {string} props.closeLink Link to return to previous user location.
* @param {string} props.finishLink Link to exit the application.
*/
export function Nav( { exitLink } ) {
export function Nav( { closeLink, finishLink } ) {
const { activePageIndex, canGoForward, isLastPage, moveBack, moveForward } = useContext( Navigation );
const { savingOptions } = useContext( Options );
const { savingDeveloperToolsOption } = useContext( User );
Expand All @@ -33,18 +34,20 @@ export function Nav( { exitLink } ) {
<div className="amp-setup-nav">
<div className="amp-setup-nav__inner">
<div className="amp-setup-nav__close">
<Button isLink href={ exitLink }>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="close-icon" mask-type="alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="19" height="19">
<path fillRule="evenodd" clipRule="evenodd" d="M19.895 3.71875H5.89502C4.79502 3.71875 3.89502 4.61875 3.89502 5.71875V19.7188C3.89502 20.8188 4.79502 21.7188 5.89502 21.7188H19.895C21.005 21.7188 21.895 20.8188 21.895 19.7188V15.7188H19.895V19.7188H5.89502V5.71875H19.895V9.71875H21.895V5.71875C21.895 4.61875 21.005 3.71875 19.895 3.71875ZM13.395 17.7188L14.805 16.3088L12.225 13.7188H21.895V11.7188H12.225L14.805 9.12875L13.395 7.71875L8.39502 12.7188L13.395 17.7188Z" fill="white" />
</mask>
<g mask="url(#close-icon)">
<rect width="24" height="24" transform="matrix(-1 0 0 1 24.895 0.71875)" fill="#2459E7" />
</g>
</svg>
{ ! isLastPage && (
<Button isLink href={ closeLink }>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="close-icon" mask-type="alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="19" height="19">
<path fillRule="evenodd" clipRule="evenodd" d="M19.895 3.71875H5.89502C4.79502 3.71875 3.89502 4.61875 3.89502 5.71875V19.7188C3.89502 20.8188 4.79502 21.7188 5.89502 21.7188H19.895C21.005 21.7188 21.895 20.8188 21.895 19.7188V15.7188H19.895V19.7188H5.89502V5.71875H19.895V9.71875H21.895V5.71875C21.895 4.61875 21.005 3.71875 19.895 3.71875ZM13.395 17.7188L14.805 16.3088L12.225 13.7188H21.895V11.7188H12.225L14.805 9.12875L13.395 7.71875L8.39502 12.7188L13.395 17.7188Z" fill="white" />
</mask>
<g mask="url(#close-icon)">
<rect width="24" height="24" transform="matrix(-1 0 0 1 24.895 0.71875)" fill="#2459E7" />
</g>
</svg>

{ __( 'Close', 'amp' ) }
</Button>
{ __( 'Close', 'amp' ) }
</Button>
) }
</div>
<div className="amp-setup-nav__prev-next">
{ 1 > activePageIndex
Expand All @@ -69,7 +72,7 @@ export function Nav( { exitLink } ) {

<Button
disabled={ ! canGoForward || savingOptions || savingDeveloperToolsOption }
href={ isLastPage && ! savingDeveloperToolsOption && ! savingOptions ? exitLink : undefined }
href={ isLastPage && ! savingDeveloperToolsOption && ! savingOptions ? finishLink : undefined }
isPrimary
onClick={ moveForward }
>
Expand All @@ -90,5 +93,6 @@ export function Nav( { exitLink } ) {
}

Nav.propTypes = {
exitLink: PropTypes.string.isRequired,
closeLink: PropTypes.string.isRequired,
finishLink: PropTypes.string.isRequired,
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 69 additions & 16 deletions assets/src/setup/components/nav/test/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

/**
* External dependencies
*/
import { act } from 'react-dom/test-utils';
import { create } from 'react-test-renderer';
import PropTypes from 'prop-types';

/**
* WordPress dependencies
Expand All @@ -29,7 +29,24 @@ const getNavButtons = ( containerElement ) => ( {
} );

const MyPageComponent = () => <div />;
const testPages = [ { PageComponent: MyPageComponent, slug: 'slug', title: 'Page 0' }, { PageComponent: MyPageComponent, slug: 'slug-2', title: 'Page 1' } ];
const testPages = [
{ PageComponent: MyPageComponent, slug: 'slug', title: 'Page 0' },
{ PageComponent: MyPageComponent, slug: 'slug-2', title: 'Page 1' },
];

const Providers = ( { children, pages } ) => (
<OptionsContextProvider>
<UserContextProvider>
<NavigationContextProvider pages={ pages }>
{ children }
</NavigationContextProvider>
</UserContextProvider>
</OptionsContextProvider>
);
Providers.propTypes = {
children: PropTypes.any,
pages: PropTypes.array,
};

describe( 'Nav', () => {
beforeEach( () => {
Expand All @@ -44,27 +61,19 @@ describe( 'Nav', () => {

it( 'matches snapshot', () => {
const wrapper = create(
<OptionsContextProvider>
<UserContextProvider>
<NavigationContextProvider pages={ testPages }>
<Nav exitLink="http://site.test" />
</NavigationContextProvider>
</UserContextProvider>
</OptionsContextProvider>,
<Providers pages={ testPages }>
<Nav closeLink="http://site.test/wp-admin" finishLink="http://site.test" />
</Providers>,
);
expect( wrapper.toJSON() ).toMatchSnapshot();
} );

it( 'hides previous button on first page', () => {
act( () => {
render(
<OptionsContextProvider>
<UserContextProvider>
<NavigationContextProvider pages={ testPages }>
<Nav exitLink="http://site.test" />
</NavigationContextProvider>
</UserContextProvider>
</OptionsContextProvider>,
<Providers pages={ testPages }>
<Nav closeLink="http://site.test/wp-admin" finishLink="http://site.test" />
</Providers>,
container,
);
} );
Expand All @@ -74,4 +83,48 @@ describe( 'Nav', () => {
expect( prevButton ).toBeNull();
expect( nextButton ).not.toBeNull();
} );

it( 'changes next button to "Finish" on last page', () => {
act( () => {
render(
<Providers pages={ testPages }>
<Nav closeLink="http://site.test/wp-admin" finishLink="http://site.test" />
</Providers>,
container,
);
} );

const { nextButton } = getNavButtons( container );

expect( nextButton.textContent ).toBe( 'Next' );

act( () => {
nextButton.dispatchEvent( new global.MouseEvent( 'click', { bubbles: true } ) );
} );

expect( nextButton.textContent ).toBe( 'Finish' );
} );

it( 'close button hides on last page', () => {
act( () => {
render(
<Providers pages={ testPages }>
<Nav closeLink="http://site.test/wp-admin" finishLink="http://site.test" />
</Providers>,
container,
);
} );

const { nextButton } = getNavButtons( container );
let closeButton = container.querySelector( '.amp-setup-nav__close a' );

expect( closeButton ).not.toBeNull();

act( () => {
nextButton.dispatchEvent( new global.MouseEvent( 'click', { bubbles: true } ) );
} );

closeButton = container.querySelector( '.amp-setup-nav__close a' );
expect( closeButton ).toBeNull();
} );
} );
91 changes: 49 additions & 42 deletions assets/src/setup/components/options-context-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {
const [ fetchingOptions, setFetchingOptions ] = useState( false );
const [ savingOptions, setSavingOptions ] = useState( false );
const [ didSaveOptions, setDidSaveOptions ] = useState( false );
const [ originalOptions, setOriginalOptions ] = useState( null );
const [ originalOptions, setOriginalOptions ] = useState( {} );

const { setError } = useError();

Expand All @@ -47,6 +47,42 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {
hasUnmounted.current = true;
}, [] );

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just moved this up because I keep expecting the fetch effect to be above the save effect.

* Fetches options.
*/
useEffect( () => {
if ( Object.keys( originalOptions ).length || fetchingOptions ) {
return;
}

/**
* Fetches plugin options from the REST endpoint.
*/
( async () => {
setFetchingOptions( true );

try {
const fetchedOptions = await apiFetch( { url: optionsRestEndpoint } );

if ( true === hasUnmounted.current ) {
return;
}

if ( fetchedOptions.wizard_completed === false ) {
fetchedOptions.mobile_redirect = true;
fetchedOptions.reader_theme = null;
}

setOriginalOptions( fetchedOptions );
} catch ( e ) {
setError( e );
return;
}

setFetchingOptions( false );
} )();
}, [ fetchingOptions, originalOptions, optionsRestEndpoint, setError ] );

/**
* Sends options to the REST endpoint to be saved.
*
Expand All @@ -64,6 +100,16 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {
delete updatesToSave.reader_theme;
}

// If this is the first time running the wizard and mobile_redirect is not in updates, set mobile_redirect to true.
// We do this here instead of in the fetch effect to prevent the exit confirmation before the user has interacted.
if ( ! originalOptions.wizard_completed && ! ( 'mobile_redirect' in updatesToSave ) ) {
updatesToSave.mobile_redirect = originalOptions.mobile_redirect;
}

if ( ! originalOptions.wizard_completed ) {
updatesToSave.wizard_completed = true;
}

// Ensure this promise lasts at least a second so that the "Saving Options" load screen is
// visible long enough for the user to see it is happening.
const [ savedOptions ] = await Promise.all(
Expand All @@ -72,7 +118,7 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {
{
method: 'post',
url: optionsRestEndpoint,
data: { ...updates, wizard_completed: true },
data: updatesToSave,
},
),
waitASecond(),
Expand All @@ -91,7 +137,7 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {

setDidSaveOptions( true );
setSavingOptions( false );
}, [ optionsRestEndpoint, setError, updates ] );
}, [ optionsRestEndpoint, setError, originalOptions, updates ] );

/**
* Updates options in state.
Expand All @@ -103,45 +149,6 @@ export function OptionsContextProvider( { children, optionsRestEndpoint } ) {
setDidSaveOptions( false );
};

useEffect( () => {
if ( Object.keys( updates ).length || fetchingOptions ) {
return;
}

/**
* Fetches plugin options from the REST endpoint.
*/
( async () => {
setFetchingOptions( true );

try {
const fetchedOptions = await apiFetch( { url: optionsRestEndpoint } );

if ( true === hasUnmounted.current ) {
return;
}

setOriginalOptions( fetchedOptions );

const initialUpdates = {
wizard_completed: true,
};

if ( fetchedOptions.wizard_completed === false ) {
initialUpdates.mobile_redirect = true;
initialUpdates.reader_theme = null;
}

setUpdates( initialUpdates );
} catch ( e ) {
setError( e );
return;
}

setFetchingOptions( false );
} )();
}, [ fetchingOptions, updates, optionsRestEndpoint, setError ] );

// Allows an item in the updates object to be removed.
const unsetOption = useCallback( ( option ) => {
const newOptions = { ...updates };
Expand Down
Loading