From 8185691bda199b7b4e5c363d9eb2d39ae2a28d42 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2020 15:38:29 -0400 Subject: [PATCH 1/5] Block Editor: Refactor BlockList test to avoid relative paths --- .../src/components/inserter/test/block-list.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js index c83df96520e09..2c83c363a27dc 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-list.js @@ -4,20 +4,24 @@ import TestUtils, { act } from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + /** * Internal dependencies */ import InserterBlockList from '../block-list'; -import useSelect from '../../../../../data/src/components/use-select'; 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: () => ( {} ), }; From f1529124aa2fb932a5e1c072ab9ffe17ee5b503e Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2020 16:11:10 -0400 Subject: [PATCH 2/5] Block Editor: Refactor Inserter BlockList test to use testing-library --- .../components/inserter/test/block-list.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js index 2c83c363a27dc..f8445c1df4b27 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-list.js @@ -1,8 +1,7 @@ /** * External dependencies */ -import TestUtils, { act } from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; +import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies @@ -27,21 +26,9 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => { }; } ); -const getWrapperForProps = ( propOverrides ) => { - let wrapper; - act( () => { - wrapper = TestUtils.renderIntoDocument( - - ); - } ); - - return wrapper; -}; const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => { - const wrapper = getWrapperForProps( propOverrides ); - // eslint-disable-next-line react/no-find-dom-node - return ReactDOM.findDOMNode( wrapper ); + return render( ).container; }; const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { @@ -50,7 +37,7 @@ const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { '.components-panel__body.is-opened button.components-panel__body-toggle' ); activeTabs.forEach( ( tab ) => { - TestUtils.Simulate.click( tab ); + fireEvent.click( tab ); } ); return element; }; From 18e33b98ca40779a4370f3b63389f5384d409765 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2020 16:34:39 -0400 Subject: [PATCH 3/5] Block Editor: Include reusable blocks in announced inserter search results --- .../src/components/inserter/block-list.js | 15 ++---- .../components/inserter/test/block-list.js | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js index a1aafcd54061d..cef20494a82a4 100644 --- a/packages/block-editor/src/components/inserter/block-list.js +++ b/packages/block-editor/src/components/inserter/block-list.js @@ -52,7 +52,7 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ]; const MAX_SUGGESTED_ITEMS = 9; -function InserterBlockList( { +export function InserterBlockList( { rootClientId, onInsert, onHover, @@ -168,19 +168,10 @@ 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 ] ); diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js index f8445c1df4b27..ca824d06409e6 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-list.js @@ -11,7 +11,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import InserterBlockList from '../block-list'; +import { InserterBlockList } from '../block-list'; import items, { categories, collections } from './fixtures'; jest.mock( '@wordpress/data/src/components/use-select', () => { @@ -26,9 +26,12 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => { }; } ); +const speak = jest.fn(); const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => { - return render( ).container; + return render( + + ).container; }; const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { @@ -58,6 +61,8 @@ const assertNoResultsMessageNotToBePresent = ( element ) => { describe( 'InserterMenu', () => { beforeEach( () => { + speak.mockClear(); + useSelect.mockImplementation( () => ( { categories, collections, @@ -183,16 +188,53 @@ describe( 'InserterMenu', () => { 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( '.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( speak ).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' ); + // Collection results. + expect( blocks[ 3 ].textContent ).toBe( 'Text' ); + expect( blocks[ 4 ].textContent ).toBe( 'Advanced Text' ); + + assertNoResultsMessageNotToBePresent( element ); + } ); + + it( 'should allow searching for reusable blocks by title', () => { + const element = initializeMenuDefaultStateAndReturnElement( { + filterValue: 'my reusable', + } ); + + const matchingCategories = element.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 = element.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( speak ).toHaveBeenCalledWith( '1 result found.' ); + expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); + expect( blocks[ 1 ].textContent ).toBe( 'My reusable block' ); + assertNoResultsMessageNotToBePresent( element ); } ); From 062e9d6f14590be6cdc8d4d1b5e711b7c4f1ece3 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 11 May 2020 16:57:57 -0400 Subject: [PATCH 4/5] Block Editor: Update lifecycle dependencies to use filteredItems --- packages/block-editor/src/components/inserter/block-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js index cef20494a82a4..92c7109059baa 100644 --- a/packages/block-editor/src/components/inserter/block-list.js +++ b/packages/block-editor/src/components/inserter/block-list.js @@ -174,7 +174,7 @@ export function InserterBlockList( { filteredItems.length ); debouncedSpeak( resultsFoundMessage ); - }, [ itemsPerCategory, debouncedSpeak ] ); + }, [ filteredItems.length, debouncedSpeak ] ); const hasItems = ! isEmpty( filteredItems ); const hasChildItems = childItems.length > 0; From 32c39313b472f348da7030d7302f1c6b60aaa2bb Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 12 May 2020 11:49:20 -0400 Subject: [PATCH 5/5] Block Editor: Announce inserter results in response to filter change --- .../src/components/inserter/block-list.js | 2 +- .../components/inserter/test/block-list.js | 123 ++++++++++-------- 2 files changed, 71 insertions(+), 54 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js index 92c7109059baa..c5ef363773a74 100644 --- a/packages/block-editor/src/components/inserter/block-list.js +++ b/packages/block-editor/src/components/inserter/block-list.js @@ -174,7 +174,7 @@ export function InserterBlockList( { filteredItems.length ); debouncedSpeak( resultsFoundMessage ); - }, [ filteredItems.length, debouncedSpeak ] ); + }, [ filterValue, debouncedSpeak ] ); const hasItems = ! isEmpty( filteredItems ); const hasChildItems = childItems.length > 0; diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js index ca824d06409e6..bf27a966a0a62 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-list.js @@ -11,7 +11,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { InserterBlockList } from '../block-list'; +import { InserterBlockList as BaseInserterBlockList } from '../block-list'; import items, { categories, collections } from './fixtures'; jest.mock( '@wordpress/data/src/components/use-select', () => { @@ -26,23 +26,23 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => { }; } ); -const speak = jest.fn(); +const debouncedSpeak = jest.fn(); -const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => { - return render( - - ).container; -}; +function InserterBlockList( props ) { + return ( + + ); +} -const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { - const element = initializeMenuDefaultStateAndReturnElement( propOverrides ); - const activeTabs = element.querySelectorAll( +const initializeAllClosedMenuState = ( propOverrides ) => { + const result = render( ); + const activeTabs = result.container.querySelectorAll( '.components-panel__body.is-opened button.components-panel__body-toggle' ); activeTabs.forEach( ( tab ) => { fireEvent.click( tab ); } ); - return element; + return result; }; const assertNoResultsMessageToBePresent = ( element ) => { @@ -61,7 +61,7 @@ const assertNoResultsMessageNotToBePresent = ( element ) => { describe( 'InserterMenu', () => { beforeEach( () => { - speak.mockClear(); + debouncedSpeak.mockClear(); useSelect.mockImplementation( () => ( { categories, @@ -77,21 +77,21 @@ describe( 'InserterMenu', () => { collections, items: noItems, } ) ); - const element = initializeMenuDefaultStateAndReturnElement( { - filterValue: 'random', - } ); - const visibleBlocks = element.querySelector( + const { container } = render( + + ); + 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( ); + const firstPanel = container.querySelector( '.block-editor-inserter__panel-content' ); const visibleBlocks = firstPanel.querySelectorAll( @@ -104,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( @@ -120,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( @@ -139,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( @@ -160,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( @@ -177,11 +177,11 @@ describe( 'InserterMenu', () => { } ); it( 'should allow searching for items', () => { - const element = initializeMenuDefaultStateAndReturnElement( { - filterValue: 'text', - } ); + const { container } = render( + + ); - const matchingCategories = element.querySelectorAll( + const matchingCategories = container.querySelectorAll( '.block-editor-inserter__panel-title' ); @@ -190,14 +190,14 @@ describe( 'InserterMenu', () => { 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( speak ).toHaveBeenCalledWith( '3 results found.' ); + expect( debouncedSpeak ).toHaveBeenCalledWith( '3 results found.' ); // Default block results. expect( blocks[ 0 ].textContent ).toBe( 'Text' ); @@ -208,15 +208,15 @@ describe( 'InserterMenu', () => { expect( blocks[ 3 ].textContent ).toBe( 'Text' ); expect( blocks[ 4 ].textContent ).toBe( 'Advanced Text' ); - assertNoResultsMessageNotToBePresent( element ); + assertNoResultsMessageNotToBePresent( container ); } ); it( 'should allow searching for reusable blocks by title', () => { - const element = initializeMenuDefaultStateAndReturnElement( { - filterValue: 'my reusable', - } ); + const { container } = render( + + ); - const matchingCategories = element.querySelectorAll( + const matchingCategories = container.querySelectorAll( '.block-editor-inserter__panel-title' ); @@ -224,26 +224,43 @@ describe( 'InserterMenu', () => { expect( matchingCategories[ 0 ].textContent ).toBe( 'Core' ); // "Core" namespace collection expect( matchingCategories[ 1 ].textContent ).toBe( 'Reusable' ); - const blocks = element.querySelectorAll( + 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( speak ).toHaveBeenCalledWith( '1 result found.' ); + expect( debouncedSpeak ).toHaveBeenCalledWith( '1 result found.' ); expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); expect( blocks[ 1 ].textContent ).toBe( 'My reusable block' ); - assertNoResultsMessageNotToBePresent( element ); + 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( + + ); + + rerender( ); + rerender( ); + + 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( + + ); - const matchingCategories = element.querySelectorAll( + const matchingCategories = container.querySelectorAll( '.block-editor-inserter__panel-title' ); @@ -251,7 +268,7 @@ describe( 'InserterMenu', () => { 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' ); @@ -260,6 +277,6 @@ describe( 'InserterMenu', () => { expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' ); expect( blocks[ 2 ].textContent ).toBe( 'A Text Embed' ); - assertNoResultsMessageNotToBePresent( element ); + assertNoResultsMessageNotToBePresent( container ); } ); } );