Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation: use same default implementation of __experimentalFetchLinkSuggestions in post, site, navigation, widget editor #29993

Merged
merged 7 commits into from
Apr 1, 2021
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/core-data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@wordpress/data": "file:../data",
"@wordpress/data-controls": "file:../data-controls",
"@wordpress/element": "file:../element",
"@wordpress/html-entities": "file:../html-entities",
"@wordpress/i18n": "file:../i18n",
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/url": "file:../url",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';

/**
* Filters the search by type
*
* @typedef { 'post' | 'term' | 'post-format' } WPLinkSearchType
*/

/**
* @typedef WPLinkSearchOptions
*
* @property {boolean} [isInitialSuggestions] Displays initial search suggestions, when true.
* @property {WPLinkSearchType} [type] Filters by search type.
* @property {string} [subtype] Slug of the post-type or taxonomy.
* @property {number} [page] Which page of results to return.
* @property {number} [perPage] Search results per page.
*/

/**
* @typedef WPLinkSearchResult
*
* @property {number} id Post or term id.
* @property {string} url Link url.
* @property {string} title Title of the link.
* @property {string} type The taxonomy or post type slug or type URL.
*/

/**
* @typedef WPEditorSettings
*
* @property {boolean} [ disablePostFormats ] Disables post formats, when true.
*/

/**
* Fetches link suggestions from the API.
*
* @async
* @param {string} search
* @param {WPLinkSearchOptions} [searchOptions]
* @param {WPEditorSettings} [settings]
*
* @example
* ```js
* import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wordpress/core-data';
*
* //...
*
* export function initialize( id, settings ) {
*
* settings.__experimentalFetchLinkSuggestions = (
* search,
* searchOptions
* ) => fetchLinkSuggestions( search, searchOptions, settings );
* ```
* @return {Promise< WPLinkSearchResult[] >} List of search suggestions
*/
const fetchLinkSuggestions = async (
search,
searchOptions = {},
settings = {}
) => {
const {
isInitialSuggestions = false,
type = undefined,
subtype = undefined,
page = undefined,
perPage = isInitialSuggestions ? 3 : 20,
} = searchOptions;

const { disablePostFormats = false } = settings;

const queries = [];

if ( ! type || type === 'post' ) {
queries.push(
apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
page,
per_page: perPage,
type: 'post',
subtype,
} ),
} ).catch( () => [] ) // fail by returning no results
);
}

if ( ! type || type === 'term' ) {
queries.push(
apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
page,
per_page: perPage,
type: 'term',
subtype,
} ),
} ).catch( () => [] )
);
}

if ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth taking this opportunity to write some unit tests for this function since it has a bunch of conditional logic that is often error prone and apiFetch is easy to mock.

queries.push(
apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
page,
per_page: perPage,
type: 'post-format',
subtype,
} ),
} ).catch( () => [] )
);
}

return Promise.all( queries ).then( ( results ) => {
return results
.reduce(
( accumulator, current ) => accumulator.concat( current ), //flatten list
[]
)
.filter(
/**
* @param {{ id: number }} result
*/
( result ) => {
return !! result.id;
}
)
.slice( 0, perPage )
.map(
/**
* @param {{ id: number, url:string, title?:string, subtype?: string, type?: string }} result
*/
( result ) => {
return {
id: result.id,
url: result.url,
title:
decodeEntities( result.title || '' ) ||
__( '(no title)' ),
type: result.subtype || result.type,
};
}
);
} );
};

export default fetchLinkSuggestions;
1 change: 1 addition & 0 deletions packages/core-data/src/fetch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as __experimentalFetchLinkSuggestions } from './__experimental-fetch-link-suggestions';
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* Internal dependencies
*/
import fetchLinkSuggestions from '../__experimental-fetch-link-suggestions';

