Add confirmation dialog before loading the sample products

This commit is contained in:
Chi-Hsuan Huang 2022-05-24 15:07:03 +08:00
parent 86553fbe1f
commit e2cc91dd60
5 changed files with 154 additions and 22 deletions

View File

@ -12,17 +12,16 @@ import './load-sample-product-confirm-modal.scss';
type Props = { type Props = {
onCancel: () => void; onCancel: () => void;
onOK: () => void; onImport: () => void;
}; };
export const LoadSampleProductConfirmModal: React.VFC< Props > = ( { export const LoadSampleProductConfirmModal: React.VFC< Props > = ( {
onCancel, onCancel,
onOK, onImport,
} ) => { } ) => {
return ( return (
<Modal <Modal
className="woocommerce-products-load-sample-product-confirm-modal" className="woocommerce-products-load-sample-product-confirm-modal"
overlayClassName="woocommerce-products-load-sample-product-confirm-modal-overlay"
title="Load sample products" title="Load sample products"
onRequestClose={ onCancel } onRequestClose={ onCancel }
> >
@ -35,7 +34,7 @@ export const LoadSampleProductConfirmModal: React.VFC< Props > = ( {
<Button isSecondary onClick={ onCancel }> <Button isSecondary onClick={ onCancel }>
{ __( 'Cancel' ) } { __( 'Cancel' ) }
</Button> </Button>
<Button isPrimary onClick={ onOK }> <Button isPrimary onClick={ onImport }>
{ __( 'Import sample products' ) } { __( 'Import sample products' ) }
</Button> </Button>
</div> </div>

View File

@ -21,11 +21,16 @@ import useProductTypeListItems from '../experimental-products/use-product-types-
import { getProductTypes } from '../experimental-products/utils'; import { getProductTypes } from '../experimental-products/utils';
import LoadSampleProductModal from '../components/load-sample-product-modal'; import LoadSampleProductModal from '../components/load-sample-product-modal';
import useLoadSampleProducts from '../components/use-load-sample-products'; import useLoadSampleProducts from '../components/use-load-sample-products';
import LoadSampleProductConfirmModal from '../components/load-sample-product-confirm-modal';
import useRecordCompletionTime from '../use-record-completion-time'; import useRecordCompletionTime from '../use-record-completion-time';
export const Products = () => { export const Products = () => {
const [ showStacks, setStackVisibility ] = useState< boolean >( false ); const [ showStacks, setStackVisibility ] = useState< boolean >( false );
const { recordCompletionTime } = useRecordCompletionTime( 'products' ); const { recordCompletionTime } = useRecordCompletionTime( 'products' );
const [
isConfirmingLoadSampleProducts,
setIsConfirmingLoadSampleProducts,
] = useState( false );
const importTypesWithTimeRecord = useMemo( const importTypesWithTimeRecord = useMemo(
() => () =>
@ -61,7 +66,9 @@ export const Products = () => {
const StacksComponent = ( const StacksComponent = (
<Stacks <Stacks
items={ productTypeListItems } items={ productTypeListItems }
onClickLoadSampleProduct={ loadSampleProduct } onClickLoadSampleProduct={ () =>
setIsConfirmingLoadSampleProducts( true )
}
/> />
); );
@ -83,7 +90,21 @@ export const Products = () => {
</Button> </Button>
{ showStacks && StacksComponent } { showStacks && StacksComponent }
</div> </div>
{ isLoadingSampleProducts && <LoadSampleProductModal /> } { isLoadingSampleProducts ? (
<LoadSampleProductModal />
) : (
isConfirmingLoadSampleProducts && (
<LoadSampleProductConfirmModal
onCancel={ () =>
setIsConfirmingLoadSampleProducts( false )
}
onImport={ () => {
setIsConfirmingLoadSampleProducts( false );
loadSampleProduct();
} }
/>
)
) }
</div> </div>
); );
}; };

View File

@ -11,6 +11,16 @@ import { Products } from '../';
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) ); jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
global.fetch = jest.fn().mockImplementation( () =>
Promise.resolve( {
json: () => Promise.resolve( {} ),
status: 200,
} )
);
const confirmModalText =
"We'll import images from woocommerce.com to set up your sample products.";
describe( 'Products', () => { describe( 'Products', () => {
beforeEach( () => { beforeEach( () => {
( recordEvent as jest.Mock ).mockClear(); ( recordEvent as jest.Mock ).mockClear();
@ -103,4 +113,57 @@ describe( 'Products', () => {
); );
} ); } );
} ); } );
it( 'should send a request to load sample products when the "Import sample products" button is clicked', async () => {
const fetchMock = jest.spyOn( global, 'fetch' );
const { queryByText, getByRole } = render( <Products /> );
userEvent.click(
getByRole( 'button', { name: 'Or add your products from scratch' } )
);
expect( queryByText( 'Load Sample Products' ) ).toBeInTheDocument();
userEvent.click(
getByRole( 'link', { name: 'Load Sample Products' } )
);
await waitFor( () =>
expect( queryByText( confirmModalText ) ).toBeInTheDocument()
);
userEvent.click(
getByRole( 'button', { name: 'Import sample products' } )
);
await waitFor( () =>
expect( queryByText( confirmModalText ) ).not.toBeInTheDocument()
);
expect( fetchMock ).toHaveBeenCalledWith(
'/wc-admin/onboarding/tasks/import_sample_products?_locale=user',
{
body: undefined,
credentials: 'include',
headers: { Accept: 'application/json, */*;q=0.1' },
method: 'POST',
}
);
} );
it( 'should close the confirmation modal when the cancel button is clicked', async () => {
const { queryByText, getByRole } = render( <Products /> );
userEvent.click(
getByRole( 'button', { name: 'Or add your products from scratch' } )
);
expect( queryByText( 'Load Sample Products' ) ).toBeInTheDocument();
userEvent.click(
getByRole( 'link', { name: 'Load Sample Products' } )
);
await waitFor( () =>
expect( queryByText( confirmModalText ) ).toBeInTheDocument()
);
userEvent.click( getByRole( 'button', { name: 'Cancel' } ) );
expect( queryByText( confirmModalText ) ).not.toBeInTheDocument();
} );
} ); } );

View File

@ -25,6 +25,7 @@ import CardLayout from './card-layout';
import { LoadSampleProductType } from './constants'; import { LoadSampleProductType } from './constants';
import LoadSampleProductModal from '../components/load-sample-product-modal'; import LoadSampleProductModal from '../components/load-sample-product-modal';
import useLoadSampleProducts from '../components/use-load-sample-products'; import useLoadSampleProducts from '../components/use-load-sample-products';
import LoadSampleProductConfirmModal from '../components/load-sample-product-confirm-modal';
import useRecordCompletionTime from '../use-record-completion-time'; import useRecordCompletionTime from '../use-record-completion-time';
import { getCountryCode } from '~/dashboard/utils'; import { getCountryCode } from '~/dashboard/utils';
import { useProductTaskExperiment } from './use-product-layout-experiment'; import { useProductTaskExperiment } from './use-product-layout-experiment';
@ -54,6 +55,10 @@ const ViewControlButton: React.FC< {
export const Products = () => { export const Products = () => {
const [ isExpanded, setIsExpanded ] = useState< boolean >( false ); const [ isExpanded, setIsExpanded ] = useState< boolean >( false );
const [
isConfirmingLoadSampleProducts,
setIsConfirmingLoadSampleProducts,
] = useState( false );
const { const {
isLoading: isLoadingExperiment, isLoading: isLoadingExperiment,
experimentLayout, experimentLayout,
@ -125,7 +130,7 @@ export const Products = () => {
if ( experimentLayout === 'card' ) { if ( experimentLayout === 'card' ) {
surfacedProductTypes.push( { surfacedProductTypes.push( {
...LoadSampleProductType, ...LoadSampleProductType,
onClick: loadSampleProduct, onClick: () => setIsConfirmingLoadSampleProducts( true ),
} ); } );
} }
} }
@ -135,7 +140,6 @@ export const Products = () => {
isExpanded, isExpanded,
productTypesWithTimeRecord, productTypesWithTimeRecord,
experimentLayout, experimentLayout,
loadSampleProduct,
] ); ] );
return ( return (
@ -159,7 +163,9 @@ export const Products = () => {
{ experimentLayout === 'stacked' ? ( { experimentLayout === 'stacked' ? (
<Stack <Stack
items={ visibleProductTypes } items={ visibleProductTypes }
onClickLoadSampleProduct={ loadSampleProduct } onClickLoadSampleProduct={ () =>
setIsConfirmingLoadSampleProducts( true )
}
showOtherOptions={ isExpanded } showOtherOptions={ isExpanded }
/> />
) : ( ) : (
@ -178,7 +184,21 @@ export const Products = () => {
/> />
<Footer /> <Footer />
</div> </div>
{ isLoadingSampleProducts && <LoadSampleProductModal /> } { isLoadingSampleProducts ? (
<LoadSampleProductModal />
) : (
isConfirmingLoadSampleProducts && (
<LoadSampleProductConfirmModal
onCancel={ () =>
setIsConfirmingLoadSampleProducts( false )
}
onImport={ () => {
setIsConfirmingLoadSampleProducts( false );
loadSampleProduct();
} }
/>
)
) }
</> </>
) } ) }
</div> </div>

View File

@ -45,6 +45,9 @@ global.fetch = jest.fn().mockImplementation( () =>
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) ); jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
const confirmModalText =
"We'll import images from woocommerce.com to set up your sample products.";
describe( 'Products', () => { describe( 'Products', () => {
beforeEach( () => { beforeEach( () => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -238,31 +241,57 @@ describe( 'Products', () => {
expect( queryByText( 'View less product types' ) ).toBeInTheDocument(); expect( queryByText( 'View less product types' ) ).toBeInTheDocument();
} ); } );
it( 'should send a request to load sample products when the link is clicked', async () => { it( 'should send a request to load sample products when the "Import sample products" button is clicked', async () => {
const fetchMock = jest.spyOn( global, 'fetch' ); const fetchMock = jest.spyOn( global, 'fetch' );
const { queryByText, getByRole } = render( <Products /> ); const { queryByText, getByRole } = render( <Products /> );
userEvent.click( userEvent.click(
getByRole( 'button', { name: 'View more product types' } ) getByRole( 'button', { name: 'View more product types' } )
); );
expect( queryByText( 'Load Sample Products' ) ).toBeInTheDocument(); expect( queryByText( 'Load Sample Products' ) ).toBeInTheDocument();
userEvent.click( userEvent.click(
getByRole( 'link', { name: 'Load Sample Products' } ) getByRole( 'link', { name: 'Load Sample Products' } )
); );
await waitFor( () => await waitFor( () =>
expect( fetchMock ).toHaveBeenCalledWith( expect( queryByText( confirmModalText ) ).toBeInTheDocument()
'/wc-admin/onboarding/tasks/import_sample_products?_locale=user',
{
body: undefined,
credentials: 'include',
headers: { Accept: 'application/json, */*;q=0.1' },
method: 'POST',
}
)
); );
userEvent.click(
getByRole( 'button', { name: 'Import sample products' } )
);
await waitFor( () =>
expect( queryByText( confirmModalText ) ).not.toBeInTheDocument()
);
expect( fetchMock ).toHaveBeenCalledWith(
'/wc-admin/onboarding/tasks/import_sample_products?_locale=user',
{
body: undefined,
credentials: 'include',
headers: { Accept: 'application/json, */*;q=0.1' },
method: 'POST',
}
);
} );
it( 'should close the confirmation modal when the cancel button is clicked', async () => {
const { queryByText, getByRole } = render( <Products /> );
userEvent.click(
getByRole( 'button', { name: 'View more product types' } )
);
expect( queryByText( 'Load Sample Products' ) ).toBeInTheDocument();
userEvent.click(
getByRole( 'link', { name: 'Load Sample Products' } )
);
await waitFor( () =>
expect( queryByText( confirmModalText ) ).toBeInTheDocument()
);
userEvent.click( getByRole( 'button', { name: 'Cancel' } ) );
expect( queryByText( confirmModalText ) ).not.toBeInTheDocument();
} ); } );
it( 'should show spinner when layout experiment is loading', async () => { it( 'should show spinner when layout experiment is loading', async () => {