Skip to content

Commit

Permalink
Block Editor: Include reusable blocks in announced inserter search re…
Browse files Browse the repository at this point in the history
…sults (#22279)

* Block Editor: Refactor BlockList test to avoid relative paths

* Block Editor: Refactor Inserter BlockList test to use testing-library

* Block Editor: Include reusable blocks in announced inserter search results

* Block Editor: Update lifecycle dependencies to use filteredItems

* Block Editor: Announce inserter results in response to filter change
  • Loading branch information
aduth authored May 12, 2020
1 parent 8676302 commit 89e21cf
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 73 deletions.
17 changes: 4 additions & 13 deletions packages/block-editor/src/components/inserter/block-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ];

const MAX_SUGGESTED_ITEMS = 9;

function InserterBlockList( {
export function InserterBlockList( {
rootClientId,
onInsert,
onHover,
Expand Down Expand Up @@ -168,22 +168,13 @@ function InserterBlockList( {

// Announce search results on change
useEffect( () => {
const resultCount = Object.keys( itemsPerCategory ).reduce(
( accumulator, currentCategorySlug ) => {
return (
accumulator + itemsPerCategory[ currentCategorySlug ].length
);
},
0
);

const resultsFoundMessage = sprintf(
/* translators: %d: number of results. */
_n( '%d result found.', '%d results found.', resultCount ),
resultCount
_n( '%d result found.', '%d results found.', filteredItems.length ),
filteredItems.length
);
debouncedSpeak( resultsFoundMessage );
}, [ itemsPerCategory, debouncedSpeak ] );
}, [ filterValue, debouncedSpeak ] );

const hasItems = ! isEmpty( filteredItems );
const hasChildItems = childItems.length > 0;
Expand Down
170 changes: 110 additions & 60 deletions packages/block-editor/src/components/inserter/test/block-list.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,48 @@
/**
* External dependencies
*/
import TestUtils, { act } from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import { render, fireEvent } from '@testing-library/react';

/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import InserterBlockList from '../block-list';
import useSelect from '../../../../../data/src/components/use-select';
import { InserterBlockList as BaseInserterBlockList } from '../block-list';
import items, { categories, collections } from './fixtures';

jest.mock( '../../../../../data/src/components/use-select', () => {
// This allows us to tweaak the returned value on each test
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( '../../../../../data/src/components/use-dispatch', () => {
jest.mock( '@wordpress/data/src/components/use-dispatch', () => {
return {
useDispatch: () => ( {} ),
};
} );

const getWrapperForProps = ( propOverrides ) => {
let wrapper;
act( () => {
wrapper = TestUtils.renderIntoDocument(
<InserterBlockList { ...propOverrides } />
);
} );
const debouncedSpeak = jest.fn();

return wrapper;
};

const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => {
const wrapper = getWrapperForProps( propOverrides );
// eslint-disable-next-line react/no-find-dom-node
return ReactDOM.findDOMNode( wrapper );
};
function InserterBlockList( props ) {
return (
<BaseInserterBlockList debouncedSpeak={ debouncedSpeak } { ...props } />
);
}

const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => {
const element = initializeMenuDefaultStateAndReturnElement( propOverrides );
const activeTabs = element.querySelectorAll(
const initializeAllClosedMenuState = ( propOverrides ) => {
const result = render( <InserterBlockList { ...propOverrides } /> );
const activeTabs = result.container.querySelectorAll(
'.components-panel__body.is-opened button.components-panel__body-toggle'
);
activeTabs.forEach( ( tab ) => {
TestUtils.Simulate.click( tab );
fireEvent.click( tab );
} );
return element;
return result;
};

const assertNoResultsMessageToBePresent = ( element ) => {
Expand All @@ -67,6 +61,8 @@ const assertNoResultsMessageNotToBePresent = ( element ) => {

describe( 'InserterMenu', () => {
beforeEach( () => {
debouncedSpeak.mockClear();

useSelect.mockImplementation( () => ( {
categories,
collections,
Expand All @@ -81,21 +77,21 @@ describe( 'InserterMenu', () => {
collections,
items: noItems,
} ) );
const element = initializeMenuDefaultStateAndReturnElement( {
filterValue: 'random',
} );
const visibleBlocks = element.querySelector(
const { container } = render(
<InserterBlockList filterValue="random" />
);
const visibleBlocks = container.querySelector(
'.block-editor-block-types-list__item'
);

expect( visibleBlocks ).toBe( null );

assertNoResultsMessageToBePresent( element );
assertNoResultsMessageToBePresent( container );
} );

it( 'should show only high utility items in the suggested tab', () => {
const element = initializeMenuDefaultStateAndReturnElement();
const firstPanel = element.querySelector(
const { container } = render( <InserterBlockList /> );
const firstPanel = container.querySelector(
'.block-editor-inserter__panel-content'
);
const visibleBlocks = firstPanel.querySelectorAll(
Expand All @@ -108,11 +104,11 @@ describe( 'InserterMenu', () => {
} );

it( 'should show items from the embed category in the embed tab', () => {
const element = initializeAllClosedMenuStateAndReturnElement();
const embedTabContent = element.querySelectorAll(
const { container } = initializeAllClosedMenuState();
const embedTabContent = container.querySelectorAll(
'.block-editor-inserter__panel-content'
)[ 4 ];
const embedTabTitle = element.querySelectorAll(
const embedTabTitle = container.querySelectorAll(
'.block-editor-inserter__panel-title'
)[ 4 ];
const blocks = embedTabContent.querySelectorAll(
Expand All @@ -124,15 +120,15 @@ describe( 'InserterMenu', () => {
expect( blocks[ 0 ].textContent ).toBe( 'YouTube' );
expect( blocks[ 1 ].textContent ).toBe( 'A Text Embed' );

assertNoResultsMessageNotToBePresent( element );
assertNoResultsMessageNotToBePresent( container );
} );

it( 'should show reusable items in the reusable tab', () => {
const element = initializeAllClosedMenuStateAndReturnElement();
const reusableTabContent = element.querySelectorAll(
const { container } = initializeAllClosedMenuState();
const reusableTabContent = container.querySelectorAll(
'.block-editor-inserter__panel-content'
)[ 6 ];
const reusableTabTitle = element.querySelectorAll(
const reusableTabTitle = container.querySelectorAll(
'.block-editor-inserter__panel-title'
)[ 6 ];
const blocks = reusableTabContent.querySelectorAll(
Expand All @@ -143,15 +139,15 @@ describe( 'InserterMenu', () => {
expect( blocks ).toHaveLength( 1 );
expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' );

assertNoResultsMessageNotToBePresent( element );
assertNoResultsMessageNotToBePresent( container );
} );

it( 'should show the common category blocks', () => {
const element = initializeAllClosedMenuStateAndReturnElement();
const commonTabContent = element.querySelectorAll(
const { container } = initializeAllClosedMenuState();
const commonTabContent = container.querySelectorAll(
'.block-editor-inserter__panel-content'
)[ 1 ];
const commonTabTitle = element.querySelectorAll(
const commonTabTitle = container.querySelectorAll(
'.block-editor-inserter__panel-title'
)[ 1 ];
const blocks = commonTabContent.querySelectorAll(
Expand All @@ -164,12 +160,12 @@ describe( 'InserterMenu', () => {
expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' );
expect( blocks[ 2 ].textContent ).toBe( 'Some Other Block' );

assertNoResultsMessageNotToBePresent( element );
assertNoResultsMessageNotToBePresent( container );
} );

it( 'should disable items with `isDisabled`', () => {
const element = initializeAllClosedMenuStateAndReturnElement();
const layoutTabContent = element.querySelectorAll(
const { container } = initializeAllClosedMenuState();
const layoutTabContent = container.querySelectorAll(
'.block-editor-inserter__panel-content'
)[ 2 ];
const disabledBlocks = layoutTabContent.querySelectorAll(
Expand All @@ -181,44 +177,98 @@ describe( 'InserterMenu', () => {
} );

it( 'should allow searching for items', () => {
const element = initializeMenuDefaultStateAndReturnElement( {
filterValue: 'text',
} );
const { container } = render(
<InserterBlockList filterValue="text" />
);

const matchingCategories = element.querySelectorAll(
const matchingCategories = container.querySelectorAll(
'.block-editor-inserter__panel-title'
);

expect( matchingCategories ).toHaveLength( 3 );
expect( matchingCategories[ 0 ].textContent ).toBe( 'Common blocks' );
expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' );
expect( matchingCategories[ 2 ].textContent ).toBe( 'Core' ); // "Core" namespace collection

const blocks = element.querySelectorAll(
const blocks = container.querySelectorAll(
'.block-editor-block-types-list__item-title'
);

// There are five buttons present for 3 total distinct results. The
// additional two account for the collection results (repeated).
expect( blocks ).toHaveLength( 5 );
expect( debouncedSpeak ).toHaveBeenCalledWith( '3 results found.' );

// Default block results.
expect( blocks[ 0 ].textContent ).toBe( 'Text' );
expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' );
expect( blocks[ 2 ].textContent ).toBe( 'A Text Embed' );

assertNoResultsMessageNotToBePresent( element );
// Collection results.
expect( blocks[ 3 ].textContent ).toBe( 'Text' );
expect( blocks[ 4 ].textContent ).toBe( 'Advanced Text' );

assertNoResultsMessageNotToBePresent( container );
} );

it( 'should allow searching for reusable blocks by title', () => {
const { container } = render(
<InserterBlockList filterValue="my reusable" />
);

const matchingCategories = container.querySelectorAll(
'.block-editor-inserter__panel-title'
);

expect( matchingCategories ).toHaveLength( 2 );
expect( matchingCategories[ 0 ].textContent ).toBe( 'Core' ); // "Core" namespace collection
expect( matchingCategories[ 1 ].textContent ).toBe( 'Reusable' );

const blocks = container.querySelectorAll(
'.block-editor-block-types-list__item-title'
);

// There are two buttons present for 1 total distinct result. The
// additional one accounts for the collection result (repeated).
expect( blocks ).toHaveLength( 2 );
expect( debouncedSpeak ).toHaveBeenCalledWith( '1 result found.' );
expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' );
expect( blocks[ 1 ].textContent ).toBe( 'My reusable block' );

assertNoResultsMessageNotToBePresent( container );
} );

it( 'should speak after any change in search term', () => {
// The search result count should always be announced any time the user
// changes the search term, even if it results in the same count.
//
// See: https://github.com/WordPress/gutenberg/pull/22279#discussion_r423317161
const { rerender } = render(
<InserterBlockList filterValue="my reusab" />
);

rerender( <InserterBlockList filterValue="my reusable" /> );
rerender( <InserterBlockList filterValue="my reusable" /> );

expect( debouncedSpeak ).toHaveBeenCalledTimes( 2 );
expect( debouncedSpeak.mock.calls[ 0 ][ 0 ] ).toBe( '1 result found.' );
expect( debouncedSpeak.mock.calls[ 1 ][ 0 ] ).toBe( '1 result found.' );
} );

it( 'should trim whitespace of search terms', () => {
const element = initializeMenuDefaultStateAndReturnElement( {
filterValue: ' text',
} );
const { container } = render(
<InserterBlockList filterValue=" text" />
);

const matchingCategories = element.querySelectorAll(
const matchingCategories = container.querySelectorAll(
'.block-editor-inserter__panel-title'
);

expect( matchingCategories ).toHaveLength( 3 );
expect( matchingCategories[ 0 ].textContent ).toBe( 'Common blocks' );
expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' );

const blocks = element.querySelectorAll(
const blocks = container.querySelectorAll(
'.block-editor-block-types-list__item-title'
);

Expand All @@ -227,6 +277,6 @@ describe( 'InserterMenu', () => {
expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' );
expect( blocks[ 2 ].textContent ).toBe( 'A Text Embed' );

assertNoResultsMessageNotToBePresent( element );
assertNoResultsMessageNotToBePresent( container );
} );
} );

0 comments on commit 89e21cf

Please sign in to comment.