jest.mock( '@wordpress/api-fetch', () =>
jest.fn( ( { path } ) => {
switch ( path ) {
case '/wp/v2/search?search=&per_page=20&type=post':
case '/wp/v2/search?search=Contact&per_page=20&type=post&subtype=page':
return Promise.resolve( [
{
id: 37,
title: 'Contact Page',
url: 'http://wordpress.local/contact-page/',
type: 'post',
subtype: 'page',
},
] );
case '/wp/v2/search?search=&per_page=20&type=term':
case '/wp/v2/search?search=cat&per_page=20&type=term&subtype=category':
return Promise.resolve( [
{
id: 9,
title: 'Cats',
url: 'http://wordpress.local/category/cats/',
type: 'category',
},
{
id: 1,
title: 'Uncategorized',
url: 'http://wordpress.local/category/uncategorized/',
type: 'category',
},
] );
case '/wp/v2/search?search=&per_page=20&type=post-format':
return Promise.resolve( [
{
id: 'gallery',
title: 'Gallery',
url: 'http://wordpress.local/type/gallery/',
type: 'post-format',
},
{
id: 'quote',
title: 'Quote',
url: 'http://wordpress.local/type/quote/',
type: 'post-format',
},
] );
case '/wp/v2/search?search=&per_page=3&type=post&subtype=page':
return Promise.resolve( [
{
id: 11,
title: 'Limit Case',
url: 'http://wordpress.local/limit-case/',
type: 'post',
subtype: 'page',
},
] );
case '/wp/v2/search?search=&page=11&per_page=20&type=post&subtype=page':
return Promise.resolve( [
{
id: 22,
title: 'Page Case',
url: 'http://wordpress.local/page-case/',
type: 'post',
subtype: 'page',
},
] );
default:
return Promise.resolve( [
{
id: -1,
title: 'missing case or failed',
url: path,
type: 'missing case or failed',
},
] );
}
} )
);

describe( 'fetchLinkSuggestions', () => {
it( 'filters suggestions by post-type', () => {
return fetchLinkSuggestions( 'Contact', {
type: 'post',
subtype: 'page',
} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 37,
title: 'Contact Page',
type: 'page',
url: 'http://wordpress.local/contact-page/',
},
] )
);
} );
it( 'filters suggestions by term', () => {
return fetchLinkSuggestions( 'cat', {
type: 'term',
subtype: 'category',
} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 9,
title: 'Cats',
url: 'http://wordpress.local/category/cats/',
type: 'category',
},
{
id: 1,
title: 'Uncategorized',
url: 'http://wordpress.local/category/uncategorized/',
type: 'category',
},
] )
);
} );
it( 'filters suggestions by post-format', () => {
return fetchLinkSuggestions( '', {
type: 'post-format',
} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 'gallery',
title: 'Gallery',
url: 'http://wordpress.local/type/gallery/',
type: 'post-format',
},
{
id: 'quote',
title: 'Quote',
url: 'http://wordpress.local/type/quote/',
type: 'post-format',
},
] )
);
} );
it( 'filters does not return post-format suggestions when formats are not supported', () => {
return fetchLinkSuggestions(
'',
{
type: 'post-format',
},
{ disablePostFormats: true }
).then( ( suggestions ) => expect( suggestions ).toEqual( [] ) );
} );
it( 'returns suggestions from post, term, and post-format', () => {
return fetchLinkSuggestions( '', {} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 37,
title: 'Contact Page',
url: 'http://wordpress.local/contact-page/',
type: 'page',
},
{
id: 9,
title: 'Cats',
url: 'http://wordpress.local/category/cats/',
type: 'category',
},
{
id: 1,
title: 'Uncategorized',
url: 'http://wordpress.local/category/uncategorized/',
type: 'category',
},
{
id: 'gallery',
title: 'Gallery',
url: 'http://wordpress.local/type/gallery/',
type: 'post-format',
},
{
id: 'quote',
title: 'Quote',
url: 'http://wordpress.local/type/quote/',
type: 'post-format',
},
] )
);
} );
it( 'initial search suggestions limits results', () => {
return fetchLinkSuggestions( '', {
type: 'post',
subtype: 'page',
isInitialSuggestions: true,
} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 11,
title: 'Limit Case',
url: 'http://wordpress.local/limit-case/',
type: 'page',
},
] )
);
} );
it( 'allows searching from a page', () => {
return fetchLinkSuggestions( '', {
type: 'post',
subtype: 'page',
page: 11,
} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
id: 22,
title: 'Page Case',
url: 'http://wordpress.local/page-case/',
type: 'page',
},
] )
);
} );
} );
1 change: 1 addition & 0 deletions packages/core-data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ register( store );

export { default as EntityProvider } from './entity-provider';
export * from './entity-provider';
export * from './fetch';
Loading