diff --git a/packages/block-directory/src/components/downloadable-block-header/index.js b/packages/block-directory/src/components/downloadable-block-header/index.js
index 0c37daef7bd65f..a35935d60a973a 100644
--- a/packages/block-directory/src/components/downloadable-block-header/index.js
+++ b/packages/block-directory/src/components/downloadable-block-header/index.js
@@ -1,8 +1,9 @@
/**
* WordPress dependencies
*/
-import { Button } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
+import { Button } from '@wordpress/components';
+import { withSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -10,11 +11,12 @@ import { __, sprintf } from '@wordpress/i18n';
import { BlockIcon } from '@wordpress/block-editor';
import BlockRatings from '../block-ratings';
-function DownloadableBlockHeader( {
+export function DownloadableBlockHeader( {
icon,
title,
rating,
ratingCount,
+ isLoading,
onClick,
} ) {
return (
@@ -45,15 +47,23 @@ function DownloadableBlockHeader( {
);
}
-export default DownloadableBlockHeader;
+export default withSelect( ( select ) => {
+ return {
+ isLoading: select( 'core/block-directory' ).isInstalling(),
+ };
+} )( DownloadableBlockHeader );
diff --git a/packages/block-directory/src/components/downloadable-block-header/test/index.js b/packages/block-directory/src/components/downloadable-block-header/test/index.js
index 96ba6247170cb9..3bc26f312e9bc5 100644
--- a/packages/block-directory/src/components/downloadable-block-header/test/index.js
+++ b/packages/block-directory/src/components/downloadable-block-header/test/index.js
@@ -7,21 +7,27 @@ import { shallow } from 'enzyme';
* WordPress dependencies
*/
import { BlockIcon } from '@wordpress/block-editor';
+import { Button } from '@wordpress/components';
/**
* Internal dependencies
*/
-import DownloadableBlockHeader from '../index';
+import { DownloadableBlockHeader } from '../index';
import { pluginWithImg, pluginWithIcon } from './fixtures';
-const getContainer = ( { icon, title, rating, ratingCount } ) => {
+const getContainer = (
+ { icon, title, rating, ratingCount },
+ onClick = jest.fn(),
+ isLoading = false
+) => {
return shallow(
{} }
+ onClick={ onClick }
title={ title }
rating={ rating }
ratingCount={ ratingCount }
+ isLoading={ isLoading }
/>
);
};
@@ -50,4 +56,28 @@ describe( 'DownloadableBlockHeader', () => {
expect( wrapper.find( BlockIcon ) ).toHaveLength( 1 );
} );
} );
+
+ describe( 'user interaction', () => {
+ test( 'should trigger the onClick function', () => {
+ const onClickMock = jest.fn();
+ const wrapper = getContainer( pluginWithIcon, onClickMock );
+ const event = {
+ preventDefault: jest.fn(),
+ };
+ wrapper.find( Button ).simulate( 'click', event );
+ expect( onClickMock ).toHaveBeenCalledTimes( 1 );
+ expect( event.preventDefault ).toHaveBeenCalled();
+ } );
+
+ test( 'should not trigger the onClick function if loading', () => {
+ const onClickMock = jest.fn();
+ const wrapper = getContainer( pluginWithIcon, onClickMock, true );
+ const event = {
+ preventDefault: jest.fn(),
+ };
+ wrapper.find( Button ).simulate( 'click', event );
+ expect( event.preventDefault ).toHaveBeenCalled();
+ expect( onClickMock ).toHaveBeenCalledTimes( 0 );
+ } );
+ } );
} );
diff --git a/packages/block-directory/src/components/downloadable-block-list-item/index.js b/packages/block-directory/src/components/downloadable-block-list-item/index.js
index 3eb69eba7d2d70..6b8e85e4cdab78 100644
--- a/packages/block-directory/src/components/downloadable-block-list-item/index.js
+++ b/packages/block-directory/src/components/downloadable-block-list-item/index.js
@@ -1,9 +1,10 @@
/**
* Internal dependencies
*/
-import DownloadableBlockHeader from '../downloadable-block-header';
import DownloadableBlockAuthorInfo from '../downloadable-block-author-info';
+import DownloadableBlockHeader from '../downloadable-block-header';
import DownloadableBlockInfo from '../downloadable-block-info';
+import DownloadableBlockNotice from '../downloadable-block-notice';
function DownloadableBlockListItem( { item, onClick } ) {
const {
@@ -32,6 +33,10 @@ function DownloadableBlockListItem( { item, onClick } ) {
/>
+
{
+ if ( ! errorNotices[ block.id ] ) {
+ return null;
+ }
+
+ // A Failed install is the default error as its the first step
+ let copy = __( 'Block could not be added.' );
+
+ if ( errorNotices[ block.id ] === DOWNLOAD_ERROR_NOTICE_ID ) {
+ copy = __(
+ 'Block could not be added. There is a problem with the block.'
+ );
+ }
+
+ return (
+
+
+ { copy }
+
+
+
+ );
+};
+
+export default withSelect( ( select ) => {
+ return {
+ errorNotices: select( 'core/block-directory' ).getErrorNotices(),
+ };
+} )( DownloadableBlockNotice );
diff --git a/packages/block-directory/src/components/downloadable-block-notice/style.scss b/packages/block-directory/src/components/downloadable-block-notice/style.scss
new file mode 100644
index 00000000000000..418940f70bbc99
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-block-notice/style.scss
@@ -0,0 +1,8 @@
+.block-directory-downloadable-blocks__notice {
+ margin: 0 0 16px;
+}
+
+.block-directory-downloadable-blocks__notice-content {
+ padding-right: 12px;
+ margin-bottom: 8px;
+}
diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js
new file mode 100644
index 00000000000000..c91c7b0c586958
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js
@@ -0,0 +1,3 @@
+export const plugin = {
+ id: 'boxer-block',
+};
diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/index.js b/packages/block-directory/src/components/downloadable-block-notice/test/index.js
new file mode 100644
index 00000000000000..d33f1206aa649d
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-block-notice/test/index.js
@@ -0,0 +1,63 @@
+/**
+ * External dependencies
+ */
+import { shallow } from 'enzyme';
+
+/**
+ * WordPress dependencies
+ */
+import { Button } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { DownloadableBlockNotice } from '../index';
+import { plugin } from './fixtures';
+
+import { INSTALL_ERROR_NOTICE_ID } from '../../../store/constants';
+
+const getContainer = ( { block, onClick = jest.fn(), errorNotices = {} } ) => {
+ return shallow(
+
+ );
+};
+
+describe( 'DownloadableBlockNotice', () => {
+ describe( 'Rendering', () => {
+ it( 'should return null when there are no error notices', () => {
+ const wrapper = getContainer( { block: plugin } );
+ expect( wrapper.isEmptyRender() ).toBe( true );
+ } );
+
+ it( 'should return something when there are error notices', () => {
+ const errorNotices = {
+ [ plugin.id ]: INSTALL_ERROR_NOTICE_ID,
+ };
+ const wrapper = getContainer( { block: plugin, errorNotices } );
+ expect( wrapper.length ).toBeGreaterThan( 0 );
+ } );
+ } );
+
+ describe( 'Behavior', () => {
+ it( 'should trigger the callback on button click', () => {
+ const errorNotices = {
+ [ plugin.id ]: INSTALL_ERROR_NOTICE_ID,
+ };
+
+ const onClick = jest.fn();
+ const wrapper = getContainer( {
+ block: plugin,
+ onClick,
+ errorNotices,
+ } );
+
+ wrapper.find( Button ).simulate( 'click', { event: {} } );
+
+ expect( onClick ).toHaveBeenCalledTimes( 1 );
+ } );
+ } );
+} );
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/index.js b/packages/block-directory/src/components/downloadable-blocks-list/index.js
index b63c40d34871f3..f94f95d5a454e7 100644
--- a/packages/block-directory/src/components/downloadable-blocks-list/index.js
+++ b/packages/block-directory/src/components/downloadable-blocks-list/index.js
@@ -6,28 +6,29 @@ import { noop } from 'lodash';
/**
* WordPress dependencies
*/
-import {
- getBlockMenuDefaultClassName,
- unregisterBlockType,
-} from '@wordpress/blocks';
+import { getBlockMenuDefaultClassName } from '@wordpress/blocks';
import { withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
-import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import DownloadableBlockListItem from '../downloadable-block-list-item';
+import {
+ DOWNLOAD_ERROR_NOTICE_ID,
+ INSTALL_ERROR_NOTICE_ID,
+} from '../../store/constants';
-const DOWNLOAD_ERROR_NOTICE_ID = 'block-download-error';
-const INSTALL_ERROR_NOTICE_ID = 'block-install-error';
-
-function DownloadableBlocksList( {
+export function DownloadableBlocksList( {
items,
onHover = noop,
children,
- downloadAndInstallBlock,
+ install,
} ) {
+ if ( ! items.length ) {
+ return null;
+ }
+
return (
/*
* Disable reason: The `list` ARIA role is redundant but
@@ -35,14 +36,14 @@ function DownloadableBlocksList( {
*/
/* eslint-disable jsx-a11y/no-redundant-roles */
- { items &&
- items.map( ( item ) => (
+ { items.map( ( item ) => {
+ return (
{
- downloadAndInstallBlock( item );
+ install( item );
onHover( null );
} }
onFocus={ () => onHover( item ) }
@@ -51,7 +52,8 @@ function DownloadableBlocksList( {
onBlur={ () => onHover( null ) }
item={ item }
/>
- ) ) }
+ );
+ } ) }
{ children }
/* eslint-enable jsx-a11y/no-redundant-roles */
@@ -59,78 +61,62 @@ function DownloadableBlocksList( {
}
export default compose(
- withDispatch( ( dispatch, props ) => {
- const { installBlock, downloadBlock } = dispatch(
- 'core/block-directory'
- );
- const { createErrorNotice, removeNotice } = dispatch( 'core/notices' );
- const { removeBlocks } = dispatch( 'core/block-editor' );
+ withDispatch( ( dispatch, props, { select } ) => {
+ const {
+ downloadBlock,
+ installBlock,
+ setErrorNotice,
+ clearErrorNotice,
+ setIsInstalling,
+ } = dispatch( 'core/block-directory' );
const { onSelect } = props;
+ const errorNotices = select( 'core/block-directory' ).getErrorNotices();
- return {
- downloadAndInstallBlock: ( item ) => {
- const onDownloadError = () => {
- createErrorNotice( __( 'Block previews can’t load.' ), {
- id: DOWNLOAD_ERROR_NOTICE_ID,
- actions: [
- {
- label: __( 'Retry' ),
- onClick: () => {
- removeNotice( DOWNLOAD_ERROR_NOTICE_ID );
- downloadBlock(
- item,
- onSuccess,
- onDownloadError
- );
- },
- },
- ],
- } );
- };
+ const downloadAssets = ( item ) => {
+ clearErrorNotice( item.id );
+ setIsInstalling( true );
- const onSuccess = () => {
- const createdBlock = onSelect( item );
+ const onDownloadError = () => {
+ setErrorNotice( item.id, DOWNLOAD_ERROR_NOTICE_ID );
+ setIsInstalling( false );
+ };
+
+ const onDownloadSuccess = () => {
+ onSelect( item );
+ setIsInstalling( false );
+ };
- const onInstallBlockError = () => {
- createErrorNotice(
- __( "Block previews can't install." ),
- {
- id: INSTALL_ERROR_NOTICE_ID,
- actions: [
- {
- label: __( 'Retry' ),
- onClick: () => {
- removeNotice(
- INSTALL_ERROR_NOTICE_ID
- );
- installBlock(
- item,
- noop,
- onInstallBlockError
- );
- },
- },
- {
- label: __( 'Remove' ),
- onClick: () => {
- removeNotice(
- INSTALL_ERROR_NOTICE_ID
- );
- removeBlocks(
- createdBlock.clientId
- );
- unregisterBlockType( item.name );
- },
- },
- ],
- }
- );
- };
+ downloadBlock( item, onDownloadSuccess, onDownloadError );
+ };
+
+ const installPlugin = ( item, onSuccess ) => {
+ if (
+ errorNotices[ item.id ] &&
+ errorNotices[ item.id ] === DOWNLOAD_ERROR_NOTICE_ID
+ ) {
+ // Install has already run & the error was in downloading the assets, so we
+ // can skip the install step to prevent re-downloading the plugin.
+ return onSuccess();
+ }
+
+ clearErrorNotice( item.id );
+ setIsInstalling( true );
+
+ const onInstallBlockError = () => {
+ setErrorNotice( item.id, INSTALL_ERROR_NOTICE_ID );
+ setIsInstalling( false );
+ };
- installBlock( item, noop, onInstallBlockError );
+ installBlock( item, onSuccess, onInstallBlockError );
+ };
+
+ return {
+ install( item ) {
+ const onSuccess = () => {
+ downloadAssets( item );
};
- downloadBlock( item, onSuccess, onDownloadError );
+ installPlugin( item, onSuccess );
},
};
} )
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js
new file mode 100644
index 00000000000000..899f7f663bac08
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js
@@ -0,0 +1,24 @@
+export const plugin = {
+ name: 'boxer/boxer',
+ title: 'Boxer',
+ description:
+ 'Boxer is a Block that puts your WordPress posts into boxes on a page.',
+ id: 'boxer-block',
+ icon: 'block-default',
+ rating: 5,
+ rating_count: 1,
+ active_installs: 0,
+ author_block_rating: 5,
+ author_block_count: '1',
+ author: 'CK Lee',
+ assets: [
+ 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/index.js',
+ 'http://plugins.svn.wordpress.org/boxer-block/trunk/build/view.js',
+ ],
+ humanized_updated: '3 months ago',
+};
+
+export const items = [
+ plugin,
+ { ...plugin, name: 'my-block/test', id: 'my-block' },
+];
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/index.js b/packages/block-directory/src/components/downloadable-blocks-list/test/index.js
new file mode 100644
index 00000000000000..d2bbec0021b314
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-blocks-list/test/index.js
@@ -0,0 +1,65 @@
+/**
+ * External dependencies
+ */
+import { shallow } from 'enzyme';
+
+/**
+ * Internal dependencies
+ */
+import { DownloadableBlocksList } from '../index';
+import DownloadableBlockListItem from '../../downloadable-block-list-item';
+import { items, plugin } from './fixtures';
+
+const getContainer = ( {
+ blocks,
+ selectMock = jest.fn(),
+ hoverMock = jest.fn(),
+ isLoading = false,
+ errorNotices = {},
+ install = jest.fn(),
+} ) => {
+ return shallow(
+
+ );
+};
+
+describe( 'DownloadableBlocksList', () => {
+ describe( 'List rendering', () => {
+ it( 'should render and empty list', () => {
+ const wrapper = getContainer( { blocks: [] } );
+ expect( wrapper.isEmptyRender() ).toBe( true );
+ } );
+
+ it( 'should render plugins items into the list', () => {
+ const wrapper = getContainer( { blocks: items } );
+
+ expect( wrapper.find( DownloadableBlockListItem ).length ).toBe(
+ items.length
+ );
+ } );
+ } );
+ describe( 'Behaviour', () => {
+ it( 'should try to install the block plugin', () => {
+ const install = jest.fn();
+ const errorNotices = {};
+
+ const wrapper = getContainer( {
+ blocks: [ plugin ],
+ install,
+ errorNotices,
+ } );
+ const listItems = wrapper.find( DownloadableBlockListItem );
+
+ listItems.get( 0 ).props.onClick();
+
+ expect( install ).toHaveBeenCalledTimes( 1 );
+ } );
+ } );
+} );
diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js
index 09ba6381b59ae0..deac6f47dd0874 100644
--- a/packages/block-directory/src/store/actions.js
+++ b/packages/block-directory/src/store/actions.js
@@ -150,3 +150,45 @@ export function removeInstalledBlockType( item ) {
item,
};
}
+
+/**
+ * Returns an action object used to indicate install in progress
+ *
+ * @param {boolean} isInstalling Boolean value that tells state whether installation is occurring
+ *
+ */
+export function setIsInstalling( isInstalling ) {
+ return {
+ type: 'SET_INSTALLING_BLOCK',
+ isInstalling,
+ };
+}
+
+/**
+ * Sets an error notice string to be displayed to the user
+ *
+ * @param {string} blockId The ID of the block plugin. eg: my-block
+ * @param {string} noticeId The ID of the message used to determine which notice to show.
+ *
+ */
+export function setErrorNotice( blockId, noticeId ) {
+ return {
+ type: 'SET_ERROR_NOTICE_ID',
+ blockId,
+ noticeId,
+ };
+}
+
+/**
+ * Sets the error noticeId to empty for specific block
+ *
+ * @param {string} blockId The ID of the block plugin. eg: my-block
+ *
+ */
+export function clearErrorNotice( blockId ) {
+ return {
+ type: 'SET_ERROR_NOTICE_ID',
+ blockId,
+ noticeId: '',
+ };
+}
diff --git a/packages/block-directory/src/store/constants.js b/packages/block-directory/src/store/constants.js
new file mode 100644
index 00000000000000..625c780faf399e
--- /dev/null
+++ b/packages/block-directory/src/store/constants.js
@@ -0,0 +1,13 @@
+/**
+ * ID of error when downloading block fails
+ *
+ * @type {string}
+ */
+export const DOWNLOAD_ERROR_NOTICE_ID = 'block-download-error';
+
+/**
+ * ID of error when installing block fails
+ *
+ * @type {string}
+ */
+export const INSTALL_ERROR_NOTICE_ID = 'block-install-error';
diff --git a/packages/block-directory/src/store/reducer.js b/packages/block-directory/src/store/reducer.js
index c0993c56dfaf64..6dd02788297977 100644
--- a/packages/block-directory/src/store/reducer.js
+++ b/packages/block-directory/src/store/reducer.js
@@ -28,9 +28,10 @@ export const downloadableBlocks = (
case 'RECEIVE_DOWNLOADABLE_BLOCKS':
return {
...state,
- results: Object.assign( {}, state.results, {
+ results: {
+ ...state.results,
[ action.filterValue ]: action.downloadableBlocks,
- } ),
+ },
isRequestingDownloadableBlocks: false,
};
}
@@ -48,6 +49,7 @@ export const downloadableBlocks = (
export const blockManagement = (
state = {
installedBlockTypes: [],
+ isInstalling: false,
},
action
) => {
@@ -67,12 +69,17 @@ export const blockManagement = (
( blockType ) => blockType.name !== action.item.name
),
};
+ case 'SET_INSTALLING_BLOCK':
+ return {
+ ...state,
+ isInstalling: action.isInstalling,
+ };
}
return state;
};
/**
- * Reducer returns whether the user can install blocks.
+ * Reducer returning an array of downloadable blocks.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
@@ -87,8 +94,36 @@ export function hasPermission( state = true, action ) {
return state;
}
+/**
+ * Reducer returning an object of error notices.
+ *
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Object} Updated state.
+ */
+export const errorNotices = (
+ state = {
+ notices: {},
+ },
+ action
+) => {
+ switch ( action.type ) {
+ case 'SET_ERROR_NOTICE_ID':
+ return {
+ ...state,
+ notices: {
+ ...state.notices,
+ [ action.blockId ]: action.noticeId,
+ },
+ };
+ }
+ return state;
+};
+
export default combineReducers( {
downloadableBlocks,
blockManagement,
hasPermission,
+ errorNotices,
} );
diff --git a/packages/block-directory/src/store/selectors.js b/packages/block-directory/src/store/selectors.js
index 7a1778b1cc09a6..e3851b2b8eb41f 100644
--- a/packages/block-directory/src/store/selectors.js
+++ b/packages/block-directory/src/store/selectors.js
@@ -45,3 +45,25 @@ export function hasInstallBlocksPermission( state ) {
export function getInstalledBlockTypes( state ) {
return state.blockManagement.installedBlockTypes;
}
+
+/**
+ * Returns true if application is calling install endpoint.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {boolean} Whether its currently installing
+ */
+export function isInstalling( state ) {
+ return state.blockManagement.isInstalling;
+}
+
+/**
+ * Returns the error notices
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {Object} Object with error notices.
+ */
+export function getErrorNotices( state ) {
+ return state.errorNotices.notices;
+}
diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js
new file mode 100644
index 00000000000000..113d40d7b0ef6e
--- /dev/null
+++ b/packages/block-directory/src/store/test/actions.js
@@ -0,0 +1,148 @@
+/**
+ * WordPress dependencies
+ */
+import * as blockFunctions from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import { downloadBlock, installBlock } from '../actions';
+import * as controls from '../controls';
+
+const ACTIONS = {
+ apiFetch: 'API_FETCH',
+ addInstalledBlockType: 'ADD_INSTALLED_BLOCK_TYPE',
+ removeInstalledBlockType: 'REMOVE_INSTALLED_BLOCK_TYPE',
+};
+
+jest.mock( '@wordpress/blocks' );
+
+describe( 'actions', () => {
+ const item = { id: 'block/block', name: 'Test Block' };
+ const blockPlugin = {
+ assets: [ 'http://www.wordpress.org/plugins/fakeasset.js' ],
+ };
+ const getBlockTypeMock = jest.spyOn( blockFunctions, 'getBlockTypes' );
+ jest.spyOn( controls, 'apiFetch' );
+ jest.spyOn( controls, 'loadAssets' );
+
+ afterEach( () => {
+ jest.clearAllMocks();
+ } );
+
+ afterAll( () => {
+ jest.resetAllMocks();
+ } );
+
+ const callsTheApi = ( generator ) => {
+ return expect( generator.next( { success: true } ).value.type ).toEqual(
+ ACTIONS.apiFetch
+ );
+ };
+
+ const expectTest = ( hasCall, noCall ) => {
+ expect( hasCall ).toHaveBeenCalledTimes( 1 );
+ expect( noCall ).toHaveBeenCalledTimes( 0 );
+ };
+
+ const expectSuccess = ( onSuccess, onError ) => {
+ expectTest( onSuccess, onError );
+ };
+
+ const expectError = ( onSuccess, onError ) => {
+ expectTest( onError, onSuccess );
+ };
+
+ describe( 'downloadBlock', () => {
+ it( 'should throw error if the plugin has no assets', () => {
+ const onSuccess = jest.fn();
+ const onError = jest.fn();
+
+ const generator = downloadBlock(
+ {
+ assets: [],
+ },
+ onSuccess,
+ onError
+ );
+
+ // Move onto the onError callback
+ generator.next();
+
+ expectError( onSuccess, onError );
+ } );
+
+ it( 'should call on success function', () => {
+ const onSuccess = jest.fn();
+ const onError = jest.fn();
+
+ // The block is registered
+ getBlockTypeMock.mockReturnValue( [ item ] );
+
+ const generator = downloadBlock( blockPlugin, onSuccess, onError );
+
+ // Trigger the loading of assets
+ generator.next();
+
+ // Trigger the block check via getBlockTypes
+ generator.next();
+
+ expectSuccess( onSuccess, onError );
+ } );
+
+ it( 'should call on error when no blocks are returned', () => {
+ const onSuccess = jest.fn();
+ const onError = jest.fn();
+
+ // The block is not registered
+ getBlockTypeMock.mockReturnValue( [] );
+
+ const generator = downloadBlock( blockPlugin, onSuccess, onError );
+
+ // Trigger the loading of assets
+ generator.next();
+
+ //Complete
+ generator.next();
+
+ expectError( onSuccess, onError );
+ } );
+ } );
+
+ describe( 'installBlock', () => {
+ it( 'should install a block successfully', () => {
+ const onSuccess = jest.fn();
+ const onError = jest.fn();
+
+ const generator = installBlock( item, onSuccess, onError );
+
+ // It triggers API_FETCH that wraps @wordpress/api-fetch
+ callsTheApi( generator );
+
+ // It triggers ADD_INSTALLED_BLOCK_TYPE
+ expect( generator.next( { success: true } ).value.type ).toEqual(
+ ACTIONS.addInstalledBlockType
+ );
+
+ // Move on to success
+ generator.next();
+
+ expectSuccess( onSuccess, onError );
+ } );
+
+ it( 'should trigger error state when error is thrown', () => {
+ const onSuccess = jest.fn();
+ const onError = jest.fn();
+
+ const generator = installBlock( item, onSuccess, onError );
+
+ // It triggers API_FETCH that wraps @wordpress/api-fetch
+ callsTheApi( generator );
+
+ // Move on to error
+ generator.next();
+
+ expectError( onSuccess, onError );
+ } );
+ } );
+} );
diff --git a/packages/block-directory/src/style.scss b/packages/block-directory/src/style.scss
index 14f33e678ede45..e0286f8fc42ae0 100644
--- a/packages/block-directory/src/style.scss
+++ b/packages/block-directory/src/style.scss
@@ -5,3 +5,4 @@
@import "./components/downloadable-blocks-list/style.scss";
@import "./components/downloadable-blocks-panel/style.scss";
@import "./components/block-ratings/style.scss";
+@import "./components/downloadable-block-notice/style.scss";