+
diff --git a/packages/block-directory/src/components/downloadable-block-icon/test/index.js b/packages/block-directory/src/components/downloadable-block-icon/test/index.js
index 063f4a49fddab9..26e9c28e4cd9c5 100644
--- a/packages/block-directory/src/components/downloadable-block-icon/test/index.js
+++ b/packages/block-directory/src/components/downloadable-block-icon/test/index.js
@@ -1,41 +1,41 @@
/**
* External dependencies
*/
-import { shallow } from 'enzyme';
+import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
-import DownloadableBlockIcon from '../index';
+import DownloadableBlockIcon from '../';
const IMAGE_URL = 'https://ps.w.org/listicles/assets/icon-128x128.png';
describe( 'Downloadable Block Icon', () => {
describe( 'icon rendering', () => {
test( 'should render an
tag', () => {
- const wrapper = shallow(
+ const { container } = render(
);
- expect( wrapper ).toMatchSnapshot();
+ expect( container ).toMatchSnapshot();
} );
test( 'should render an
tag if icon URL has query string', () => {
- const wrapper = shallow(
+ const { container } = render(
);
- expect( wrapper ).toMatchSnapshot();
+ expect( container ).toMatchSnapshot();
} );
test( 'should render a
component', () => {
- const wrapper = shallow(
+ const { container } = render(
);
- expect( wrapper ).toMatchSnapshot();
+ expect( container ).toMatchSnapshot();
} );
} );
} );
diff --git a/packages/block-directory/src/components/downloadable-block-info/index.js b/packages/block-directory/src/components/downloadable-block-info/index.js
deleted file mode 100644
index aabfcfc050b243..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-info/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __, sprintf } from '@wordpress/i18n';
-import { decodeEntities } from '@wordpress/html-entities';
-import { Fragment } from '@wordpress/element';
-import { Icon, update, chartLine } from '@wordpress/icons';
-
-function DownloadableBlockInfo( {
- activeInstalls,
- description,
- humanizedUpdated,
-} ) {
- let activeInstallsString;
-
- if ( activeInstalls > 1000000 ) {
- activeInstallsString = sprintf(
- /* translators: %d: number of active installations. */
- __( '%d+ Million active installations' ),
- Math.floor( activeInstalls / 1000000 )
- );
- } else if ( 0 === activeInstalls ) {
- activeInstallsString = __( 'Less than 10 active installations' );
- } else {
- activeInstallsString = sprintf(
- /* translators: %d: number of active installations. */
- __( '%d+ active installations' ),
- activeInstalls
- );
- }
-
- return (
-
-
- { decodeEntities( description ) }
-
-
-
- { activeInstallsString }
-
-
-
- {
- // translators: %s: Humanized date of last update e.g: "2 months ago".
- sprintf( __( 'Updated %s' ), humanizedUpdated )
- }
-
-
- );
-}
-
-export default DownloadableBlockInfo;
diff --git a/packages/block-directory/src/components/downloadable-block-info/style.scss b/packages/block-directory/src/components/downloadable-block-info/style.scss
deleted file mode 100644
index fb63e381983cb8..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-info/style.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.block-directory-downloadable-block-info__content {
- margin: 0 0 $grid-unit-20;
- font-size: $default-font-size;
-}
-
-.block-directory-downloadable-block-info__meta {
- display: flex;
- align-items: center;
- margin-bottom: 2px;
- color: $gray-700;
- font-size: 12px;
-}
-
-.block-directory-downloadable-block-info__meta:last-child {
- margin-bottom: 0;
-}
-
-.block-directory-downloadable-block-info__icon {
- margin-right: 4px;
- fill: $gray-700;
-}
diff --git a/packages/block-directory/src/components/downloadable-block-info/test/index.js b/packages/block-directory/src/components/downloadable-block-info/test/index.js
deleted file mode 100644
index e5972f1e619914..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-info/test/index.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * External dependencies
- */
-import { shallow } from 'enzyme';
-
-/**
- * Internal dependencies
- */
-import DownloadableBlockInfo from '../index';
-
-describe( 'DownloadableBlockInfo', () => {
- const metaSelector = '.block-directory-downloadable-block-info__meta';
- describe( 'Active Installs Count', () => {
- it( 'should display the correct count for over a million installs', () => {
- const wrapper = shallow(
-
- );
-
- const count = wrapper.find( metaSelector ).first().text();
-
- expect( count ).toContain( '10+ Million' );
- } );
-
- it( 'should display the correct count for 0 installs', () => {
- const wrapper = shallow(
-
- );
-
- const count = wrapper.find( metaSelector ).first().text();
-
- expect( count ).toContain( 'Less than 10 active installations' );
- } );
-
- it( 'should display the correct count for 10+ and less than a Million installs', () => {
- const wrapper = shallow(
-
- );
-
- const count = wrapper.find( metaSelector ).first().text();
-
- expect( count ).toContain( '100+ active installations' );
- } );
- } );
-} );
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 8a88e8593f0adc..0d63cc3bcbc2d8 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,79 +1,162 @@
/**
* WordPress dependencies
*/
+import { __, _n, sprintf } from '@wordpress/i18n';
+import {
+ Button,
+ Spinner,
+ VisuallyHidden,
+ __unstableCompositeItem as CompositeItem,
+} from '@wordpress/components';
+import { createInterpolateElement } from '@wordpress/element';
+import { decodeEntities } from '@wordpress/html-entities';
+import { getBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
-import DownloadableBlockAuthorInfo from '../downloadable-block-author-info';
-import DownloadableBlockHeader from '../downloadable-block-header';
-import DownloadableBlockInfo from '../downloadable-block-info';
+import BlockRatings from '../block-ratings';
+import DownloadableBlockIcon from '../downloadable-block-icon';
import DownloadableBlockNotice from '../downloadable-block-notice';
import { store as blockDirectoryStore } from '../../store';
-export default function DownloadableBlockListItem( { item, onClick } ) {
- const { isLoading, isInstallable } = useSelect(
+// Return the appropriate block item label, given the block data and status.
+function getDownloadableBlockLabel(
+ { title, rating, ratingCount },
+ { hasNotice, isInstalled, isInstalling }
+) {
+ const stars = Math.round( rating / 0.5 ) * 0.5;
+
+ if ( ! isInstalled && hasNotice ) {
+ /* translators: %1$s: block title */
+ return sprintf( 'Retry installing %s.', decodeEntities( title ) );
+ }
+
+ if ( isInstalled ) {
+ /* translators: %1$s: block title */
+ return sprintf( 'Add %s.', decodeEntities( title ) );
+ }
+
+ if ( isInstalling ) {
+ /* translators: %1$s: block title */
+ return sprintf( 'Installing %s.', decodeEntities( title ) );
+ }
+
+ // No ratings yet, just use the title.
+ if ( ratingCount < 1 ) {
+ /* translators: %1$s: block title */
+ return sprintf( 'Install %s.', decodeEntities( title ) );
+ }
+
+ return sprintf(
+ /* translators: %1$s: block title, %2$s: average rating, %3$s: total ratings count. */
+ _n(
+ 'Install %1$s. %2$s stars with %3$s review.',
+ 'Install %1$s. %2$s stars with %3$s reviews.',
+ ratingCount
+ ),
+ decodeEntities( title ),
+ stars,
+ ratingCount
+ );
+}
+
+function DownloadableBlockListItem( { composite, item, onClick } ) {
+ const { author, description, icon, rating, title } = item;
+ // getBlockType returns a block object if this block exists, or null if not.
+ const isInstalled = !! getBlockType( item.name );
+
+ const { hasNotice, isInstalling, isInstallable } = useSelect(
( select ) => {
- const { isInstalling, getErrorNoticeForBlock } = select(
- blockDirectoryStore
- );
+ const {
+ getErrorNoticeForBlock,
+ isInstalling: isBlockInstalling,
+ } = select( blockDirectoryStore );
const notice = getErrorNoticeForBlock( item.id );
const hasFatal = notice && notice.isFatal;
return {
- isLoading: isInstalling( item.id ),
+ hasNotice: !! notice,
+ isInstalling: isBlockInstalling( item.id ),
isInstallable: ! hasFatal,
};
},
[ item ]
);
- const {
- icon,
- title,
- description,
- rating,
- activeInstalls,
- ratingCount,
- author,
- humanizedUpdated,
- authorBlockCount,
- authorBlockRating,
- } = item;
+ let statusText = '';
+ if ( isInstalled ) {
+ statusText = __( 'Installed!' );
+ } else if ( isInstalling ) {
+ statusText = __( 'Installing…' );
+ }
return (
-
-
-
-
-
-
-
+
{
+ event.preventDefault();
+ onClick();
+ } }
+ isBusy={ isInstalling }
+ disabled={ isInstalling || ! isInstallable }
+ label={ getDownloadableBlockLabel( item, {
+ hasNotice,
+ isInstalled,
+ isInstalling,
+ } ) }
+ showTooltip={ true }
+ tooltipPosition="top center"
+ >
+
+
+ { isInstalling ? (
+
+
+
+ ) : (
+
+ ) }
+
+
+
+ { createInterpolateElement(
+ sprintf(
+ /* translators: %1$s: block title, %2$s: author name. */
+ __( '%1$s by %2$s ' ),
+ decodeEntities( title ),
+ author
+ ),
+ {
+ span: (
+
+ ),
+ }
+ ) }
+
+ { hasNotice ? (
+
+ ) : (
+ <>
+
+ { !! statusText
+ ? statusText
+ : decodeEntities( description ) }
+
+ { isInstallable &&
+ ! ( isInstalled || isInstalling ) && (
+
+ { __( 'Install block' ) }
+
+ ) }
+ >
+ ) }
+
+
);
}
+
+export default DownloadableBlockListItem;
diff --git a/packages/block-directory/src/components/downloadable-block-list-item/style.scss b/packages/block-directory/src/components/downloadable-block-list-item/style.scss
index c2de59768c54fc..61b292788a03ae 100644
--- a/packages/block-directory/src/components/downloadable-block-list-item/style.scss
+++ b/packages/block-directory/src/components/downloadable-block-list-item/style.scss
@@ -1,61 +1,78 @@
.block-directory-downloadable-block-list-item {
+ padding: $grid-unit-15;
width: 100%;
- padding: 0;
- margin: 0;
- display: flex;
- flex-direction: row;
- font-size: $default-font-size;
- color: $gray-900;
- align-items: flex-start;
- justify-content: center;
- background: transparent;
- word-break: break-word;
- border-top: $border-width solid $gray-300;
- border-bottom: $border-width solid $gray-300;
- transition: all 0.05s ease-in-out;
- @include reduce-motion("transition");
- position: relative;
+ height: auto;
text-align: left;
- overflow: hidden;
+ display: grid;
+ grid-template-columns: auto 1fr;
- & + .block-directory-downloadable-block-list-item {
- border-top: none;
+ &:hover {
+ box-shadow: 0 0 0 $border-width-focus var(--wp-admin-theme-color);
+ }
+
+ &.is-busy {
+ background: transparent;
+
+ .block-directory-downloadable-block-list-item__author {
+ border: 0;
+ clip: rect(1px, 1px, 1px, 1px);
+ -webkit-clip-path: inset(50%);
+ clip-path: inset(50%);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ word-wrap: normal !important;
+ }
}
-}
-.block-directory-downloadable-block-list-item:last-child:not(:only-of-type) {
- border-top: 0;
+ &:disabled,
+ &[aria-disabled] {
+ opacity: 1;
+ }
}
-.block-directory-downloadable-block-list-item:last-child {
- border-bottom: 0;
+.block-directory-downloadable-block-list-item__icon {
+ position: relative;
+ margin-right: $grid-unit-20;
+ align-self: flex-start;
+
+ .block-directory-downloadable-block-list-item__spinner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: rgba(255, 255, 255, 0.75);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
}
-.block-directory-downloadable-block-list-item__panel {
- display: flex;
- flex-grow: 1;
- flex-direction: column;
+.block-directory-block-ratings {
+ display: block;
+ margin-top: $grid-unit-05;
}
-.block-directory-downloadable-block-list-item__header {
- display: flex;
- flex-direction: column;
- padding: $grid-unit-20 $grid-unit-20 0;
+.block-directory-downloadable-block-list-item__details {
+ color: $gray-900;
}
-.block-directory-downloadable-block-list-item__body {
- display: flex;
- flex-direction: column;
- padding: $grid-unit-20;
+.block-directory-downloadable-block-list-item__title {
+ display: block;
+ font-weight: 600;
}
-.block-directory-downloadable-block-list-item__footer {
- display: flex;
- flex-direction: column;
- padding: $grid-unit-20;
- background-color: $gray-100;
+.block-directory-downloadable-block-list-item__author {
+ display: block;
+ margin-top: $grid-unit-05;
+ font-weight: normal;
}
-.block-directory-downloadable-block-list-item__content {
- color: $gray-700;
+.block-directory-downloadable-block-list-item__desc {
+ display: block;
+ margin-top: $grid-unit-10;
}
diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap
deleted file mode 100644
index 59ec11f93e6dab..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-list-item/test/__snapshots__/index.js.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DownloadableBlockListItem should render a block item 1`] = `
-
-
-
-
-
-
-
-`;
diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js
deleted file mode 100644
index f54e9e81b1bc2c..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-list-item/test/fixtures/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-export const item = {
- 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',
-};
diff --git a/packages/block-directory/src/components/downloadable-block-list-item/test/index.js b/packages/block-directory/src/components/downloadable-block-list-item/test/index.js
index ef96da905e0830..8d9b1b60401e42 100644
--- a/packages/block-directory/src/components/downloadable-block-list-item/test/index.js
+++ b/packages/block-directory/src/components/downloadable-block-list-item/test/index.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { shallow } from 'enzyme';
+import { render, fireEvent } from '@testing-library/react';
/**
* WordPress dependencies
@@ -11,9 +11,8 @@ import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
-import DownloadableBlockListItem from '../index';
-import DownloadableBlockHeader from '../../downloadable-block-header';
-import { item } from './fixtures';
+import DownloadableBlockListItem from '../';
+import { plugin } from '../../test/fixtures';
jest.mock( '@wordpress/data/src/components/use-select', () => {
// This allows us to tweak the returned value on each test
@@ -24,26 +23,58 @@ jest.mock( '@wordpress/data/src/components/use-select', () => {
describe( 'DownloadableBlockListItem', () => {
it( 'should render a block item', () => {
useSelect.mockImplementation( () => ( {
- isLoading: false,
+ isInstalling: false,
isInstallable: true,
} ) );
- const wrapper = shallow(
-
+ const { queryByText } = render(
+
);
+ const author = queryByText( `by ${ plugin.author }` );
+ const description = queryByText( plugin.description );
+ expect( author ).not.toBeNull();
+ expect( description ).not.toBeNull();
+ } );
+
+ it( 'should show installing status when installing the block', () => {
+ useSelect.mockImplementation( () => ( {
+ isInstalling: true,
+ isInstallable: true,
+ } ) );
- expect( wrapper ).toMatchSnapshot();
+ const { queryByText } = render(
+
+ );
+ const statusLabel = queryByText( 'Installing…' );
+ expect( statusLabel ).not.toBeNull();
+ } );
+
+ it( "should be disabled when a plugin can't be installed", () => {
+ useSelect.mockImplementation( () => ( {
+ isInstalling: false,
+ isInstallable: false,
+ } ) );
+
+ const { getByRole } = render(
+
+ );
+ const button = getByRole( 'option' );
+ expect( button.disabled ).toBe( true );
+ expect( button.getAttribute( 'aria-disabled' ) ).toBe( 'true' );
} );
it( 'should try to install the block plugin', () => {
+ useSelect.mockImplementation( () => ( {
+ isInstalling: false,
+ isInstallable: true,
+ } ) );
const onClick = jest.fn();
- const wrapper = shallow(
-
+ const { getByRole } = render(
+
);
- wrapper
- .find( DownloadableBlockHeader )
- .simulate( 'click', { event: {} } );
+ const button = getByRole( 'option' );
+ fireEvent.click( button );
expect( onClick ).toHaveBeenCalledTimes( 1 );
} );
diff --git a/packages/block-directory/src/components/downloadable-block-notice/index.js b/packages/block-directory/src/components/downloadable-block-notice/index.js
index f24545954e63ae..539c3d59e7152f 100644
--- a/packages/block-directory/src/components/downloadable-block-notice/index.js
+++ b/packages/block-directory/src/components/downloadable-block-notice/index.js
@@ -2,7 +2,6 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { Button, Notice } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
/**
@@ -10,7 +9,7 @@ import { useSelect } from '@wordpress/data';
*/
import { store as blockDirectoryStore } from '../../store';
-export const DownloadableBlockNotice = ( { block, onClick } ) => {
+export const DownloadableBlockNotice = ( { block } ) => {
const errorNotice = useSelect(
( select ) =>
select( blockDirectoryStore ).getErrorNoticeForBlock( block.id ),
@@ -22,29 +21,14 @@ export const DownloadableBlockNotice = ( { block, onClick } ) => {
}
return (
-
+
{ errorNotice.message }
+ { errorNotice.isFatal
+ ? ' ' + __( 'Try reloading the page.' )
+ : null }
-
{
- if ( errorNotice.isFatal ) {
- window.location.reload();
- return false;
- }
-
- onClick( block );
- } }
- >
- { errorNotice.isFatal ? __( 'Reload' ) : __( 'Retry' ) }
-
-
+
);
};
diff --git a/packages/block-directory/src/components/downloadable-block-notice/style.scss b/packages/block-directory/src/components/downloadable-block-notice/style.scss
index aeb5b009640003..44c876cc0610a5 100644
--- a/packages/block-directory/src/components/downloadable-block-notice/style.scss
+++ b/packages/block-directory/src/components/downloadable-block-notice/style.scss
@@ -1,8 +1,9 @@
.block-directory-downloadable-block-notice {
- margin: 0 0 16px;
+ margin: $grid-unit-10 0 0;
+ color: $alert-red;
}
.block-directory-downloadable-block-notice__content {
- padding-right: 12px;
- margin-bottom: 8px;
+ padding-right: $grid-unit-15;
+ margin-bottom: $grid-unit-10;
}
diff --git a/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap
deleted file mode 100644
index 0e283e5ec1a1a4..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-notice/test/__snapshots__/index.js.snap
+++ /dev/null
@@ -1,22 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DownloadableBlockNotice Rendering should return something when there are error notices 1`] = `
-
-
- Plugin not found.
-
-
- Retry
-
-
-`;
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
deleted file mode 100644
index c91c7b0c586958..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-notice/test/fixtures/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-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
deleted file mode 100644
index 32b3bb8c6023a9..00000000000000
--- a/packages/block-directory/src/components/downloadable-block-notice/test/index.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * External dependencies
- */
-import { shallow } from 'enzyme';
-
-/**
- * WordPress dependencies
- */
-import { Button } from '@wordpress/components';
-import { useSelect } from '@wordpress/data';
-
-/**
- * Internal dependencies
- */
-import { DownloadableBlockNotice } from '../index';
-import { plugin } from './fixtures';
-
-jest.mock( '@wordpress/data/src/components/use-select', () => {
- // This allows us to tweak the returned value on each test
- const mock = jest.fn();
- return mock;
-} );
-
-describe( 'DownloadableBlockNotice', () => {
- describe( 'Rendering', () => {
- it( 'should return null when there are no error notices', () => {
- useSelect.mockImplementation( () => false );
- const wrapper = shallow(
-
- );
- expect( wrapper.isEmptyRender() ).toBe( true );
- } );
-
- it( 'should return something when there are error notices', () => {
- useSelect.mockImplementation( () => {
- return { message: 'Plugin not found.', isFatal: false };
- } );
- const wrapper = shallow(
-
- );
- expect( wrapper ).toMatchSnapshot();
- } );
- } );
-
- describe( 'Behavior', () => {
- it( 'should trigger the callback on button click', () => {
- useSelect.mockImplementation( () => 'Plugin not found.' );
- const onClick = jest.fn();
- const wrapper = shallow(
-
- );
-
- 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 917f8becb33502..02561a087b5681 100644
--- a/packages/block-directory/src/components/downloadable-blocks-list/index.js
+++ b/packages/block-directory/src/components/downloadable-blocks-list/index.js
@@ -6,6 +6,11 @@ import { noop } from 'lodash';
/**
* WordPress dependencies
*/
+import { __ } from '@wordpress/i18n';
+import {
+ __unstableComposite as Composite,
+ __unstableUseCompositeState as useCompositeState,
+} from '@wordpress/components';
import { getBlockType } from '@wordpress/blocks';
import { useDispatch } from '@wordpress/data';
@@ -16,6 +21,7 @@ import DownloadableBlockListItem from '../downloadable-block-list-item';
import { store as blockDirectoryStore } from '../../store';
function DownloadableBlocksList( { items, onHover = noop, onSelect } ) {
+ const composite = useCompositeState();
const { installBlockType } = useDispatch( blockDirectoryStore );
if ( ! items.length ) {
@@ -23,16 +29,17 @@ function DownloadableBlocksList( { items, onHover = noop, onSelect } ) {
}
return (
- /*
- * Disable reason: The `list` ARIA role is redundant but
- * Safari+VoiceOver won't announce the list otherwise.
- */
- /* eslint-disable jsx-a11y/no-redundant-roles */
-
+
{ items.map( ( item ) => {
return (
{
// Check if the block is registered (`getBlockType`
// will return an object). If so, insert the block.
@@ -48,12 +55,12 @@ function DownloadableBlocksList( { items, onHover = noop, onSelect } ) {
}
onHover( null );
} }
+ onHover={ onHover }
item={ item }
/>
);
} ) }
-
- /* eslint-enable jsx-a11y/no-redundant-roles */
+
);
}
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/style.scss b/packages/block-directory/src/components/downloadable-blocks-list/style.scss
deleted file mode 100644
index f879d3d2346d79..00000000000000
--- a/packages/block-directory/src/components/downloadable-blocks-list/style.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.block-directory-downloadable-blocks-list {
- list-style: none;
- margin: 0;
- overflow: hidden;
- display: flex;
- flex-wrap: wrap;
-}
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap b/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap
deleted file mode 100644
index f7d6a077d7e520..00000000000000
--- a/packages/block-directory/src/components/downloadable-blocks-list/test/__snapshots__/index.js.snap
+++ /dev/null
@@ -1,57 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DownloadableBlocksList List rendering should render plugins items into the list 1`] = `
-
-`;
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
index c93197b5d4856b..1926a700a8d126 100644
--- a/packages/block-directory/src/components/downloadable-blocks-list/test/index.js
+++ b/packages/block-directory/src/components/downloadable-blocks-list/test/index.js
@@ -1,13 +1,24 @@
/**
* External dependencies
*/
-import { shallow } from 'enzyme';
+import { render } from '@testing-library/react';
+
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
-import DownloadableBlocksList from '../index';
-import { items } from './fixtures';
+import DownloadableBlocksList from '../';
+import { items } from '../../test/fixtures';
+
+jest.mock( '@wordpress/data/src/components/use-select', () => {
+ // This allows us to tweak the returned value on each test
+ const mock = jest.fn();
+ return mock;
+} );
jest.mock( '@wordpress/data/src/components/use-dispatch', () => ( {
useDispatch: () => ( { installBlockType: jest.fn() } ),
@@ -15,28 +26,34 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => ( {
describe( 'DownloadableBlocksList', () => {
describe( 'List rendering', () => {
+ useSelect.mockImplementation( () => ( {
+ isLoading: false,
+ isInstallable: true,
+ } ) );
+
it( 'should render and empty list', () => {
- const wrapper = shallow(
+ const { container } = render(
);
- expect( wrapper.isEmptyRender() ).toBe( true );
+
+ expect( container.firstChild ).toBe( null );
} );
it( 'should render plugins items into the list', () => {
- const wrapper = shallow(
+ const { getAllByRole } = render(
);
- expect( wrapper ).toMatchSnapshot();
+ const downloadableBlocks = getAllByRole( 'option' );
+
+ expect( downloadableBlocks ).toHaveLength( items.length );
} );
} );
} );
diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/index.js b/packages/block-directory/src/components/downloadable-blocks-panel/index.js
index e30117b5407244..f075aa1e5c52c1 100644
--- a/packages/block-directory/src/components/downloadable-blocks-panel/index.js
+++ b/packages/block-directory/src/components/downloadable-blocks-panel/index.js
@@ -1,80 +1,71 @@
/**
* WordPress dependencies
*/
-import { Fragment } from '@wordpress/element';
-import { compose, useDebounce } from '@wordpress/compose';
-import { withSelect } from '@wordpress/data';
-import { __, _n, sprintf } from '@wordpress/i18n';
+import { __ } from '@wordpress/i18n';
import { Spinner } from '@wordpress/components';
-import { speak } from '@wordpress/a11y';
+import { compose } from '@wordpress/compose';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
+import { withSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import DownloadableBlocksList from '../downloadable-blocks-list';
+import DownloadableBlocksInserterPanel from './inserter-panel';
+import DownloadableBlocksNoResults from './no-results';
import { store as blockDirectoryStore } from '../../store';
function DownloadableBlocksPanel( {
downloadableItems,
onSelect,
onHover,
+ hasLocalBlocks,
hasPermission,
isLoading,
- isWaiting,
+ isTyping,
} ) {
- const debouncedSpeak = useDebounce( speak, 500 );
-
- if ( false === hasPermission ) {
- debouncedSpeak( __( 'No blocks found in your library.' ) );
+ if ( typeof hasPermission === 'undefined' || isLoading || isTyping ) {
return (
-
- { __( 'No blocks found in your library.' ) }
-
+ <>
+ { hasPermission && ! hasLocalBlocks && (
+ <>
+
+ { __(
+ 'No results available from your installed blocks.'
+ ) }
+
+
+ >
+ ) }
+
+
+
+ >
);
}
- if ( typeof hasPermission === 'undefined' || isLoading || isWaiting ) {
- return (
-
-
-
- );
- }
+ if ( false === hasPermission ) {
+ if ( ! hasLocalBlocks ) {
+ return ;
+ }
- if ( ! downloadableItems.length ) {
- return (
-
- { __( 'No blocks found in your library.' ) }
-
- );
+ return null;
}
- const resultsFoundMessage = sprintf(
- /* translators: %s: number of available blocks. */
- _n(
- 'No blocks found in your library. We did find %d block available for download.',
- 'No blocks found in your library. We did find %d blocks available for download.',
- downloadableItems.length
- ),
- downloadableItems.length
- );
-
- debouncedSpeak( resultsFoundMessage );
- return (
-
-
- { __(
- 'No blocks found in your library. These blocks can be downloaded and installed:'
- ) }
-
+ return !! downloadableItems.length ? (
+
-
+
+ ) : (
+ ! hasLocalBlocks &&
);
}
diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js b/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js
new file mode 100644
index 00000000000000..133c39fb6c980a
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-blocks-panel/inserter-panel.js
@@ -0,0 +1,55 @@
+/**
+ * WordPress dependencies
+ */
+import { __, _n, sprintf } from '@wordpress/i18n';
+import { useEffect } from '@wordpress/element';
+import { speak } from '@wordpress/a11y';
+
+function DownloadableBlocksInserterPanel( {
+ children,
+ downloadableItems,
+ hasLocalBlocks,
+} ) {
+ const count = downloadableItems.length;
+ useEffect( () => {
+ speak(
+ sprintf(
+ /* translators: %d: number of available blocks. */
+ _n(
+ '%d additional block is available to install.',
+ '%d additional blocks are available to install.',
+ count
+ ),
+ count
+ )
+ );
+ }, [ count ] );
+
+ return (
+ <>
+ { ! hasLocalBlocks && (
+
+ { __( 'No results available from your installed blocks.' ) }
+
+ ) }
+
+
+
+
+
+
+ { __( 'Available to install' ) }
+
+
+ { __(
+ 'Select a block to install and add it to your post.'
+ ) }
+
+
+ { children }
+
+ >
+ );
+}
+
+export default DownloadableBlocksInserterPanel;
diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js b/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js
new file mode 100644
index 00000000000000..0b97b4abe0b9b5
--- /dev/null
+++ b/packages/block-directory/src/components/downloadable-blocks-panel/no-results.js
@@ -0,0 +1,19 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Icon, blockDefault } from '@wordpress/icons';
+
+function DownloadableBlocksNoResults() {
+ return (
+
+
+
{ __( 'No results found.' ) }
+
+ );
+}
+
+export default DownloadableBlocksNoResults;
diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/style.scss b/packages/block-directory/src/components/downloadable-blocks-panel/style.scss
index f6b9b5bde951f6..ff3fdb9ea8e319 100644
--- a/packages/block-directory/src/components/downloadable-blocks-panel/style.scss
+++ b/packages/block-directory/src/components/downloadable-blocks-panel/style.scss
@@ -1,20 +1,37 @@
-.block-directory-downloadable-blocks-panel__description {
- font-style: italic;
+.block-directory-downloadable-blocks-panel {
padding: $grid-unit-20;
- margin: 0;
- text-align: left;
- color: $gray-700;
+
+ &.has-blocks-loading {
+ $no-result-padding: $grid-unit-20 * 7;
+ font-style: normal;
+ padding: 0;
+ margin: $no-result-padding 0;
+ text-align: center;
+ color: $gray-700;
+
+ .components-spinner {
+ float: inherit;
+ }
+ }
}
-.block-directory-downloadable-blocks-panel__description.has-no-results {
- $no-result-padding: $grid-unit-20 * 7;
- font-style: normal;
- padding: 0;
- margin: $no-result-padding 0;
- text-align: center;
+.block-directory-downloadable-blocks-panel__no-local {
+ margin: $grid-unit-60 0;
+ padding: 0 $grid-unit-40 * 2;
color: $gray-700;
- .components-spinner {
- float: inherit;
- }
+ text-align: center;
+}
+
+.block-directory-downloadable-blocks-panel__title {
+ margin: 0 0 $grid-unit-05;
+ font-size: 14px;
+}
+
+.block-directory-downloadable-blocks-panel__description {
+ margin-top: 0;
+}
+
+.block-directory-downloadable-blocks-panel button {
+ margin-top: $grid-unit-05;
}
diff --git a/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js b/packages/block-directory/src/components/test/fixtures/index.js
similarity index 79%
rename from packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js
rename to packages/block-directory/src/components/test/fixtures/index.js
index 899f7f663bac08..1dc66947f09e5b 100644
--- a/packages/block-directory/src/components/downloadable-blocks-list/test/fixtures/index.js
+++ b/packages/block-directory/src/components/test/fixtures/index.js
@@ -6,16 +6,16 @@ export const plugin = {
id: 'boxer-block',
icon: 'block-default',
rating: 5,
- rating_count: 1,
- active_installs: 0,
- author_block_rating: 5,
- author_block_count: '1',
+ ratingCount: 1,
+ activeInstalls: 0,
+ authorBlockRating: 5,
+ authorBlockCount: '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',
+ humanizedUpdated: '3 months ago',
};
export const items = [
diff --git a/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js b/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js
index fa96d684791c41..d6528715e03437 100644
--- a/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js
+++ b/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js
@@ -16,7 +16,6 @@ import DownloadableBlocksPanel from '../../components/downloadable-blocks-panel'
function InserterMenuDownloadableBlocksPanel() {
const [ debouncedFilterValue, setFilterValue ] = useState( '' );
-
const debouncedSetFilterValue = debounce( setFilterValue, 400 );
return (
@@ -28,21 +27,22 @@ function InserterMenuDownloadableBlocksPanel() {
hasItems,
rootClientId,
} ) => {
- if ( hasItems || ! filterValue ) {
- return null;
- }
-
if ( debouncedFilterValue !== filterValue ) {
debouncedSetFilterValue( filterValue );
}
+ if ( ! debouncedFilterValue ) {
+ return null;
+ }
+
return (
);
} }
diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js
index ee143492ab7b56..e6b4867c930f37 100644
--- a/packages/block-directory/src/store/actions.js
+++ b/packages/block-directory/src/store/actions.js
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { controls } from '@wordpress/data';
import { apiFetch } from '@wordpress/data-controls';
import { store as noticesStore } from '@wordpress/notices';
@@ -96,6 +96,19 @@ export function* installBlockType( block ) {
);
}
+ yield controls.dispatch(
+ noticesStore,
+ 'createInfoNotice',
+ sprintf(
+ // translators: %s is the block title.
+ __( 'Block %s installed and added.' ),
+ block.title
+ ),
+ {
+ speak: true,
+ type: 'snackbar',
+ }
+ );
success = true;
} catch ( error ) {
let message = error.message || __( 'An error occurred.' );
@@ -119,6 +132,10 @@ export function* installBlockType( block ) {
}
yield setErrorNotice( id, message, isFatal );
+ yield controls.dispatch( noticesStore, 'createErrorNotice', message, {
+ speak: true,
+ isDismissible: true,
+ } );
}
yield setIsInstalling( block.id, false );
return success;
diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js
index 0b40494b44d838..d076b8752bfa62 100644
--- a/packages/block-directory/src/store/test/actions.js
+++ b/packages/block-directory/src/store/test/actions.js
@@ -90,7 +90,13 @@ describe( 'actions', () => {
type: '@@data/SELECT',
} );
- expect( generator.next( [ block ] ).value ).toEqual( {
+ expect( generator.next( [ block ] ).value ).toMatchObject( {
+ type: '@@data/DISPATCH',
+ actionName: 'createInfoNotice',
+ storeKey: noticesStore,
+ } );
+
+ expect( generator.next().value ).toEqual( {
type: 'SET_INSTALLING_BLOCK',
blockId: block.id,
isInstalling: false,
@@ -152,7 +158,13 @@ describe( 'actions', () => {
type: '@@data/SELECT',
} );
- expect( generator.next( [ inactiveBlock ] ).value ).toEqual( {
+ expect( generator.next( [ inactiveBlock ] ).value ).toMatchObject( {
+ type: '@@data/DISPATCH',
+ actionName: 'createInfoNotice',
+ storeKey: noticesStore,
+ } );
+
+ expect( generator.next().value ).toEqual( {
type: 'SET_INSTALLING_BLOCK',
blockId: inactiveBlock.id,
isInstalling: false,
@@ -196,6 +208,12 @@ describe( 'actions', () => {
blockId: block.id,
} );
+ expect( generator.next().value ).toMatchObject( {
+ type: '@@data/DISPATCH',
+ actionName: 'createErrorNotice',
+ storeKey: noticesStore,
+ } );
+
expect( generator.next().value ).toEqual( {
type: 'SET_INSTALLING_BLOCK',
blockId: block.id,
diff --git a/packages/block-directory/src/style.scss b/packages/block-directory/src/style.scss
index 82cc67442872a4..8d2ab7148756ba 100644
--- a/packages/block-directory/src/style.scss
+++ b/packages/block-directory/src/style.scss
@@ -1,11 +1,7 @@
@import "./components/block-ratings/style.scss";
@import "./components/compact-list/style.scss";
-@import "./components/downloadable-block-author-info/style.scss";
-@import "./components/downloadable-block-header/style.scss";
@import "./components/downloadable-block-icon/style.scss";
-@import "./components/downloadable-block-info/style.scss";
@import "./components/downloadable-block-list-item/style.scss";
@import "./components/downloadable-block-notice/style.scss";
-@import "./components/downloadable-blocks-list/style.scss";
@import "./components/downloadable-blocks-panel/style.scss";
@import "./plugins/installed-blocks-pre-publish-panel/style.scss";
diff --git a/packages/e2e-test-utils/src/inserter.js b/packages/e2e-test-utils/src/inserter.js
index 29a5d5d8444091..4c907efab7a18b 100644
--- a/packages/e2e-test-utils/src/inserter.js
+++ b/packages/e2e-test-utils/src/inserter.js
@@ -208,13 +208,13 @@ export async function insertBlockDirectoryBlock( searchTerm ) {
// Grab the first block in the list
const insertButton = await page.waitForSelector(
- '.block-directory-downloadable-blocks-list li:first-child button'
+ '.block-directory-downloadable-blocks-list button:first-child'
);
await insertButton.click();
await page.waitForFunction(
() =>
! document.body.querySelector(
- '.block-directory-downloadable-blocks-list li:first-child button.is-busy'
+ '.block-directory-downloadable-blocks-list button:first-child.is-busy'
)
);
await focusSelectedBlock();
diff --git a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js
index dde952f6274774..a659716602715c 100644
--- a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js
+++ b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js
@@ -171,7 +171,9 @@ describe( 'adding blocks from block directory', () => {
document.querySelector( '.block-editor-inserter__main-area' )
.innerHTML
);
- expect( selectorContent ).toContain( 'has-no-results' );
+ expect( selectorContent ).toContain(
+ 'block-editor-inserter__no-results'
+ );
} );
it( 'Should be able to add (the first) block.', async () => {