Make `Search` accept sync `autocompleter.options.` (https://github.com/woocommerce/woocommerce-admin/pull/6884)

Co-authored-by: Jeff Stieler <jeff.m.stieler@gmail.com>

Make `Search` component accept `autocompleter.options` that meet the requirements stated in [the docs](https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/autocomplete#options): 
> May be an array, a function that returns an array, or a function that returns a promise for an array.


Fixes https://github.com/woocommerce/woocommerce-admin/issues/6061.
This commit is contained in:
Tomek Wytrębowicz 2021-05-03 19:18:07 +02:00 committed by GitHub
parent 0f4c102c51
commit 6aa78cbdb9
4 changed files with 113 additions and 4 deletions

View File

@ -1,5 +1,6 @@
# Unreleased # Unreleased
- Make `Search` accept synchronous `autocompleter.options`. #6884
- Add new (experimental) collapsible list item to collapse list items. #6869 - Add new (experimental) collapsible list item to collapse list items. #6869
- SelectControl: fix display of multiple selections without inline tags. #6862 - SelectControl: fix display of multiple selections without inline tags. #6862
- Add new (experimental) list, and add depreciation notice for the current list. #6787 - Add new (experimental) list, and add depreciation notice for the current list. #6787

View File

@ -108,8 +108,15 @@ export class Search extends Component {
return []; return [];
} }
const autocompleter = this.getAutocompleter(); const autocompleterOptions = this.getAutocompleter().options;
return autocompleter.options( query ).then( async ( response ) => {
// Support arrays, sync- & async functions that returns an array.
const resolvedOptions = Promise.resolve(
typeof autocompleterOptions === 'function'
? autocompleterOptions( query )
: autocompleterOptions || []
);
return resolvedOptions.then( async ( response ) => {
const options = this.getFormattedOptions( response, query ); const options = this.getFormattedOptions( response, query );
this.setState( { options } ); this.setState( { options } );
return options; return options;

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { render } from '@testing-library/react'; import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
/** /**
@ -10,8 +10,11 @@ import userEvent from '@testing-library/user-event';
import { Search } from '../index'; import { Search } from '../index';
import { computeSuggestionMatch } from '../autocompleters/utils'; import { computeSuggestionMatch } from '../autocompleters/utils';
const delay = ( timeout ) =>
new Promise( ( resolve ) => setTimeout( resolve, timeout ) );
describe( 'Search', () => { describe( 'Search', () => {
it( 'shows the free text search option', async () => { it( 'shows the free text search option', () => {
const { getByRole, queryAllByRole } = render( const { getByRole, queryAllByRole } = render(
<Search type="products" allowFreeTextSearch /> <Search type="products" allowFreeTextSearch />
); );
@ -22,6 +25,103 @@ describe( 'Search', () => {
expect( queryAllByRole( 'option' ) ).toHaveLength( 0 ); expect( queryAllByRole( 'option' ) ).toHaveLength( 0 );
} ); } );
describe( 'with `type="custom"`', () => {
let sampleOptions, sampleAutocompleter;
beforeEach( () => {
sampleOptions = [
{ name: 'Apple', id: 1 },
{ name: 'Orange', id: 2 },
{ name: 'Grapes', id: 3 },
];
sampleAutocompleter = {
options: sampleOptions,
getOptionIdentifier: ( fruit ) => fruit.id,
getOptionLabel: ( option ) => (
<nicer-label>{ option.name }</nicer-label>
),
getOptionKeywords: ( option ) => [ option.name ],
getOptionCompletion: ( attribute ) => ( {
key: attribute.id,
label: attribute.name,
} ),
};
} );
describe( 'renders options given in `autocompleter.options`', () => {
it( 'as a static array', async () => {
const { getByRole, queryAllByRole } = render(
<Search
type="custom"
autocompleter={ sampleAutocompleter }
/>
);
// Emulate typing to render available options.
userEvent.type( getByRole( 'combobox' ), 'A' );
// Wait for async options procesing.
await waitFor( () => {
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
} );
} );
it( 'being a function that for the given query returns an array', async () => {
const optionsSpy = jest
.fn()
.mockName( 'autocompleter.options' );
const customAutocompleter = {
...sampleAutocompleter,
// Set the options as a function that returns an array.
options: ( ...args ) => {
optionsSpy( ...args );
return sampleOptions;
},
};
const { getByRole, queryAllByRole } = render(
<Search
type="custom"
autocompleter={ customAutocompleter }
/>
);
// Emulate typing to render available options.
userEvent.type( getByRole( 'combobox' ), 'A' );
// Wait for async options procesing.
await waitFor( () => {
expect( optionsSpy ).toBeCalledWith( 'A' );
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
} );
} );
it( 'being a function that for the given query returns a promise for an array', async () => {
const optionsSpy = jest
.fn()
.mockName( 'autocompleter.options' );
const customAutocompleter = {
...sampleAutocompleter,
// Set the options as a function that returns a promise for an array.
options: async ( ...args ) => {
optionsSpy( ...args );
await delay( 1 );
return sampleOptions;
},
};
const { getByRole, queryAllByRole } = render(
<Search
type="custom"
autocompleter={ customAutocompleter }
/>
);
// Emulate typing to render available options.
userEvent.type( getByRole( 'combobox' ), 'A' );
// Wait for async options procesing.
await waitFor( () => {
expect( optionsSpy ).toBeCalledWith( 'A' );
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
} );
} );
} );
} );
it( 'returns an object with decoded text', () => { it( 'returns an object with decoded text', () => {
const decodedText = computeSuggestionMatch( const decodedText = computeSuggestionMatch(
'A test &amp; a &#116;&#101;&#115;&#116;', 'A test &amp; a &#116;&#101;&#115;&#116;',

View File

@ -75,6 +75,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
== Unreleased == == Unreleased ==
- Fix: Make `Search` accept synchronous `autocompleter.options`. #6884
- Add: Consume remote payment methods on frontend #6867 - Add: Consume remote payment methods on frontend #6867
- Add: Add plugin installer to allow installation of plugins via URL #6805 - Add: Add plugin installer to allow installation of plugins via URL #6805
- Add: Optional children prop to SummaryNumber component #6748 - Add: Optional children prop to SummaryNumber component #6748