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 12 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
3 changes: 3 additions & 0 deletions assets/src/setup/__mocks__/amp-setup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module.exports = {
APP_ROOT_ID: 'amp-setup',
CURRENT_THEME: {
name: 'Twenty Twenty',
},
EXIT_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',
Expand Down
24 changes: 13 additions & 11 deletions assets/src/setup/components/nav/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,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={ 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>

{ __( 'Close', 'amp' ) }
</Button>
{ __( 'Close', 'amp' ) }
</Button>
) }
</div>
<div className="amp-setup-nav__prev-next">
{ 1 > activePageIndex
Expand Down
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 exitLink="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 exitLink="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 exitLink="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 exitLink="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();
} );
} );
77 changes: 37 additions & 40 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 Down Expand Up @@ -103,45 +139,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
56 changes: 39 additions & 17 deletions assets/src/setup/components/reader-themes-context-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,64 @@ import PropTypes from 'prop-types';
*/
import { useError } from '../utils/use-error';
import { Options } from './options-context-provider';
import { Navigation } from './navigation-context-provider';

export const ReaderThemes = createContext();

/**
* Context provider for options retrieval and updating.
*
* @param {Object} props Component props.
* @param {string} props.currentTheme The theme currently active on the site.
* @param {string} props.wpAjaxUrl WP AJAX URL.
* @param {?any} props.children Component children.
* @param {string} props.readerThemesEndpoint REST endpoint to fetch reader themes.
* @param {string} props.updatesNonce Nonce for the AJAX request to install a theme.
*/
export function ReaderThemesContextProvider( { wpAjaxUrl, children, readerThemesEndpoint, updatesNonce } ) {
export function ReaderThemesContextProvider( { wpAjaxUrl, children, currentTheme, readerThemesEndpoint, updatesNonce } ) {
const { setError } = useError();

const [ themes, setThemes ] = useState( null );
const [ fetchingThemes, setFetchingThemes ] = useState( false );
const [ downloadingTheme, setDownloadingTheme ] = useState( false );
const [ downloadedTheme, setDownloadedTheme ] = useState( false );

/**
* Handle downloaded theme errors separately from normal error handling because we don't want it to break the application
* after settings have already been saved.
*/
const [ downloadingThemeError, setDownloadingThemeError ] = useState( null );

const { setError } = useError();

const { editedOptions, savingOptions } = useContext( Options );
const { editedOptions, updateOptions, savingOptions } = useContext( Options );
const { reader_theme: readerTheme, theme_support: themeSupport } = editedOptions;

// This component sets state inside async functions. Use this ref to prevent state updates after unmount.
const hasUnmounted = useRef( false );
useEffect( () => () => {
hasUnmounted.current = true;
}, [] );

/**
* The active reader theme.
*/
const selectedTheme = useMemo(
() => themes ? themes.find( ( { slug } ) => slug === readerTheme ) : null,
() => themes ? themes.find( ( { slug } ) => slug === readerTheme ) || { name: null } : { name: null },
[ readerTheme, themes ],
);

/**
* When reader mode was selected selected and the user chooses the currently active theme as the reader theme,
* we will override their choice with transitional.
*/
const [ readerModeWasOverridden, setReaderModeWasOverridden ] = useState( false );
const { currentPage: { slug: currentPageSlug } } = useContext( Navigation );
useEffect( () => {
if ( 'summary' === currentPageSlug && 'reader' === themeSupport && selectedTheme.name === currentTheme.name ) {
updateOptions( { theme_support: 'transitional' } );
setReaderModeWasOverridden( true );
}
}, [ selectedTheme.name, currentTheme.name, themeSupport, currentPageSlug, updateOptions ] );

/**
* Handle downloaded theme errors separately from normal error handling because we don't want it to break the application
* after settings have already been saved.
*/
const [ downloadingThemeError, setDownloadingThemeError ] = useState( null );

/**
* Downloads the selected reader theme, if necessary, when options are saved.
*/
Expand Down Expand Up @@ -116,7 +137,7 @@ export function ReaderThemesContextProvider( { wpAjaxUrl, children, readerThemes
* Fetches theme data when needed.
*/
useEffect( () => {
if ( fetchingThemes || ! readerThemesEndpoint || themes || 'reader' !== themeSupport ) {
if ( fetchingThemes || ! readerThemesEndpoint || themes || 'standard' === themeSupport ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

'standard' === themeSupport

Why only bail if it's standard mode? Shouldn't it be 'reader' !== themeSupport, like what was before?

return;
}

Expand Down Expand Up @@ -144,18 +165,16 @@ export function ReaderThemesContextProvider( { wpAjaxUrl, children, readerThemes
} )();
}, [ fetchingThemes, readerThemesEndpoint, setError, themes, themeSupport ] );

useEffect( () => () => {
hasUnmounted.current = true;
}, [] );

return (
<ReaderThemes.Provider
value={
{
currentTheme,
downloadedTheme,
downloadingTheme,
downloadingThemeError,
fetchingThemes,
readerModeWasOverridden,
themes,
}
}
Expand All @@ -166,8 +185,11 @@ export function ReaderThemesContextProvider( { wpAjaxUrl, children, readerThemes
}

ReaderThemesContextProvider.propTypes = {
wpAjaxUrl: PropTypes.string.isRequired,
children: PropTypes.any,
currentTheme: PropTypes.shape( {
name: PropTypes.string.isRequired,
} ).isRequired,
readerThemesEndpoint: PropTypes.string.isRequired,
updatesNonce: PropTypes.string.isRequired,
wpAjaxUrl: PropTypes.string.isRequired,
};
Loading