From 49ef0eb8f289963ca7dfb2cb8c56af23a0f84c8c Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 21 Jan 2020 10:17:33 +0000 Subject: [PATCH 001/170] Adds new create component to search results --- .../src/components/link-control/index.js | 8 ++++ .../link-control/search-create-button.js | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/block-editor/src/components/link-control/search-create-button.js diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 3ef9c75fa8f261..ea5804ca5ce6fc 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -33,6 +33,7 @@ import { focus } from '@wordpress/dom'; import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; import LinkControlSearchInput from './search-input'; +import LinkControlSearchCreate from './search-create-button'; /** * Default properties associated with a link control value. @@ -304,7 +305,14 @@ function LinkControl( { searchTerm={ inputValue } /> ) ) } + + { ! isInitialSuggestions && ( + + ) } + ); }; diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js new file mode 100644 index 00000000000000..6c5f282516d67a --- /dev/null +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { safeDecodeURI } from '@wordpress/url'; +import { + Button, + Icon, +} from '@wordpress/components'; + +export const LinkControlSearchCreate = ( { searchTerm = '', onClick, itemProps } ) => { + return ( + + ); +}; + +export default LinkControlSearchCreate; + From 6758a60944223a4e7dbc30c046c7ca8f2f19dd2a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 21 Jan 2020 10:38:11 +0000 Subject: [PATCH 002/170] Styling and i18n --- .../link-control/search-create-button.js | 11 ++++++++--- .../src/components/link-control/style.scss | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 6c5f282516d67a..aaae7180fa03d5 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -6,6 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { __, sprintf } from '@wordpress/i18n'; import { safeDecodeURI } from '@wordpress/url'; import { Button, @@ -13,19 +14,23 @@ import { } from '@wordpress/components'; export const LinkControlSearchCreate = ( { searchTerm = '', onClick, itemProps } ) => { + if ( ! searchTerm.trim() ) { + return null; + } + return ( ); From 7046ff84c9410135b7d9951269da22a8849005f4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 21 Jan 2020 10:49:39 +0000 Subject: [PATCH 004/170] Fix to ensure border above create button is displayed --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index f8acf262968530..d21c2df1f76751 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -185,7 +185,7 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__search-create { margin-top: 20px; - overflow: auto; + overflow: visible; // Create fake border. We cannot use border because the button has a border // radius applied to it From 126974553f2a0aa846110a3997f7333f2c264e1b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 21 Jan 2020 12:25:37 +0000 Subject: [PATCH 005/170] Implement prototype async load of created Page --- .../src/components/link-control/index.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index ea5804ca5ce6fc..2f655373c55891 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -23,7 +23,7 @@ import { prependHTTP, getProtocol, } from '@wordpress/url'; -import { useInstanceId } from '@wordpress/compose'; +import { useInstanceId, withSafeTimeout } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; import { focus } from '@wordpress/dom'; @@ -309,6 +309,23 @@ function LinkControl( { { ! isInitialSuggestions && ( { + setIsEditingLink( false ); + onChange( { + title: 'Loading link...', + url: 'loading...', + } ); + const _result = await new Promise( ( resolve ) => { + setTimeout( () => { + resolve( { + title: 'Resolved Titlte', + url: '/some-revoled/slug', + } ); + }, 5000 ); + } ); + setIsEditingLink( false ); + onChange( _result ); + } } /> ) } @@ -386,4 +403,4 @@ function LinkControl( { ); } -export default LinkControl; +export default withSafeTimeout( LinkControl ); From 0ff7ddf78362d2bbdeefaeee234e5e6820d77532 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 21 Jan 2020 13:36:04 +0000 Subject: [PATCH 006/170] Add state and UI to represent link being in a resolving/loading state This is require to accommodate the new async mode for setting a link. For example when a Page is being created async with the API. --- .../src/components/link-control/index.js | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 2f655373c55891..d1a223f7dd710c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -109,7 +109,8 @@ function LinkControl( { ? forceIsEditingLink : ! value || ! value.url ); - const isEndingEditWithFocus = useRef( false ); + const [ isResolvingLink, setIsResolvingLink ] = useState( false ); + const { fetchSearchSuggestions } = useSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); return { @@ -310,7 +311,7 @@ function LinkControl( { { - setIsEditingLink( false ); + setIsResolvingLink( true ); onChange( { title: 'Loading link...', url: 'loading...', @@ -323,6 +324,7 @@ function LinkControl( { } ); }, 5000 ); } ); + setIsResolvingLink( false ); setIsEditingLink( false ); onChange( _result ); } } @@ -336,10 +338,30 @@ function LinkControl( { return (
+ { isResolvingLink && ( +
+ + + { __( 'Creating Page' ) } + + + { __( 'Your new Page is being created' ) }. + + +
+ + { isEditingLink || ! value ? ( + ) } + ) : (

+ } + + { isEditingLink && ! isResolvingLink && ( + { + setIsEditingLink( false ); + onChange( { ...value, ...suggestion } ); + } } + renderSuggestions={ renderSearchResults } + fetchSuggestions={ getSearchHandler } + onReset={ resetInput } + showInitialSuggestions={ showInitialSuggestions } + /> + ) } + + { ! isEditingLink && ! isResolvingLink && ( + ) } Date: Tue, 21 Jan 2020 14:19:14 +0100 Subject: [PATCH 007/170] add basic API integration (inline in component) --- .../src/components/link-control/index.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index d1a223f7dd710c..99c72fa3eb3d39 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -316,13 +316,21 @@ function LinkControl( { title: 'Loading link...', url: 'loading...', } ); - const _result = await new Promise( ( resolve ) => { - setTimeout( () => { - resolve( { - title: 'Resolved Titlte', - url: '/some-revoled/slug', - } ); - }, 5000 ); + const newPage = await apiFetch( { + path: `/wp/v2/posts`, + data: { + title: inputValue, + content: '', + status: 'publish', // TODO: use publish? + }, + method: 'POST', + } ); + // TODO: handle error from API + onChange( { + id: newPage.id, + title: newPage.title.raw, // TODO: use raw or rendered? + url: newPage.link, + type: newPage.type, } ); setIsResolvingLink( false ); setIsEditingLink( false ); From 4dc6b3cff7f1d47af9bfbe6d93bfff9fd83e3bbc Mon Sep 17 00:00:00 2001 From: Marek Hrabe Date: Tue, 21 Jan 2020 14:23:41 +0100 Subject: [PATCH 008/170] actually create pages, not posts --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 99c72fa3eb3d39..234e73c2f3f88c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -317,7 +317,7 @@ function LinkControl( { url: 'loading...', } ); const newPage = await apiFetch( { - path: `/wp/v2/posts`, + path: `/wp/v2/pages`, data: { title: inputValue, content: '', From 9ce1457108ea9eaa6fb29be24749000bac6e7f2c Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 22 Jan 2020 10:03:27 +0000 Subject: [PATCH 009/170] Add API to control display of the Create functionality on LinkControl --- packages/block-editor/src/components/link-control/index.js | 5 ++++- packages/block-library/src/navigation-link/edit.js | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 234e73c2f3f88c..f49db017bd1621 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -25,7 +25,10 @@ import { } from '@wordpress/url'; import { useInstanceId, withSafeTimeout } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; + +/* eslint-disable import/no-extraneous-dependencies */ import { focus } from '@wordpress/dom'; +/* eslint-enable import/no-extraneous-dependencies */ /** * Internal dependencies @@ -307,7 +310,7 @@ function LinkControl( { /> ) ) } - { ! isInitialSuggestions && ( + { showCreatePages && ! isInitialSuggestions && ( { diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 488bbd6fde855d..f7fcdc7b3dc7a7 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -226,6 +226,7 @@ function NavigationLinkEdit( { className="wp-block-navigation-link__inline-link-input" value={ link } showInitialSuggestions={ true } + showCreatePages={ true } onChange={ ( { title: newTitle = '', url: newURL = '', From 79f4db7d6c84ec9a6ebb8046f7dd3e7abdfc84d8 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 22 Jan 2020 10:09:02 +0000 Subject: [PATCH 010/170] Adds initial tests for Create button feature --- .../link-control/search-create-button.js | 1 + .../src/components/link-control/test/index.js | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 84fa474dbe1e6c..03efb27f926aba 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -20,6 +20,7 @@ export const LinkControlSearchCreate = ( { searchTerm = '', onClick, itemProps } return ( ); }; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index d12b597cb20905..26e34717aa0660 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -519,13 +519,13 @@ describe( 'Default search suggestions', () => { describe( 'Creating pages', () => { it.each( [ - 'HelloWorld', // no spaces - 'Hello World', // with spaces - ] )( 'should display option to create a link for a valid Page title "%s"', async ( pageNameText ) => { + [ 'HelloWorld', 'without spaces' ], + [ 'Hello World', 'with spaces' ], + ] )( 'should display option to create a link for a valid Page title "%s" (%s)', async ( pageNameText ) => { const noResults = []; - // Force returning empty results for existing Pages, meaning only item - // shown should be "Create Page" because our input does not confirm to a + // Force returning empty results for existing Pages. Doing this means that the only item + // shown should be "Create Page" suggestion because our input does not confirm to a // direct entry schema (eg: a URL). mockFetchSearchSuggestions.mockImplementation( () => Promise.resolve( noResults ) ); @@ -535,7 +535,9 @@ describe( 'Creating pages', () => { return ( setLink( suggestion ) } + onChange={ ( suggestion ) => { + setLink( suggestion ); + } } createEmptyPage={ ( title ) => Promise.resolve( { type: 'page', id: 123, @@ -576,15 +578,17 @@ describe( 'Creating pages', () => { await eventLoopTick(); + // console.log( container.innerHTML ); + // Also acts as implicit test for "Selected Link" UI - const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); + // const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); - const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); + // const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); - const currentLinkHTML = currentLink.innerHTML; + // const currentLinkHTML = currentLink.innerHTML; - expect( currentLinkHTML ).toEqual( expect.stringContaining( pageNameText ) ); //title - expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug + // expect( currentLinkHTML ).toEqual( expect.stringContaining( pageNameText ) ); //title + // expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug } ); it.each( [ From 38e9be55df40559b408fb6708c4de4b32a45011c Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 23 Jan 2020 11:40:26 +0000 Subject: [PATCH 023/170] Reinstate full test conditions Left some commented out test conditions. These have revealed that the previous commit did not fix the outstanding broken tests. --- .../src/components/link-control/test/index.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 26e34717aa0660..9685cbcdcfc3c7 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -578,17 +578,14 @@ describe( 'Creating pages', () => { await eventLoopTick(); - // console.log( container.innerHTML ); + const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); - // Also acts as implicit test for "Selected Link" UI - // const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); + const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); - // const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); - - // const currentLinkHTML = currentLink.innerHTML; + const currentLinkHTML = currentLink.innerHTML; - // expect( currentLinkHTML ).toEqual( expect.stringContaining( pageNameText ) ); //title - // expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug + expect( currentLinkHTML ).toEqual( expect.stringContaining( pageNameText ) ); //title + expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug } ); it.each( [ From 9914409e009bddc43cb99d20030f2878c5e35538 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 23 Jan 2020 11:44:13 +0000 Subject: [PATCH 024/170] Fix bug whereby editing state was no disabled follow successful onChange --- packages/block-editor/src/components/link-control/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 6839d108674f4d..f26b2d94b662e7 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -306,6 +306,7 @@ function LinkControl( { url: newPage.link, type: newPage.type, } ); + setIsEditingLink( false ); } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( suggestion, index ) } From e148a128f1bf63f86e47fab7a8d2e0c3377b42ce Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 23 Jan 2020 12:42:20 +0000 Subject: [PATCH 025/170] Allow create button to display for initial suggestions --- .../src/components/link-control/index.js | 4 +- .../link-control/search-create-button.js | 4 -- .../src/components/link-control/test/index.js | 50 ++++++++++++++++--- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f26b2d94b662e7..ff5ab32ed3d58e 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -283,7 +283,7 @@ function LinkControl( { { labelText } ); - const showShowCreatePages = showCreatePages && createEmptyPage && ! isInitialSuggestions && ! isSingleDirectEntryResult; + const shouldShowCreatePages = showCreatePages && createEmptyPage && ! isSingleDirectEntryResult; return (
@@ -291,7 +291,7 @@ function LinkControl( {
{ suggestions.map( ( suggestion, index ) => { - if ( showShowCreatePages && CREATE_TYPE === suggestion.type ) { + if ( shouldShowCreatePages && CREATE_TYPE === suggestion.type ) { return ( { - if ( ! searchTerm.trim() ) { - return null; - } - return (
); diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 9259436c16e5a9..104f3968510bc5 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -5,6 +5,12 @@ $block-editor-link-control-number-of-actions: 1; min-width: $modal-min-width; } +// Provides positioning context for reset button. Without this then when an +// error notice is displayed above the input the reset button is incorrectly positioned. +.block-editor-link-control__search-input-wrapper { + position: relative; +} + // LinkControl popover. .block-editor-link-control .block-editor-link-control__search-input { // Specificity override. From 0799ed887e5ba0b3ae8660d5ac856cee43757b91 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 24 Jan 2020 11:53:58 +0000 Subject: [PATCH 038/170] Force repopulation of search results on create button failure. --- .../block-editor/src/components/link-control/index.js | 6 ++++++ .../src/components/link-control/test/index.js | 11 +++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c64d919a656c8f..09a62fe7086726 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -266,6 +266,11 @@ function LinkControl( { [ handleDirectEntry, fetchSearchSuggestions ] ); + const forceUpdateInputValue = ( val ) => { + setInputValue( '' ); + setInputValue( val ); + }; + // Render Components const renderSearchResults = ( { suggestionsListProps, @@ -334,6 +339,7 @@ function LinkControl( { setIsEditingLink( false ); } else { setIsEditingLink( true ); + forceUpdateInputValue( inputValue ); } } } key={ `${ suggestion.id }-${ suggestion.type }` } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 649b24836ccaa7..5f151711bd1244 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -674,8 +674,8 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + let searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + let createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); await act( async () => { Simulate.click( createButton ); @@ -699,8 +699,11 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( searchInput ).not.toBeFalsy(); expect( searchInput.value ).toBe( searchText ); - // TODO: check for Create button being visible again once - // https://github.com/WordPress/gutenberg/issues/19647 is resolved + // Verify search results are re-shown and create button is available. + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + + expect( createButton ).not.toBeFalsy(); // shouldn't exist! } ); } ); From 6bc9df0d6645f2b9ba775f2a7829fd08f8531ad1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 27 Jan 2020 15:37:52 +0000 Subject: [PATCH 039/170] Avoid showing Create UI when there is not input value --- .../src/components/link-control/test/index.js | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 5f151711bd1244..4a94f1acf18159 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -585,7 +585,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug } ); - it( 'should show not show an option to create "blank" entity in initial suggestions (when input is empty)', async () => { + it( 'should not show not show an option to create an entity when input is empty', async () => { act( () => { render( { expect( createButton ).toBeFalsy(); // shouldn't exist! } ); - it.each( [ - 'https://wordpress.org', - 'www.wordpress.org', - 'mailto:example123456@wordpress.org', - 'tel:example123456@wordpress.org', - '#internal-anchor', - ] )( 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', async ( inputText ) => { - act( () => { - render( - , container - ); - } ); - - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: inputText } } ); - } ); - - await eventLoopTick(); - - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - - const createButton = Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ); - - expect( createButton ).toBeNull(); - } ); + // it.each( [ + // 'https://wordpress.org', + // 'www.wordpress.org', + // 'mailto:example123456@wordpress.org', + // 'tel:example123456@wordpress.org', + // '#internal-anchor', + // ] )( 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', async ( inputText ) => { + // act( () => { + // render( + // , container + // ); + // } ); + + // // Search Input UI + // const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // // Simulate searching for a term + // act( () => { + // Simulate.change( searchInput, { target: { value: inputText } } ); + // } ); + + // await eventLoopTick(); + + // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + // const createButton = Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ); + + // expect( createButton ).toBeNull(); + // } ); it( 'should display human readable error notice and re-show create button and search input if page creation request fails', async () => { const searchText = 'This page to be created'; From 1949c91ca4e33700f50677a50cf3a16de305968d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 27 Jan 2020 15:41:19 +0000 Subject: [PATCH 040/170] Moves error note below search input UI --- .../components/link-control/search-input.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 9d2a82c84ba4e9..aac573fea80108 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -57,17 +57,6 @@ const LinkControlSearchInput = ( { return (
-
- { errorMsg && ( - - { errorMsg } - - ) } -
+
+ { errorMsg && ( + + { errorMsg } + + ) } +
); From 9206a449f8027fe03eec6969bf5ecf5ee5c9071d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 29 Jan 2020 09:34:22 -0800 Subject: [PATCH 041/170] Remove forceUpdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn’t actually work. Removing. --- packages/block-editor/src/components/link-control/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 09a62fe7086726..c64d919a656c8f 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -266,11 +266,6 @@ function LinkControl( { [ handleDirectEntry, fetchSearchSuggestions ] ); - const forceUpdateInputValue = ( val ) => { - setInputValue( '' ); - setInputValue( val ); - }; - // Render Components const renderSearchResults = ( { suggestionsListProps, @@ -339,7 +334,6 @@ function LinkControl( { setIsEditingLink( false ); } else { setIsEditingLink( true ); - forceUpdateInputValue( inputValue ); } } } key={ `${ suggestion.id }-${ suggestion.type }` } From 6cebe7ccafc68f34f2273a63bd2202d791dc921e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 29 Jan 2020 09:37:20 -0800 Subject: [PATCH 042/170] Refactor out a handle create function --- .../src/components/link-control/index.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c64d919a656c8f..c1dfa85c1cda4a 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -266,6 +266,28 @@ function LinkControl( { [ handleDirectEntry, fetchSearchSuggestions ] ); + const handleOnCreate = async () => { + let newEntity; + + setIsResolvingLink( true ); + setErrorMsg( null ); + + try { + newEntity = await createEntity( 'page', inputValue ); + } catch ( error ) { + setErrorMsg( error.msg || __( 'An unknown error occurred during Page creation. Please try again.' ) ); + } + + setIsResolvingLink( false ); + + if ( newEntity ) { // only set if request is resolved + onChange( newEntity ); + setIsEditingLink( false ); + } else { + setIsEditingLink( true ); + } + }; + // Render Components const renderSearchResults = ( { suggestionsListProps, @@ -315,27 +337,7 @@ function LinkControl( { return ( { - setIsResolvingLink( true ); - setErrorMsg( null ); - - let newEntity; - - try { - newEntity = await createEntity( 'page', inputValue ); - } catch ( error ) { - setErrorMsg( error.msg || __( 'An unknown error occurred during Page creation. Please try again.' ) ); - } - - setIsResolvingLink( false ); - - if ( newEntity ) { // only set if request is resolved - onChange( newEntity ); - setIsEditingLink( false ); - } else { - setIsEditingLink( true ); - } - } } + onClick={ handleOnCreate } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( suggestion, index ) } isSelected={ index === selectedSuggestion } From 26aa31819b247eb338143a26e6139211b395b10d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 29 Jan 2020 09:50:40 -0800 Subject: [PATCH 043/170] Extract common handler for selecting suggestion. --- .../block-editor/src/components/link-control/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c1dfa85c1cda4a..99ab094de917e0 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -288,6 +288,11 @@ function LinkControl( { } }; + const handleSelectSuggestion = ( suggestion, _value = {} ) => () => { + setIsEditingLink( false ); + onChange( { ..._value, ...suggestion } ); + }; + // Render Components const renderSearchResults = ( { suggestionsListProps, @@ -492,8 +497,7 @@ function LinkControl( { value={ inputValue } onChange={ onInputChange } onSelect={ ( suggestion ) => { - setIsEditingLink( false ); - onChange( { ...value, ...suggestion } ); + handleSelectSuggestion( suggestion, value )(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } From 83bef9f001547d38d9e0c9dc617d8636cb453022 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 30 Jan 2020 09:13:59 -0800 Subject: [PATCH 044/170] Add test (failing) to cover creation of entities via keyboard --- .../src/components/link-control/test/index.js | 75 +++++++++++++++++-- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 4a94f1acf18159..9e8dfe7b711863 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -518,17 +518,17 @@ describe( 'Default search suggestions', () => { } ); describe( 'Creating Entities (eg: Posts, Pages)', () => { - it.each( [ - [ 'HelloWorld', 'without spaces' ], - [ 'Hello World', 'with spaces' ], - ] )( 'should display option to create a link for a valid Entity title "%s" (%s)', async ( entityNameText ) => { - const noResults = []; - + const noResults = []; + beforeEach( () => { // Force returning empty results for existing Pages. Doing this means that the only item // shown should be "Create Page" suggestion because there will be no search suggestions // and our input does not conform to a direct entry schema (eg: a URL). mockFetchSearchSuggestions.mockImplementation( () => Promise.resolve( noResults ) ); - + } ); + it.each( [ + [ 'HelloWorld', 'without spaces' ], + [ 'Hello World', 'with spaces' ], + ] )( 'should display option to create a link for a valid Entity title "%s" (%s)', async ( entityNameText ) => { const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); @@ -585,6 +585,67 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug } ); + it( 'should allow creation of entities via the keyboard', async () => { + const entityNameText = 'A new page to be created'; + + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( { + setLink( suggestion ); + } } + createEntity={ ( type, title ) => Promise.resolve( { + type, + title, + id: 123, + url: '/?p=123', + } ) } + /> ); + }; + + act( () => { + render( , container ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: entityNameText } } ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + + // Step down into the search results, highlighting the first result item + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + await act( async () => { + Simulate.keyDown( createButton, { keyCode: ENTER } ); + } ); + + await eventLoopTick(); + + const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); + + const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); + + const currentLinkHTML = currentLink.innerHTML; + + expect( currentLinkHTML ).toEqual( expect.stringContaining( entityNameText ) ); //title + expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug + } ); + it( 'should not show not show an option to create an entity when input is empty', async () => { act( () => { render( From 7bacea109b6ba21c81d3121c805b162a472420cf Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 30 Jan 2020 15:04:09 -0800 Subject: [PATCH 045/170] Resolve rebase errors --- .../src/components/link-control/index.js | 202 +++++++++--------- 1 file changed, 98 insertions(+), 104 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 99ab094de917e0..41448666ca6480 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -9,7 +9,6 @@ import { noop, startsWith, uniqueId } from 'lodash'; */ import { Button, ExternalLink, VisuallyHidden } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { useRef, useCallback, useState, Fragment, useEffect } from '@wordpress/element'; import { useRef, useCallback, @@ -102,8 +101,8 @@ function LinkControl( { onChange = noop, showInitialSuggestions, forceIsEditingLink, - showCreateEntity, - createEntity, + showCreateEntity, + createEntity, } ) { const wrapperNode = useRef(); const instanceId = useInstanceId( LinkControl ); @@ -115,8 +114,9 @@ function LinkControl( { ? forceIsEditingLink : ! value || ! value.url ); - const [ isResolvingLink, setIsResolvingLink ] = useState( false ); + const [ isResolvingLink, setIsResolvingLink ] = useState( false ); const [ errorMsg, setErrorMsg ] = useState( null ); + const isEndingEditWithFocus = useRef( false ); const { fetchSearchSuggestions } = useSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); @@ -173,10 +173,6 @@ function LinkControl( { setInputValue( val ); }; - setErrorMsg( null ); // remove lingering error messages - setInputValue( '' ); - }; - const handleDirectEntry = ( val ) => { let type = 'URL'; @@ -194,8 +190,8 @@ function LinkControl( { type = 'internal'; } - return Promise.resolve( - [ { + return Promise.resolve( [ + { id: uniqueId(), title: val, url: type === 'URL' ? prependHTTP( val ) : val, @@ -217,7 +213,10 @@ function LinkControl( { // If it's potentially a URL search then concat on a URL search suggestion // just for good measure. That way once the actual results run out we always // have a URL option to fallback on. - results = couldBeURL && ! args.isInitialSuggestions ? results[ 0 ].concat( results[ 1 ] ) : results[ 0 ]; + results = + couldBeURL && ! args.isInitialSuggestions + ? results[ 0 ].concat( results[ 1 ] ) + : results[ 0 ]; // Here we append a faux suggestion to represent a "CREATE" option. This // is detected in the rendering of the search results and handeled as a @@ -230,7 +229,6 @@ function LinkControl( { // keyboard handling, ARIA roles...etc). return results.concat( { id: uniqueId(), - ? results[ 0 ].concat( results[ 1 ] ) title: '', url: '', type: CREATE_TYPE, @@ -261,7 +259,7 @@ function LinkControl( { return handleManualEntry ? handleDirectEntry( val, args ) - return ( maybeURL ) ? handleDirectEntry( val, args ) : handleEntitySearch( val, args ); + : handleEntitySearch( val, args ); }, [ handleDirectEntry, fetchSearchSuggestions ] ); @@ -275,12 +273,18 @@ function LinkControl( { try { newEntity = await createEntity( 'page', inputValue ); } catch ( error ) { - setErrorMsg( error.msg || __( 'An unknown error occurred during Page creation. Please try again.' ) ); + setErrorMsg( + error.msg || + __( + 'An unknown error occurred during Page creation. Please try again.' + ) + ); } setIsResolvingLink( false ); - if ( newEntity ) { // only set if request is resolved + if ( newEntity ) { + // only set if request is resolved onChange( newEntity ); setIsEditingLink( false ); } else { @@ -310,17 +314,26 @@ function LinkControl( { ); const directLinkEntryTypes = [ 'url', 'mailto', 'tel', 'internal' ]; - const isSingleDirectEntryResult = suggestions.length === 1 && directLinkEntryTypes.includes( suggestions[ 0 ].type.toLowerCase() ); - const shouldShowCreateEntity = showCreateEntity && createEntity && ! isSingleDirectEntryResult && ! isInitialSuggestions; - : undefined; - const labelText = isInitialSuggestions - ? __( 'Recently updated' ) + const isSingleDirectEntryResult = + suggestions.length === 1 && + directLinkEntryTypes.includes( + suggestions[ 0 ].type.toLowerCase() + ); + const shouldShowCreateEntity = + showCreateEntity && + createEntity && + ! isSingleDirectEntryResult && + ! isInitialSuggestions; // According to guidelines aria-label should be added if the label // itself is not visible. // See: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role - const searchResultsLabelId = isInitialSuggestions ? `block-editor-link-control-search-results-label-${ instanceId }` : undefined; - const labelText = isInitialSuggestions ? __( 'Recently updated' ) : sprintf( __( 'Search results for %s' ), inputValue ); + const searchResultsLabelId = isInitialSuggestions + ? `block-editor-link-control-search-results-label-${ instanceId }` + : undefined; + const labelText = isInitialSuggestions + ? __( 'Recently updated' ) + : sprintf( __( 'Search results for %s' ), inputValue ); const ariaLabel = isInitialSuggestions ? undefined : labelText; const SearchResultsLabel = ( - { isInitialSuggestions ? ( - -
+
{ suggestions.map( ( suggestion, index ) => { - if ( shouldShowCreateEntity && CREATE_TYPE === suggestion.type ) { + if ( + shouldShowCreateEntity && + CREATE_TYPE === suggestion.type + ) { return ( ); @@ -357,103 +378,78 @@ function LinkControl( { } return ( - { ...suggestionsListProps } - className={ resultsListClasses } - aria-labelledby={ searchResultsLabelId } - > - { suggestions.map( ( suggestion, index ) => ( - { - onChange( { ...value, ...suggestion } ); - stopEditing(); - } } - isSelected={ index === selectedSuggestion } - isURL={ manualLinkEntryTypes.includes( - suggestion.type.toLowerCase() - ) } - searchTerm={ inputValue } - /> - ) ) } - ; + onClick={ () => { + stopEditing(); + onChange( { ...value, ...suggestion } ); + } } + isSelected={ index === selectedSuggestion } + isURL={ directLinkEntryTypes.includes( + suggestion.type.toLowerCase() + ) } + searchTerm={ inputValue } + /> + ); } ) } - - { showCreatePages && createEmptyPage && ! isInitialSuggestions && ! isSingleDirectEntryResult && ( - { - setIsResolvingLink( true ); - const newPage = await createEmptyPage( inputValue ); - // TODO: handle error from API - onChange( { - id: newPage.id, - title: newPage.title.raw, // TODO: use raw or rendered? - url: newPage.link, - type: newPage.type, - } ); - setIsResolvingLink( false ); - setIsEditingLink( false ); - onChange( _result ); - } } - /> - ) }
-
); }; return (
- { isResolvingLink && ( -
- - - { __( 'Creating Page' ) } - - - { __( 'Your new Page is being created' ) }. - - -
- + { isResolvingLink && ( +
+ + + { __( 'Creating Page' ) } + + + { __( 'Your new Page is being created' ) }. + + +
+ ) } { isEditingLink || ! value ? ( { - onChange( { ...value, ...suggestion } ); stopEditing(); + + handleSelectSuggestion( suggestion, value )(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } showInitialSuggestions={ showInitialSuggestions } + errorMsg={ errorMsg } /> - ) } - ) : ( - className="screen-reader-text" -

- > +

{ __( 'Currently selected' ) }:

@@ -490,7 +486,7 @@ function LinkControl( {
- } + ) } { isEditingLink && ! isResolvingLink && ( ) } { ! isEditingLink && ! isResolvingLink && ( - + ) } - ); } From 8d10183700cbab292e89bb832917c48ee5495970 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 09:11:14 -0800 Subject: [PATCH 046/170] Removes unused keypress handler. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This appears never to be called. The prop is not used directly within `URLInput` or “spread” from the passed props. Testing by adding console.log and running the tests confirms that the code is not being run. Manual testing also. --- .../src/components/link-control/search-input.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index aac573fea80108..abad3c3925b54e 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -21,14 +21,6 @@ const handleLinkControlOnKeyDown = ( event ) => { } }; -const handleLinkControlOnKeyPress = ( event ) => { - const { keyCode } = event; - - event.stopPropagation(); - - if ( keyCode === ENTER ) { - } -}; const LinkControlSearchInput = ( { value, @@ -69,7 +61,6 @@ const LinkControlSearchInput = ( { } handleLinkControlOnKeyDown( event ); } } - onKeyPress={ handleLinkControlOnKeyPress } placeholder={ __( 'Search or type url' ) } __experimentalRenderSuggestions={ renderSuggestions } __experimentalFetchLinkSuggestions={ fetchSuggestions } From 7a644a2942b7dab027aa9e6acd4f2f699c20a526 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 09:24:26 -0800 Subject: [PATCH 047/170] Remove redundant onKeyDown prop Again onKeyDown prop is not used within `URLInput`. Nor is it spread from props. Never called in tests. Not called when testing manually either. --- .../components/link-control/search-input.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index abad3c3925b54e..a51acd76259957 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -4,24 +4,12 @@ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button, Notice } from '@wordpress/components'; -import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; -import { keyboardReturn } from '@wordpress/icons'; /** * Internal dependencies */ import { URLInput } from '../'; -const handleLinkControlOnKeyDown = ( event ) => { - const { keyCode } = event; - - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( keyCode ) > -1 ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - event.stopPropagation(); - } -}; - - const LinkControlSearchInput = ( { value, onChange, @@ -55,12 +43,6 @@ const LinkControlSearchInput = ( { value={ value } onChange={ selectItemHandler } onFocus={ selectItemHandler } - onKeyDown={ ( event ) => { - if ( event.keyCode === ENTER ) { - return; - } - handleLinkControlOnKeyDown( event ); - } } placeholder={ __( 'Search or type url' ) } __experimentalRenderSuggestions={ renderSuggestions } __experimentalFetchLinkSuggestions={ fetchSuggestions } From 5b959d9ead93c4e8ed981a5aabf136c44c12293b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 13:04:37 -0800 Subject: [PATCH 048/170] Improve naming of handler functions to better reflect purpose --- .../src/components/link-control/search-input.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index a51acd76259957..89ecc61c68ad56 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -21,12 +21,19 @@ const LinkControlSearchInput = ( { } ) => { const [ selectedSuggestion, setSelectedSuggestion ] = useState(); + /** + * Handles the user moving between different suggestions. Does not handle + * choosing an individual item. + * + * @param {string} selection the url of the selected suggestion + * @param {Object} suggestion the suggestion object + */ const selectItemHandler = ( selection, suggestion ) => { onChange( selection ); setSelectedSuggestion( suggestion ); }; - function selectSuggestionOrCurrentInputValue( event ) { + function handleSubmit( event ) { // Avoid default forms behavior, since it's being handled custom here. event.preventDefault(); @@ -36,7 +43,7 @@ const LinkControlSearchInput = ( { } return ( -
+
Date: Fri, 31 Jan 2020 13:06:39 -0800 Subject: [PATCH 049/170] Fix to allow keyboard and form submit to handle entity creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously only mouse click on the “create” suggestion correctly created a new entity. Improved by ensuring the form `onSubmit` handler also correctly triggers the onCreate handler if the suggestion type is the “create” type. --- .../src/components/link-control/index.js | 33 ++++++++++--------- .../src/components/link-control/test/index.js | 9 +++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 41448666ca6480..7d9f3d1c5a638a 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -9,13 +9,6 @@ import { noop, startsWith, uniqueId } from 'lodash'; */ import { Button, ExternalLink, VisuallyHidden } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { - useRef, - useCallback, - useState, - Fragment, - useEffect, -} from '@wordpress/element'; import { safeDecodeURI, filterURLForDisplay, @@ -227,10 +220,14 @@ function LinkControl( { // API. In addition promoting CREATE to a first class suggestion affords // the a11y benefits afforded by `URLInput` to all suggestions (eg: // keyboard handling, ARIA roles...etc). + // Note also that due to the flow of data and the nature of the + // controlled components the value of the `url` property must correspond + // to the text value of the `` otherwise it will result in an + // incorrectly named entity being created. return results.concat( { id: uniqueId(), - title: '', - url: '', + title: val, // placeholder + url: val, // must match the existing ``s text value type: CREATE_TYPE, } ); }; @@ -264,14 +261,13 @@ function LinkControl( { [ handleDirectEntry, fetchSearchSuggestions ] ); - const handleOnCreate = async () => { + const handleOnCreate = async (entityTitle) => { let newEntity; setIsResolvingLink( true ); setErrorMsg( null ); - try { - newEntity = await createEntity( 'page', inputValue ); + newEntity = await createEntity('page', entityTitle ); } catch ( error ) { setErrorMsg( error.msg || @@ -360,7 +356,9 @@ function LinkControl( { return ( { + handleOnCreate(suggestion.title); + } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( suggestion, @@ -434,9 +432,14 @@ function LinkControl( { value={ inputValue } onChange={ onInputChange } onSelect={ ( suggestion ) => { - stopEditing(); + stopEditing(); - handleSelectSuggestion( suggestion, value )(); + if (suggestion.type && CREATE_TYPE === suggestion.type) { + handleOnCreate(inputValue); + } else { + + handleSelectSuggestion( suggestion, value )(); + } } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 9e8dfe7b711863..5af6a429687c16 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -622,7 +622,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { // TODO: select these by aria relationship to autocomplete rather than arbitary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - + const form = container.querySelector('form'); const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); // Step down into the search results, highlighting the first result item @@ -630,10 +630,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { Simulate.keyDown( searchInput, { keyCode: DOWN } ); } ); - await act( async () => { + act( () => { Simulate.keyDown( createButton, { keyCode: ENTER } ); } ); + await act(async () => { + Simulate.submit(form); + }); + await eventLoopTick(); const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); @@ -643,7 +647,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const currentLinkHTML = currentLink.innerHTML; expect( currentLinkHTML ).toEqual( expect.stringContaining( entityNameText ) ); //title - expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug } ); it( 'should not show not show an option to create an entity when input is empty', async () => { From decd4de430463b79aac20550eeffcf0410032987 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 13:13:27 -0800 Subject: [PATCH 050/170] Tweak to error positioning styling --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 104f3968510bc5..74617dca48143e 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -38,7 +38,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__search-error { - margin: 20px 20px 0 20px; + margin: 0 20px 20px 20px; } .block-editor-link-control__search-actions { From 0f09491b84af5091c47946268424b2eef777ded9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 13:32:09 -0800 Subject: [PATCH 051/170] Update to ensure created Page entities conform to object schema required by LinkControl --- packages/block-library/src/navigation-link/edit.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 1fe58a9917a14b..cbd5186516fbf0 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -38,7 +38,6 @@ import { link as linkIcon } from '@wordpress/icons'; * Internal dependencies */ import { ToolbarSubmenuIcon, ItemSubmenuIcon } from './icons'; -/* eslint-enable import/no-extraneous-dependencies */ function NavigationLinkEdit( { attributes, @@ -241,6 +240,14 @@ function NavigationLinkEdit( { throw new TypeError( 'API response returned invalid entity.', entity ); } + const requiredEntityProps = ['id', 'title', 'link']; + + const entityMissingProperty = requiredEntityProps.find(entityProp => !entity.hasOwnProperty(entityProp)); + + if (Boolean(entityMissingProperty)) { + throw new TypeError(`API response returned invalid entity. Missing required property "${entityMissingProperty}".`, entity); + } + return { id: entity.id, type, From 4c7206a79153cc012a1db248c2a7751b35790079 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 31 Jan 2020 14:31:25 -0800 Subject: [PATCH 052/170] Include test for loading state in create entity tests --- .../src/components/link-control/index.js | 18 +------ .../src/components/link-control/test/index.js | 47 +++++++++++++++---- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 7d9f3d1c5a638a..afbac98a04d683 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -408,22 +408,8 @@ function LinkControl( { className="block-editor-link-control" > { isResolvingLink && ( -
- - - { __( 'Creating Page' ) } - - - { __( 'Your new Page is being created' ) }. - - +
+ Loading...
) } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 5af6a429687c16..35375c53ef9cf6 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -520,6 +520,8 @@ describe( 'Default search suggestions', () => { describe( 'Creating Entities (eg: Posts, Pages)', () => { const noResults = []; beforeEach( () => { + + // Force returning empty results for existing Pages. Doing this means that the only item // shown should be "Create Page" suggestion because there will be no search suggestions // and our input does not conform to a direct entry schema (eg: a URL). @@ -528,7 +530,23 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { it.each( [ [ 'HelloWorld', 'without spaces' ], [ 'Hello World', 'with spaces' ], - ] )( 'should display option to create a link for a valid Entity title "%s" (%s)', async ( entityNameText ) => { + ] )( 'should allow creating a link for a valid Entity title "%s" (%s)', async ( entityNameText ) => { + + + let resolver; + let resolvedEntity; + let currentLinkLabel; + + const createEntity = (type, title) => new Promise((resolve) => { + resolver = resolve; + resolvedEntity = { + type, + title, + id: 123, + url: '/?p=123', + }; + }); + const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); @@ -538,12 +556,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { onChange={ ( suggestion ) => { setLink( suggestion ); } } - createEntity={ ( type, title ) => Promise.resolve( { - type, - title, - id: 123, - url: '/?p=123', - } ) } + createEntity={ createEntity } /> ); }; @@ -569,14 +582,30 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( createButton ).not.toBeNull(); expect( createButton.innerHTML ).toEqual( expect.stringContaining( entityNameText ) ); - await act( async () => { + // No need to wait in this test because we control the Promise + // resolution manually via the `resolver` reference + act( () => { Simulate.click( createButton ); } ); await eventLoopTick(); - const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); + // Check for loading indicator + const loadingIndicator = container.querySelector('.block-editor-link-control__loading'); + currentLinkLabel = container.querySelector('[aria-label="Currently selected"]'); + + expect(currentLinkLabel).toBeNull(); + expect(loadingIndicator.innerHTML).toEqual(expect.stringContaining('Loading')); + + // Resolve the `createEntity` promise + await act( async () => { + resolver(resolvedEntity); + }); + + await eventLoopTick(); + // Test the new entity was displayed. + currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); const currentLinkHTML = currentLink.innerHTML; From 3263b5e597ef207e99c1f1c70a3d0ce06647403b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 11:06:56 +0000 Subject: [PATCH 053/170] Fix errors introduced during rebase --- .../src/components/link-control/index.js | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index afbac98a04d683..5d6396ca70cba2 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -9,6 +9,13 @@ import { noop, startsWith, uniqueId } from 'lodash'; */ import { Button, ExternalLink, VisuallyHidden } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; +import { + useRef, + useCallback, + useState, + Fragment, + useEffect, +} from '@wordpress/element'; import { safeDecodeURI, filterURLForDisplay, @@ -251,23 +258,23 @@ function LinkControl( { ( val, args ) => { const isInternal = startsWith( val, '#' ); - const handleManualEntry = + const maybeURL = isInternal || isURL( val ) || ( val && val.includes( 'www.' ) ); - return handleManualEntry + return maybeURL ? handleDirectEntry( val, args ) : handleEntitySearch( val, args ); }, [ handleDirectEntry, fetchSearchSuggestions ] ); - const handleOnCreate = async (entityTitle) => { + const handleOnCreate = async ( entityTitle ) => { let newEntity; setIsResolvingLink( true ); setErrorMsg( null ); try { - newEntity = await createEntity('page', entityTitle ); + newEntity = await createEntity( 'page', entityTitle ); } catch ( error ) { setErrorMsg( error.msg || @@ -343,6 +350,12 @@ function LinkControl( { return (
+ { isInitialSuggestions ? ( + SearchResultsLabel + ) : ( + { SearchResultsLabel } + ) } +
{ - handleOnCreate(suggestion.title); - } } + handleOnCreate( suggestion.title ); + } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( suggestion, @@ -420,10 +433,12 @@ function LinkControl( { onSelect={ ( suggestion ) => { stopEditing(); - if (suggestion.type && CREATE_TYPE === suggestion.type) { - handleOnCreate(inputValue); + if ( + suggestion.type && + CREATE_TYPE === suggestion.type + ) { + handleOnCreate( inputValue ); } else { - handleSelectSuggestion( suggestion, value )(); } } } From cc9bc97f24685c03c02442e865a54103e26a35a4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 13:16:10 +0000 Subject: [PATCH 054/170] Fix implementation broken due to rebase. --- packages/block-editor/src/components/link-control/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 5d6396ca70cba2..b9803f4a3eb7b8 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -431,8 +431,6 @@ function LinkControl( { value={ inputValue } onChange={ onInputChange } onSelect={ ( suggestion ) => { - stopEditing(); - if ( suggestion.type && CREATE_TYPE === suggestion.type @@ -441,6 +439,10 @@ function LinkControl( { } else { handleSelectSuggestion( suggestion, value )(); } + + // Must be called after handling to ensure focus is + // managed correctly. + stopEditing(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } From c28a41f50afb059a43eafce55840492d53eb86a5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 14:17:57 +0000 Subject: [PATCH 055/170] Add i18n for Loading state --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index b9803f4a3eb7b8..6b5a276baa291a 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -422,7 +422,7 @@ function LinkControl( { > { isResolvingLink && (
- Loading... + { __( 'Loading' ) }...
) } From 579490cd80dece7f468b45c57c67c0392d460974 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 14:20:48 +0000 Subject: [PATCH 056/170] Ensure correct timing of stopEditing by awaiting entity creation promise --- packages/block-editor/src/components/link-control/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 6b5a276baa291a..4754fb8922b8d2 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -430,12 +430,13 @@ function LinkControl( { { + onSelect={ async ( suggestion ) => { if ( suggestion.type && CREATE_TYPE === suggestion.type ) { - handleOnCreate( inputValue ); + // Await the promise to ensure `stopEditing` is not called prematurely. + await handleOnCreate( inputValue ); } else { handleSelectSuggestion( suggestion, value )(); } From faaf3fa7262d99891eda3092cbe6a8087df63070 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 14:35:27 +0000 Subject: [PATCH 057/170] Fixes render conditionals to be mutually exclusive Rebasing with master caused conditionals to be normalised into one. Now we have `isResolving` as state we need to ensure the component renders differently with this conditional involved. The clearest means of doing this is to break the render up into exclusive sections based on conditions. --- packages/block-editor/src/components/link-control/index.js | 6 ++++-- .../block-editor/src/components/link-control/style.scss | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4754fb8922b8d2..62251060a614a5 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -426,7 +426,7 @@ function LinkControl( {
) } - { isEditingLink || ! value ? ( + { ( isEditingLink || ! value ) && ! isResolvingLink && ( - ) : ( + ) } + + { ! isEditingLink && ! isResolvingLink && (

Date: Tue, 4 Feb 2020 14:37:42 +0000 Subject: [PATCH 058/170] Fix broken conditional exposed by test failures. --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 62251060a614a5..f20f26766e73f2 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -452,7 +452,7 @@ function LinkControl( { /> ) } - { ! isEditingLink && ! isResolvingLink && ( + { value && ! isEditingLink && ! isResolvingLink && (

Date: Tue, 4 Feb 2020 14:51:48 +0000 Subject: [PATCH 059/170] Fix to stop users without create pages permission seeing the Create entity UI --- .../block-library/src/navigation-link/edit.js | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index cbd5186516fbf0..0508b092252cee 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -52,6 +52,7 @@ function NavigationLinkEdit( { rgbTextColor, rgbBackgroundColor, saveEntityRecord, + userCanCreatePages = false, } ) { const { label, opensInNewTab, url, nofollow, description } = attributes; const link = { @@ -115,6 +116,43 @@ function NavigationLinkEdit( { selection.addRange( range ); } + function handleCreateEntity( type, entityTitle ) { + return saveEntityRecord( 'postType', type, { + title: entityTitle, + status: 'publish', + } ).then( ( entity ) => { + // `entity` may not reject the Promise + // but may still be invalid. Here we + // tests for unexpected values and throw accordingly. + if ( null === entity || undefined === entity ) { + throw new TypeError( + 'API response returned invalid entity.', + entity + ); + } + + const requiredEntityProps = [ 'id', 'title', 'link' ]; + + const entityMissingProperty = requiredEntityProps.find( + ( entityProp ) => ! entity.hasOwnProperty( entityProp ) + ); + + if ( Boolean( entityMissingProperty ) ) { + throw new TypeError( + `API response returned invalid entity. Missing required property "${ entityMissingProperty }".`, + entity + ); + } + + return { + id: entity.id, + type, + title: entity.title.raw, // TODO: use raw or rendered? + url: entity.link, + }; + } ); + } + return ( @@ -228,33 +266,10 @@ function NavigationLinkEdit( { value={ link } showInitialSuggestions={ true } showCreateEntity={ true } - createEntity={ ( type, entityTitle ) => - saveEntityRecord( 'postType', type, { - title: entityTitle, - status: 'publish', - } ).then( ( entity ) => { - // `entity` may not reject the Promise - // but may still be invalid. Here we - // tests for unexpected values and throw accordingly. - if ( null === entity || undefined === entity ) { - throw new TypeError( 'API response returned invalid entity.', entity ); - } - - const requiredEntityProps = ['id', 'title', 'link']; - - const entityMissingProperty = requiredEntityProps.find(entityProp => !entity.hasOwnProperty(entityProp)); - - if (Boolean(entityMissingProperty)) { - throw new TypeError(`API response returned invalid entity. Missing required property "${entityMissingProperty}".`, entity); - } - - return { - id: entity.id, - type, - title: entity.title.raw, // TODO: use raw or rendered? - url: entity.link, - }; - } ) + createEntity={ + userCanCreatePages + ? handleCreateEntity + : undefined } onChange={ ( { title: newTitle = '', @@ -350,12 +365,19 @@ export default compose( [ !! navigationBlockAttributes.showSubmenuIcon && hasDescendants; const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); + const userCanCreatePages = select( 'core' ).canUser( + 'create', + 'pages' + ); + return { isParentOfSelectedBlock, hasDescendants, showSubmenuIcon, textColor: navigationBlockAttributes.textColor, backgroundColor: navigationBlockAttributes.backgroundColor, + navigationBlockAttributes, + userCanCreatePages, rgbTextColor: getColorObjectByColorSlug( colors, navigationBlockAttributes.textColor, From 8203176735ee42db1178300b9354b8970117fc57 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 4 Feb 2020 15:52:21 +0000 Subject: [PATCH 060/170] Add test to verify Create UI not shown if valid handler not provided to component --- .../src/components/link-control/index.js | 2 - .../src/components/link-control/test/index.js | 491 +++++++++++------- 2 files changed, 303 insertions(+), 190 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f20f26766e73f2..60f5315792b4d6 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -101,7 +101,6 @@ function LinkControl( { onChange = noop, showInitialSuggestions, forceIsEditingLink, - showCreateEntity, createEntity, } ) { const wrapperNode = useRef(); @@ -323,7 +322,6 @@ function LinkControl( { suggestions[ 0 ].type.toLowerCase() ); const shouldShowCreateEntity = - showCreateEntity && createEntity && ! isSingleDirectEntryResult && ! isInitialSuggestions; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 35375c53ef9cf6..4b821937869cd0 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -520,99 +520,129 @@ describe( 'Default search suggestions', () => { describe( 'Creating Entities (eg: Posts, Pages)', () => { const noResults = []; beforeEach( () => { - - // Force returning empty results for existing Pages. Doing this means that the only item // shown should be "Create Page" suggestion because there will be no search suggestions // and our input does not conform to a direct entry schema (eg: a URL). - mockFetchSearchSuggestions.mockImplementation( () => Promise.resolve( noResults ) ); + mockFetchSearchSuggestions.mockImplementation( () => + Promise.resolve( noResults ) + ); } ); it.each( [ [ 'HelloWorld', 'without spaces' ], [ 'Hello World', 'with spaces' ], - ] )( 'should allow creating a link for a valid Entity title "%s" (%s)', async ( entityNameText ) => { + ] )( + 'should allow creating a link for a valid Entity title "%s" (%s)', + async ( entityNameText ) => { + let resolver; + let resolvedEntity; + let currentLinkLabel; + + const createEntity = ( type, title ) => + new Promise( ( resolve ) => { + resolver = resolve; + resolvedEntity = { + type, + title, + id: 123, + url: '/?p=123', + }; + } ); + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); - let resolver; - let resolvedEntity; - let currentLinkLabel; - - const createEntity = (type, title) => new Promise((resolve) => { - resolver = resolve; - resolvedEntity = { - type, - title, - id: 123, - url: '/?p=123', + return ( + { + setLink( suggestion ); + } } + createEntity={ createEntity } + /> + ); }; - }); - const LinkControlConsumer = () => { - const [ link, setLink ] = useState( null ); - - return ( { - setLink( suggestion ); - } } - createEntity={ createEntity } - /> ); - }; - - act( () => { - render( , container ); - } ); + act( () => { + render( , container ); + } ); - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: entityNameText } } ); - } ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: entityNameText }, + } ); + } ); - await eventLoopTick(); + await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); - const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); - expect( createButton ).not.toBeNull(); - expect( createButton.innerHTML ).toEqual( expect.stringContaining( entityNameText ) ); + expect( createButton ).not.toBeNull(); + expect( createButton.innerHTML ).toEqual( + expect.stringContaining( entityNameText ) + ); - // No need to wait in this test because we control the Promise - // resolution manually via the `resolver` reference - act( () => { - Simulate.click( createButton ); - } ); + // No need to wait in this test because we control the Promise + // resolution manually via the `resolver` reference + act( () => { + Simulate.click( createButton ); + } ); - await eventLoopTick(); + await eventLoopTick(); - // Check for loading indicator - const loadingIndicator = container.querySelector('.block-editor-link-control__loading'); - currentLinkLabel = container.querySelector('[aria-label="Currently selected"]'); + // Check for loading indicator + const loadingIndicator = container.querySelector( + '.block-editor-link-control__loading' + ); + currentLinkLabel = container.querySelector( + '[aria-label="Currently selected"]' + ); - expect(currentLinkLabel).toBeNull(); - expect(loadingIndicator.innerHTML).toEqual(expect.stringContaining('Loading')); + expect( currentLinkLabel ).toBeNull(); + expect( loadingIndicator.innerHTML ).toEqual( + expect.stringContaining( 'Loading' ) + ); - // Resolve the `createEntity` promise - await act( async () => { - resolver(resolvedEntity); - }); + // Resolve the `createEntity` promise + await act( async () => { + resolver( resolvedEntity ); + } ); - await eventLoopTick(); + await eventLoopTick(); - // Test the new entity was displayed. - currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); - const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); + // Test the new entity was displayed. + currentLinkLabel = container.querySelector( + '[aria-label="Currently selected"]' + ); + const currentLink = container.querySelector( + `[aria-labelledby="${ currentLinkLabel.id }"]` + ); - const currentLinkHTML = currentLink.innerHTML; + const currentLinkHTML = currentLink.innerHTML; - expect( currentLinkHTML ).toEqual( expect.stringContaining( entityNameText ) ); //title - expect( currentLinkHTML ).toEqual( expect.stringContaining( '/?p=123' ) ); // slug - } ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( entityNameText ) + ); //title + expect( currentLinkHTML ).toEqual( + expect.stringContaining( '/?p=123' ) + ); // slug + } + ); it( 'should allow creation of entities via the keyboard', async () => { const entityNameText = 'A new page to be created'; @@ -620,19 +650,23 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const LinkControlConsumer = () => { const [ link, setLink ] = useState( null ); - return ( { - setLink( suggestion ); - } } - createEntity={ ( type, title ) => Promise.resolve( { - type, - title, - id: 123, - url: '/?p=123', - } ) } - /> ); + return ( + { + setLink( suggestion ); + } } + createEntity={ ( type, title ) => + Promise.resolve( { + type, + title, + id: 123, + url: '/?p=123', + } ) + } + /> + ); }; act( () => { @@ -640,19 +674,29 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { } ); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); // Simulate searching for a term act( () => { - Simulate.change( searchInput, { target: { value: entityNameText } } ); + Simulate.change( searchInput, { + target: { value: entityNameText }, + } ); } ); await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const form = container.querySelector('form'); - const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const form = container.querySelector( 'form' ); + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); // Step down into the search results, highlighting the first result item act( () => { @@ -663,140 +707,211 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { Simulate.keyDown( createButton, { keyCode: ENTER } ); } ); - await act(async () => { - Simulate.submit(form); - }); + await act( async () => { + Simulate.submit( form ); + } ); await eventLoopTick(); - const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); + const currentLinkLabel = container.querySelector( + '[aria-label="Currently selected"]' + ); - const currentLink = container.querySelector( `[aria-labelledby="${ currentLinkLabel.id }"]` ); + const currentLink = container.querySelector( + `[aria-labelledby="${ currentLinkLabel.id }"]` + ); const currentLinkHTML = currentLink.innerHTML; - expect( currentLinkHTML ).toEqual( expect.stringContaining( entityNameText ) ); //title + expect( currentLinkHTML ).toEqual( + expect.stringContaining( entityNameText ) + ); //title } ); - it( 'should not show not show an option to create an entity when input is empty', async () => { - act( () => { - render( - , container + describe( 'No create option', () => { + it.each( [ [ undefined ], [ null ], [ false ] ] )( + 'should not show not show an option to create an entity when "createEntity" handler is %s', + async ( handler ) => { + act( () => { + render( + , + container + ); + } ); + // Await the initial suggestions to be fetched + await eventLoopTick(); + + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); + + // Verify input has no value + expect( searchInput.value ).toBe( '' ); + expect( createButton ).toBeFalsy(); // shouldn't exist! + } + ); + + it( 'should not show not show an option to create an entity when input is empty', async () => { + act( () => { + render( + , + container + ); + } ); + // Await the initial suggestions to be fetched + await eventLoopTick(); + + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' ); - } ); - // Await the initial suggestions to be fetched - await eventLoopTick(); - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + // Verify input has no value + expect( searchInput.value ).toBe( '' ); + expect( createButton ).toBeFalsy(); // shouldn't exist! + } ); - // Verify input has no value - expect( searchInput.value ).toBe( '' ); - expect( createButton ).toBeFalsy(); // shouldn't exist! + // it.each( [ + // 'https://wordpress.org', + // 'www.wordpress.org', + // 'mailto:example123456@wordpress.org', + // 'tel:example123456@wordpress.org', + // '#internal-anchor', + // ] )( 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', async ( inputText ) => { + // act( () => { + // render( + // , container + // ); + // } ); + + // // Search Input UI + // const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // // Simulate searching for a term + // act( () => { + // Simulate.change( searchInput, { target: { value: inputText } } ); + // } ); + + // await eventLoopTick(); + + // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + // const createButton = Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ); + + // expect( createButton ).toBeNull(); + // } ); } ); - // it.each( [ - // 'https://wordpress.org', - // 'www.wordpress.org', - // 'mailto:example123456@wordpress.org', - // 'tel:example123456@wordpress.org', - // '#internal-anchor', - // ] )( 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', async ( inputText ) => { - // act( () => { - // render( - // , container - // ); - // } ); - - // // Search Input UI - // const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - - // // Simulate searching for a term - // act( () => { - // Simulate.change( searchInput, { target: { value: inputText } } ); - // } ); - - // await eventLoopTick(); - - // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - // const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - - // const createButton = Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ); - - // expect( createButton ).toBeNull(); - // } ); - - it( 'should display human readable error notice and re-show create button and search input if page creation request fails', async () => { - const searchText = 'This page to be created'; - let searchInput; + describe( 'Error handling', () => { + it( 'should display human readable error notice and re-show create button and search input if page creation request fails', async () => { + const searchText = 'This page to be created'; + let searchInput; - const throwsError = () => { - throw new Error( 'API response returned invalid entity.' ); // this can be any error and msg - }; + const throwsError = () => { + throw new Error( 'API response returned invalid entity.' ); // this can be any error and msg + }; - const createEntity = () => Promise.reject( throwsError() ); + const createEntity = () => Promise.reject( throwsError() ); - act( () => { - render( - , container - ); - } ); + act( () => { + render( + , + container + ); + } ); - // Search Input UI - searchInput = container.querySelector( 'input[aria-label="URL"]' ); + // Search Input UI + searchInput = container.querySelector( 'input[aria-label="URL"]' ); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchText } } ); - } ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: searchText }, + } ); + } ); - await eventLoopTick(); + await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - let searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - let createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + let searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + let createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); - await act( async () => { - Simulate.click( createButton ); - } ); + await act( async () => { + Simulate.click( createButton ); + } ); - await eventLoopTick(); + await eventLoopTick(); - searchInput = container.querySelector( 'input[aria-label="URL"]' ); + searchInput = container.querySelector( 'input[aria-label="URL"]' ); - // This is a Notice component wrapped in an aria-live div with role of "alert". - const errorNotice = container.querySelector( '[role="alert"]' ); + // This is a Notice component wrapped in an aria-live div with role of "alert". + const errorNotice = container.querySelector( '[role="alert"]' ); - // Catch the error in the test to avoid test failures - expect( throwsError ).toThrow( Error ); + // Catch the error in the test to avoid test failures + expect( throwsError ).toThrow( Error ); - // Check human readable error notice is displayed - expect( errorNotice ).not.toBeFalsy(); - expect( errorNotice.innerHTML ).toEqual( expect.stringContaining( 'An unknown error occurred during Page creation. Please try again.' ) ); + // Check human readable error notice is displayed + expect( errorNotice ).not.toBeFalsy(); + expect( errorNotice.innerHTML ).toEqual( + expect.stringContaining( + 'An unknown error occurred during Page creation. Please try again.' + ) + ); - // Verify input is repopulated with original search text - expect( searchInput ).not.toBeFalsy(); - expect( searchInput.value ).toBe( searchText ); + // Verify input is repopulated with original search text + expect( searchInput ).not.toBeFalsy(); + expect( searchInput.value ).toBe( searchText ); - // Verify search results are re-shown and create button is available. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - createButton = first( Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ) ); + // Verify search results are re-shown and create button is available. + searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) + ); - expect( createButton ).not.toBeFalsy(); // shouldn't exist! + expect( createButton ).not.toBeFalsy(); // shouldn't exist! + } ); } ); } ); From fe7a96f31ccaf4526f3a0b079a7f67016bfc7b20 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 4 Feb 2020 14:49:40 -0600 Subject: [PATCH 061/170] Renamed 'Loading...' to 'Creating Page' to match mockup --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 60f5315792b4d6..b88d7556d798e1 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -420,7 +420,7 @@ function LinkControl( { > { isResolvingLink && (

- { __( 'Loading' ) }... + { __( 'Creating Page' ) }
) } From 3263bb59fac22ea8f93e93e4d745ee1310816c1e Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 4 Feb 2020 15:06:55 -0600 Subject: [PATCH 062/170] Adds the component to the Creating Page loading state --- .../block-editor/src/components/link-control/index.js | 9 +++++++-- .../block-editor/src/components/link-control/style.scss | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index b88d7556d798e1..e8ea1c0209129b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -7,7 +7,12 @@ import { noop, startsWith, uniqueId } from 'lodash'; /** * WordPress dependencies */ -import { Button, ExternalLink, VisuallyHidden } from '@wordpress/components'; +import { + Button, + ExternalLink, + Spinner, + VisuallyHidden, +} from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useRef, @@ -420,7 +425,7 @@ function LinkControl( { > { isResolvingLink && (
- { __( 'Creating Page' ) } + { __( 'Creating Page' ) }
) } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index f907180bf31307..327f84d0bb6e15 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -195,6 +195,12 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__loading { margin: $grid-size-large; // when only loading control is shown it requires it's own spacing. + display: flex; + align-items: center; + + .components-spinner { + margin-top: 0; + } } .block-editor-link-control__search-create { From b1925205ba10647a176bcd146fbfc443bb520514 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 4 Feb 2020 15:44:27 -0600 Subject: [PATCH 063/170] Reduced padding on bottom of LinkControl list results and moved error message up to match design specs more closely. --- packages/block-editor/src/components/link-control/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 327f84d0bb6e15..8928b67dd5fe88 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -38,7 +38,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__search-error { - margin: 0 20px 20px 20px; + margin: -$grid-size-large/2 $grid-size-large $grid-size-large; } .block-editor-link-control__search-actions { @@ -97,7 +97,7 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__search-results { margin: 0; - padding: $grid-size-large/2 $grid-size-large $grid-size-large; + padding: $grid-size-large/2 $grid-size-large $grid-size-large/2; max-height: 200px; overflow-y: auto; // allow results list to scroll From ee4ce2c072816f3404f71c5d513badbd0ae558a0 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 4 Feb 2020 13:30:18 -0800 Subject: [PATCH 064/170] Only add separator when it needs separating We only need a separator when there are other suggestions the Create button needs separation from. --- packages/block-editor/src/components/link-control/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 8928b67dd5fe88..6f9845dc2a4203 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -203,7 +203,8 @@ $block-editor-link-control-number-of-actions: 1; } } -.block-editor-link-control__search-create { +// Separate Create button when following other suggestions. +.components-button + .block-editor-link-control__search-create { margin-top: 20px; overflow: visible; From 996a99264f4d85473f29a64b172d07d015b3b0a4 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 4 Feb 2020 16:05:10 -0600 Subject: [PATCH 065/170] Committed formatted js --- .../link-control/search-create-button.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 12e047de21bcb7..8ca44502e65ce3 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -7,21 +7,29 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { - Button, - Icon, -} from '@wordpress/components'; +import { Button, Icon } from '@wordpress/components'; -export const LinkControlSearchCreate = ( { searchTerm = '', onClick, itemProps, isSelected } ) => { +export const LinkControlSearchCreate = ( { + searchTerm = '', + onClick, + itemProps, + isSelected, +} ) => { return (
- { errorMsg && ( + { errorMessage && ( - { errorMsg } + { errorMessage } ) }
From f67c538aaeec1488fe41ab30e2af0cfccab11d55 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:15:02 +0000 Subject: [PATCH 071/170] Remove unnecessary cast to Boolean Co-Authored-By: Andrew Duthie --- packages/block-library/src/navigation-link/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 0508b092252cee..c25b0a3e5d9191 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -137,7 +137,7 @@ function NavigationLinkEdit( { ( entityProp ) => ! entity.hasOwnProperty( entityProp ) ); - if ( Boolean( entityMissingProperty ) ) { + if ( entityMissingProperty ) { throw new TypeError( `API response returned invalid entity. Missing required property "${ entityMissingProperty }".`, entity From c38f0724723836e017762b2780af27e0dbe66862 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:15:37 +0000 Subject: [PATCH 072/170] Update to utilise `rendered` title value. Co-Authored-By: Andrew Duthie --- packages/block-library/src/navigation-link/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index c25b0a3e5d9191..3bec94f1b4a729 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -147,7 +147,7 @@ function NavigationLinkEdit( { return { id: entity.id, type, - title: entity.title.raw, // TODO: use raw or rendered? + title: entity.title.rendered, url: entity.link, }; } ); From 6f97a87f640488aaf3362048a0ceea80a3ed7198 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:18:00 +0000 Subject: [PATCH 073/170] Remove redundant prop usage --- packages/block-library/src/navigation-link/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 3bec94f1b4a729..1f3c8b980354c0 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -265,7 +265,6 @@ function NavigationLinkEdit( { className="wp-block-navigation-link__inline-link-input" value={ link } showInitialSuggestions={ true } - showCreateEntity={ true } createEntity={ userCanCreatePages ? handleCreateEntity From 015e5d34c0f529d100a125f212dd44e65dc9923e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:21:22 +0000 Subject: [PATCH 074/170] Restore commented out test This should never have been committed with commented out code. --- .../src/components/link-control/test/index.js | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 507063091397ac..3b5e1181841d15 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -728,7 +728,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ); //title } ); - describe( 'No create option', () => { + describe( 'Do not show create option', () => { it.each( [ [ undefined ], [ null ], [ false ] ] )( 'should not show not show an option to create an entity when "createEntity" handler is %s', async ( handler ) => { @@ -796,39 +796,53 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( createButton ).toBeFalsy(); // shouldn't exist! } ); - // it.each( [ - // 'https://wordpress.org', - // 'www.wordpress.org', - // 'mailto:example123456@wordpress.org', - // 'tel:example123456@wordpress.org', - // '#internal-anchor', - // ] )( 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', async ( inputText ) => { - // act( () => { - // render( - // , container - // ); - // } ); - - // // Search Input UI - // const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - - // // Simulate searching for a term - // act( () => { - // Simulate.change( searchInput, { target: { value: inputText } } ); - // } ); - - // await eventLoopTick(); - - // // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - // const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - - // const createButton = Array.from( searchResultElements ).filter( ( result ) => result.innerHTML.includes( 'Create new' ) ); - - // expect( createButton ).toBeNull(); - // } ); + it.each( [ + 'https://wordpress.org', + 'www.wordpress.org', + 'mailto:example123456@wordpress.org', + 'tel:example123456@wordpress.org', + '#internal-anchor', + ] )( + 'should not show option to "Create Page" when text is a form of direct entry (eg: %s)', + async ( inputText ) => { + act( () => { + render( + , + container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: inputText }, + } ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + + const createButton = Array.from( + searchResultElements + ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ); + + expect( createButton ).toBeNull(); + } + ); } ); describe( 'Error handling', () => { From 5fe3d2a7c38b4c951a8fa3d7a350c57cfeca60b0 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:48:18 +0000 Subject: [PATCH 075/170] =?UTF-8?q?Update=20to=20invert=20create=20entity?= =?UTF-8?q?=20=E2=80=9Ctype=E2=80=9D=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously `LInkControl` was specifiying the type of `page`. This meant it was “aware” of the entity being created. This commit inverts that so that the consuming component is now required to define the type itself (this has been updated on Nav Block). --- .../block-editor/src/components/link-control/index.js | 2 +- .../src/components/link-control/test/index.js | 8 ++++---- packages/block-library/src/navigation-link/edit.js | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 03bbadb4993366..7a3d1c0770ab52 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -274,7 +274,7 @@ function LinkControl( { setIsResolvingLink( true ); setErrorMessage( null ); try { - newEntity = await createEntity( 'page', entityTitle ); + newEntity = await createEntity( entityTitle ); } catch ( error ) { setErrorMessage( error.msg || diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 3b5e1181841d15..6807b684624980 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -537,14 +537,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { let resolvedEntity; let currentLinkLabel; - const createEntity = ( type, title ) => + const createEntity = ( title ) => new Promise( ( resolve ) => { resolver = resolve; resolvedEntity = { - type, title, id: 123, url: '/?p=123', + type: 'page', }; } ); @@ -657,12 +657,12 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { onChange={ ( suggestion ) => { setLink( suggestion ); } } - createEntity={ ( type, title ) => + createEntity={ ( title ) => Promise.resolve( { - type, title, id: 123, url: '/?p=123', + type: 'page', } ) } /> diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 1f3c8b980354c0..3406ca983b2a42 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -116,7 +116,8 @@ function NavigationLinkEdit( { selection.addRange( range ); } - function handleCreateEntity( type, entityTitle ) { + function handleCreateEntity( entityTitle ) { + const type = 'page'; return saveEntityRecord( 'postType', type, { title: entityTitle, status: 'publish', From a623d85c5e736798ccb6fb773c239a24626a1a32 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 12:49:11 +0000 Subject: [PATCH 076/170] Removes outdated `showCreateEntity` prop --- .../src/components/link-control/test/index.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 6807b684624980..f78a89dd934917 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -554,7 +554,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { return ( { setLink( suggestion ); } } @@ -653,7 +652,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { return ( { setLink( suggestion ); } } @@ -767,7 +765,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { render( , container @@ -807,10 +804,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { async ( inputText ) => { act( () => { render( - , + , container ); } ); @@ -858,10 +852,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { act( () => { render( - , + , container ); } ); From e2aff999034c82fe2d7b2c97b48747c231588c7d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:07:40 +0000 Subject: [PATCH 077/170] Fix so that CREATE option not displayed if result is a direct entry URL Also amends tests which were incorrectly asserting. --- .../src/components/link-control/index.js | 25 +++++++++++-------- .../src/components/link-control/test/index.js | 10 ++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 7a3d1c0770ab52..f4b8c8d84c9c8b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -232,11 +232,13 @@ function LinkControl( { // controlled components the value of the `url` property must correspond // to the text value of the `` otherwise it will result in an // incorrectly named entity being created. - return results.concat( { - title: val, // placeholder - url: val, // must match the existing ``s text value - type: CREATE_TYPE, - } ); + return maybeURL( val ) + ? results + : results.concat( { + title: val, // placeholder + url: val, // must match the existing ``s text value + type: CREATE_TYPE, + } ); }; /** @@ -253,15 +255,16 @@ function LinkControl( { setIsEditingLink( false ); } + function maybeURL( val ) { + const isInternal = startsWith( val, '#' ); + + return isInternal || isURL( val ) || ( val && val.includes( 'www.' ) ); + } + // Effects const getSearchHandler = useCallback( ( val, args ) => { - const isInternal = startsWith( val, '#' ); - - const maybeURL = - isInternal || isURL( val ) || ( val && val.includes( 'www.' ) ); - - return maybeURL + return maybeURL( val ) ? handleDirectEntry( val, args ) : handleEntitySearch( val, args ); }, diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index f78a89dd934917..3c613eef06e032 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -828,13 +828,13 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { '[role="listbox"] [role="option"]' ); - const createButton = Array.from( - searchResultElements - ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'Create new' ) + ) ); - expect( createButton ).toBeNull(); + expect( createButton ).toBeFalsy(); // shouldn't exist! } ); } ); From 704341c0c8f2b260d7e70029edaf3ec101eb25df Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:08:13 +0000 Subject: [PATCH 078/170] Fix typo --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f4b8c8d84c9c8b..bb796322a85fef 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -242,7 +242,7 @@ function LinkControl( { }; /** - * Detmineres which type of search handler to use based on the users input. + * Determines which type of search handler to use based on the users input. * Cancels editing state and marks that focus may need to be restored after * the next render, if focus was within the wrapper when editing finished. * else a API search should made for matching Entities. From c81c7a7a6ed1abdeb2d88c53d09ac93a0c575ea3 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:10:00 +0000 Subject: [PATCH 079/170] Adds docblock to newly extracted function for clarity --- packages/block-editor/src/components/link-control/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bb796322a85fef..e66ae3a8ecb27b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -255,6 +255,13 @@ function LinkControl( { setIsEditingLink( false ); } + /** + * Determines whether a given value could be a URL. Note this does not + * guarantee the value is a URL only that it looks like it might be one + * (thus the naming of the method). + * + * @param {string} val the candidate for being a valid URL (or not). + */ function maybeURL( val ) { const isInternal = startsWith( val, '#' ); From 27d534c37e0a8e2a70a7add2b5c5121dd06fb42d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:14:48 +0000 Subject: [PATCH 080/170] Corrects comment which implied a specific visual layout and DOM order Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375011570 --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 6f9845dc2a4203..d782b0134a0e69 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -6,7 +6,7 @@ $block-editor-link-control-number-of-actions: 1; } // Provides positioning context for reset button. Without this then when an -// error notice is displayed above the input the reset button is incorrectly positioned. +// error notice is displayed the input's reset button is incorrectly positioned. .block-editor-link-control__search-input-wrapper { position: relative; } From 1bbd5d3030e939196c699c9d3d988e2e1cd4122f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:21:47 +0000 Subject: [PATCH 081/170] =?UTF-8?q?Fix=20to=20use=20standardised=20?= =?UTF-8?q?=E2=80=9Cgray=E2=80=9D=20var=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375011767 --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index d782b0134a0e69..b248367b8bd288 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -217,7 +217,7 @@ $block-editor-link-control-number-of-actions: 1; left: 0; display: block; width: 100%; - border-top: 1px solid #ccc; + border-top: 1px solid $dark-gray-150; } } From 4119ddb8ddd9aef56061fce41ed1bf107efd7b75 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:30:23 +0000 Subject: [PATCH 082/170] Correct comment to reference the correct property. Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375012024 --- .../block-editor/src/components/link-control/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index e66ae3a8ecb27b..6b8f5c556fd3d4 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -229,14 +229,14 @@ function LinkControl( { // the a11y benefits afforded by `URLInput` to all suggestions (eg: // keyboard handling, ARIA roles...etc). // Note also that due to the flow of data and the nature of the - // controlled components the value of the `url` property must correspond - // to the text value of the `` otherwise it will result in an - // incorrectly named entity being created. + // controlled components the value of the `title` property must correspond + // to the text value of the ``. This is because `title` is used + // when creating the entity. return maybeURL( val ) ? results : results.concat( { - title: val, // placeholder - url: val, // must match the existing ``s text value + title: val, // // must match the existing ``s text value + url: '', // placeholder - not used when creating type: CREATE_TYPE, } ); }; From 64fc2b2433b8849f755442307565920ab1b5a4de Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:32:08 +0000 Subject: [PATCH 083/170] Remove partially applied function as not necessary Addresses * https://github.com/WordPress/gutenberg/pull/19775#discussion_r375012428 * https://github.com/WordPress/gutenberg/pull/19775#discussion_r375012551 --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 6b8f5c556fd3d4..7504ff559e26b8 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -305,7 +305,7 @@ function LinkControl( { } }; - const handleSelectSuggestion = ( suggestion, _value = {} ) => () => { + const handleSelectSuggestion = ( suggestion, _value = {} ) => { setIsEditingLink( false ); onChange( { ..._value, ...suggestion } ); }; @@ -447,7 +447,7 @@ function LinkControl( { // Await the promise to ensure `stopEditing` is not called prematurely. await handleOnCreate( inputValue ); } else { - handleSelectSuggestion( suggestion, value )(); + handleSelectSuggestion( suggestion, value ); } // Must be called after handling to ensure focus is From 313dad272eef0fc8e9c578c42fb14a7604dde55b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 13:35:14 +0000 Subject: [PATCH 084/170] Reinstate descriptive function name Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375013143 --- .../block-editor/src/components/link-control/search-input.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 77562a3a307234..4aec3dce450e41 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -33,7 +33,7 @@ const LinkControlSearchInput = ( { setSelectedSuggestion( suggestion ); }; - function handleSubmit( event ) { + function selectSuggestionOrCurrentInputValue( event ) { // Avoid default forms behavior, since it's being handled custom here. event.preventDefault(); @@ -43,7 +43,7 @@ const LinkControlSearchInput = ( { } return ( - +
Date: Wed, 5 Feb 2020 15:41:45 +0000 Subject: [PATCH 085/170] Rename `createEntity` prop to `createSuggestion` Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r374795242 --- .../src/components/link-control/index.js | 14 ++++++------- .../src/components/link-control/test/index.js | 20 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 7504ff559e26b8..4b008cda6202af 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -104,7 +104,7 @@ function LinkControl( { onChange = noop, showInitialSuggestions, forceIsEditingLink, - createEntity, + createSuggestion, } ) { const wrapperNode = useRef(); const instanceId = useInstanceId( LinkControl ); @@ -278,13 +278,13 @@ function LinkControl( { [ handleDirectEntry, fetchSearchSuggestions ] ); - const handleOnCreate = async ( entityTitle ) => { - let newEntity; + const handleOnCreate = async ( suggestionTitle ) => { + let newSuggestion; setIsResolvingLink( true ); setErrorMessage( null ); try { - newEntity = await createEntity( entityTitle ); + newSuggestion = await createSuggestion( suggestionTitle ); } catch ( error ) { setErrorMessage( error.msg || @@ -296,9 +296,9 @@ function LinkControl( { setIsResolvingLink( false ); - if ( newEntity ) { + if ( newSuggestion ) { // only set if request is resolved - onChange( newEntity ); + onChange( newSuggestion ); setIsEditingLink( false ); } else { setIsEditingLink( true ); @@ -333,7 +333,7 @@ function LinkControl( { suggestions[ 0 ].type.toLowerCase() ); const shouldShowCreateEntity = - createEntity && + createSuggestion && ! isSingleDirectEntryResult && ! isInitialSuggestions; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 3c613eef06e032..f95e58c486e8bf 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -537,7 +537,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { let resolvedEntity; let currentLinkLabel; - const createEntity = ( title ) => + const createSuggestion = ( title ) => new Promise( ( resolve ) => { resolver = resolve; resolvedEntity = { @@ -557,7 +557,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { onChange={ ( suggestion ) => { setLink( suggestion ); } } - createEntity={ createEntity } + createSuggestion={ createSuggestion } /> ); }; @@ -617,7 +617,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect.stringContaining( 'Creating' ) ); - // Resolve the `createEntity` promise + // Resolve the `createSuggestion` promise await act( async () => { resolver( resolvedEntity ); } ); @@ -655,7 +655,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { onChange={ ( suggestion ) => { setLink( suggestion ); } } - createEntity={ ( title ) => + createSuggestion={ ( title ) => Promise.resolve( { title, id: 123, @@ -728,11 +728,11 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { describe( 'Do not show create option', () => { it.each( [ [ undefined ], [ null ], [ false ] ] )( - 'should not show not show an option to create an entity when "createEntity" handler is %s', + 'should not show not show an option to create an entity when "createSuggestion" handler is %s', async ( handler ) => { act( () => { render( - , + , container ); } ); @@ -765,7 +765,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { render( , container ); @@ -804,7 +804,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { async ( inputText ) => { act( () => { render( - , + , container ); } ); @@ -848,11 +848,11 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { throw new Error( 'API response returned invalid entity.' ); // this can be any error and msg }; - const createEntity = () => Promise.reject( throwsError() ); + const createSuggestion = () => Promise.reject( throwsError() ); act( () => { render( - , + , container ); } ); From 3ef5de43aa1442200d0d9b2be1e1dc8891eaadc4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 15:45:01 +0000 Subject: [PATCH 086/170] Attempt update of type definitions https://github.com/WordPress/gutenberg/pull/19775#discussion_r374795957 --- packages/block-editor/src/components/link-control/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4b008cda6202af..64331e0774a66b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -89,6 +89,7 @@ const CREATE_TYPE = '__CREATE__'; * @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if * the user selects a new link or updates settings. * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. + * @property {(title:string)=>WPLinkControlValue=} createSuggestion Handler to manage creation of link value from suggestion. */ /** From 01b972784013dfa99116e0076ee708842dd786dd Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 15:51:28 +0000 Subject: [PATCH 087/170] Avoid need to use entity term where making it more generic can avoid this. --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 64331e0774a66b..dcdab984325934 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -432,7 +432,7 @@ function LinkControl( { > { isResolvingLink && (
- { __( 'Creating Page' ) } + { __( 'Creating' ) }…
) } From 3049729b8f6fc20ebb903e637f8882fede7f909a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 15:55:05 +0000 Subject: [PATCH 088/170] Remove unused timeout HOC --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index dcdab984325934..bdec273817b17b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -28,7 +28,7 @@ import { prependHTTP, getProtocol, } from '@wordpress/url'; -import { useInstanceId, withSafeTimeout } from '@wordpress/compose'; +import { useInstanceId } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; import { focus } from '@wordpress/dom'; @@ -532,4 +532,4 @@ function LinkControl( { ); } -export default withSafeTimeout( LinkControl ); +export default LinkControl; From c63f8e11ba8f1ee7e068af07f7effda66fa03d30 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:01:48 +0000 Subject: [PATCH 089/170] Fix misnamed property --- packages/block-library/src/navigation-link/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 3406ca983b2a42..9a861d1ca58779 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -266,7 +266,7 @@ function NavigationLinkEdit( { className="wp-block-navigation-link__inline-link-input" value={ link } showInitialSuggestions={ true } - createEntity={ + createSuggestion={ userCanCreatePages ? handleCreateEntity : undefined From 7496092dded62a2cf160909068149c97f27d5c57 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:03:58 +0000 Subject: [PATCH 090/170] =?UTF-8?q?Update=20Nav=20Block=20create=20handler?= =?UTF-8?q?=20to=20use=20term=20=E2=80=9CPage=E2=80=9D=20to=20better=20ref?= =?UTF-8?q?lect=20purpose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block-library/src/navigation-link/edit.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 9a861d1ca58779..3229de0738bf5d 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -116,40 +116,40 @@ function NavigationLinkEdit( { selection.addRange( range ); } - function handleCreateEntity( entityTitle ) { + function handleCreatePage( pageTitle ) { const type = 'page'; return saveEntityRecord( 'postType', type, { - title: entityTitle, + title: pageTitle, status: 'publish', - } ).then( ( entity ) => { - // `entity` may not reject the Promise + } ).then( ( page ) => { + // `page` may not reject the Promise // but may still be invalid. Here we // tests for unexpected values and throw accordingly. - if ( null === entity || undefined === entity ) { + if ( null === page || undefined === page ) { throw new TypeError( - 'API response returned invalid entity.', - entity + 'API response returned invalid Page.', + page ); } const requiredEntityProps = [ 'id', 'title', 'link' ]; - const entityMissingProperty = requiredEntityProps.find( - ( entityProp ) => ! entity.hasOwnProperty( entityProp ) + const pageMissingProperty = requiredEntityProps.find( + ( pageProp ) => ! page.hasOwnProperty( pageProp ) ); - if ( entityMissingProperty ) { + if ( pageMissingProperty ) { throw new TypeError( - `API response returned invalid entity. Missing required property "${ entityMissingProperty }".`, - entity + `API response returned invalid page. Missing required property "${ pageMissingProperty }".`, + page ); } return { - id: entity.id, + id: page.id, type, - title: entity.title.rendered, - url: entity.link, + title: page.title.rendered, + url: page.link, }; } ); } @@ -268,7 +268,7 @@ function NavigationLinkEdit( { showInitialSuggestions={ true } createSuggestion={ userCanCreatePages - ? handleCreateEntity + ? handleCreatePage : undefined } onChange={ ( { From 52dd8fe3109f09c880193e81ad66a467bb5c5251 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:08:34 +0000 Subject: [PATCH 091/170] Refactor handleCreatePage to async/await to avoid nesting Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r374809751 --- .../block-library/src/navigation-link/edit.js | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 3229de0738bf5d..cc25285619aae1 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -116,42 +116,39 @@ function NavigationLinkEdit( { selection.addRange( range ); } - function handleCreatePage( pageTitle ) { + async function handleCreatePage( pageTitle ) { const type = 'page'; - return saveEntityRecord( 'postType', type, { + const page = await saveEntityRecord( 'postType', type, { title: pageTitle, status: 'publish', - } ).then( ( page ) => { - // `page` may not reject the Promise - // but may still be invalid. Here we - // tests for unexpected values and throw accordingly. - if ( null === page || undefined === page ) { - throw new TypeError( - 'API response returned invalid Page.', - page - ); - } + } ); - const requiredEntityProps = [ 'id', 'title', 'link' ]; + // `page` may not reject the Promise + // but may still be invalid. Here we + // tests for unexpected values and throw accordingly. + if ( null === page || undefined === page ) { + throw new TypeError( 'API response returned invalid Page.', page ); + } - const pageMissingProperty = requiredEntityProps.find( - ( pageProp ) => ! page.hasOwnProperty( pageProp ) - ); + const requiredEntityProps = [ 'id', 'title', 'link' ]; - if ( pageMissingProperty ) { - throw new TypeError( - `API response returned invalid page. Missing required property "${ pageMissingProperty }".`, - page - ); - } + const pageMissingProperty = requiredEntityProps.find( + ( pageProp ) => ! page.hasOwnProperty( pageProp ) + ); - return { - id: page.id, - type, - title: page.title.rendered, - url: page.link, - }; - } ); + if ( pageMissingProperty ) { + throw new TypeError( + `API response returned invalid page. Missing required property "${ pageMissingProperty }".`, + page + ); + } + + return { + id: page.id, + type, + title: page.title.rendered, + url: page.link, + }; } return ( From 07463be78316a2e34bc2c91ac5f172d6f870140f Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 5 Feb 2020 10:19:37 -0600 Subject: [PATCH 092/170] Set url items from LinkControl to have sanitized url as the id and updated e2e snapshot Based on conversations around potential issues with not including an id on navigation links, the ID has been added back in as the sanitized url as it should be fairly unique and not actually matter if it's unique. --- packages/block-editor/src/components/link-control/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bdec273817b17b..04d2c226a7321c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -22,6 +22,7 @@ import { useEffect, } from '@wordpress/element'; import { + cleanForSlug, safeDecodeURI, filterURLForDisplay, isURL, @@ -195,6 +196,7 @@ function LinkControl( { return Promise.resolve( [ { + id: cleanForSlug( val ), title: val, url: type === 'URL' ? prependHTTP( val ) : val, type, @@ -236,6 +238,7 @@ function LinkControl( { return maybeURL( val ) ? results : results.concat( { + id: cleanForSlug( val ), title: val, // // must match the existing ``s text value url: '', // placeholder - not used when creating type: CREATE_TYPE, From 79c60641af1b9a864c2eae103a58a46aecdf58ed Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:14:20 +0000 Subject: [PATCH 093/170] Reinstate keyboard handling of suggestion selection bug fix. Failing to pass the current input value as `url` of the suggestion causes the keyboard handling to break when hitting `ENTER`. Not entirely sure why at this stage. --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 04d2c226a7321c..d9472e40d45c54 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -239,8 +239,8 @@ function LinkControl( { ? results : results.concat( { id: cleanForSlug( val ), - title: val, // // must match the existing ``s text value - url: '', // placeholder - not used when creating + title: val, // must match the existing ``s text value + url: val, // must match the existing ``s text value type: CREATE_TYPE, } ); }; From a6bd8835bed10328451d95c5efbb60abf17c28e9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:19:32 +0000 Subject: [PATCH 094/170] Update comment to better reflect need for `title` and `url` props to reflect input text value --- packages/block-editor/src/components/link-control/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index d9472e40d45c54..e4eaface0de729 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -231,10 +231,11 @@ function LinkControl( { // API. In addition promoting CREATE to a first class suggestion affords // the a11y benefits afforded by `URLInput` to all suggestions (eg: // keyboard handling, ARIA roles...etc). - // Note also that due to the flow of data and the nature of the - // controlled components the value of the `title` property must correspond + // + // Note also that the value of the `title` and `url` properties must correspond // to the text value of the ``. This is because `title` is used - // when creating the entity. + // when creating the entity. Similarly `url` is used when using keyboard to select + // the suggestion (the `onSubmit` handler falls-back to `url`). return maybeURL( val ) ? results : results.concat( { From f2609b617a915051b9eca82c289d1b628c0fd6c4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:27:01 +0000 Subject: [PATCH 095/170] Remove unwanted reference to entity type. The more generic we keep the errors the simpler this component needs to be. Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r374803589 --- packages/block-editor/src/components/link-control/index.js | 2 +- packages/block-editor/src/components/link-control/test/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index e4eaface0de729..af18ba87423164 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -294,7 +294,7 @@ function LinkControl( { setErrorMessage( error.msg || __( - 'An unknown error occurred during Page creation. Please try again.' + 'An unknown error occurred during creation. Please try again.' ) ); } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index f95e58c486e8bf..41943a66d96fb3 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -897,7 +897,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { expect( errorNotice ).not.toBeFalsy(); expect( errorNotice.innerHTML ).toEqual( expect.stringContaining( - 'An unknown error occurred during Page creation. Please try again.' + 'An unknown error occurred during creation. Please try again.' ) ); From e176aa192df8901ec36127939ecb284b1a931590 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 5 Feb 2020 11:21:40 -0600 Subject: [PATCH 096/170] Corrects usage of aria-label and aria-labelled by on the link control search results Use aria-labelledby to reference the visible label ID Use aria-label when no visible label --- .../block-editor/src/components/link-control/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index af18ba87423164..1547bc04223ed4 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -364,16 +364,12 @@ function LinkControl( { return (
- { isInitialSuggestions ? ( - SearchResultsLabel - ) : ( - { SearchResultsLabel } - ) } - + { isInitialSuggestions && SearchResultsLabel }
{ suggestions.map( ( suggestion, index ) => { if ( From 7149b445671071459614a9efa3dbcb3086460405 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 5 Feb 2020 16:31:00 +0000 Subject: [PATCH 097/170] Attempt to fix Typescript docblock alignment --- .../block-editor/src/components/link-control/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 1547bc04223ed4..4232bf37383e0b 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -86,10 +86,10 @@ const CREATE_TYPE = '__CREATE__'; * @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the * internal editing state of the component to respective * show or not show the URL input field. - * @property {WPLinkControlValue=} value Current link value. - * @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if - * the user selects a new link or updates settings. - * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. + * @property {WPLinkControlValue=} value Current link value. + * @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if + * the user selects a new link or updates settings. + * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. * @property {(title:string)=>WPLinkControlValue=} createSuggestion Handler to manage creation of link value from suggestion. */ From 6dae057e5e06e82b0fa6d9297c68f8d5d3b63b61 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 5 Feb 2020 15:07:50 -0600 Subject: [PATCH 098/170] Updated hardcoded color values to scss color variables --- .../src/components/link-control/style.scss | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index b248367b8bd288..1a0f792a4eb569 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -21,7 +21,7 @@ $block-editor-link-control-number-of-actions: 1; margin: $grid-size-large; padding-right: ( $icon-button-size * $block-editor-link-control-number-of-actions ); // width of reset and submit buttons position: relative; - border: 1px solid #e1e1e1; + border: 1px solid $light-gray-500; border-radius: $radius-round-rectangle; /* Fonts smaller than 16px causes mobile safari to zoom. */ @@ -122,14 +122,14 @@ $block-editor-link-control-number-of-actions: 1; &:hover, &:focus { - background-color: #e9e9e9; + background-color: $light-gray-300; } &.is-selected { - background: #f2f2f2; + background: $light-gray-200; .block-editor-link-control__search-item-type { - background: #fff; + background: $white; } } @@ -167,7 +167,7 @@ $block-editor-link-control-number-of-actions: 1; mark { font-weight: 700; - color: #000; + color: $black; background-color: transparent; } @@ -178,7 +178,7 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__search-item-info { display: block; - color: #999; + color: $dark-gray-300; font-size: 0.9em; line-height: 1.3; } @@ -188,7 +188,7 @@ $block-editor-link-control-number-of-actions: 1; padding: 3px 8px; margin-left: auto; font-size: 0.9em; - background-color: #f3f4f5; + background-color: $light-gray-200; border-radius: 2px; } } @@ -227,7 +227,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__settings { - border-top: 1px solid #e1e1e1; + border-top: 1px solid $light-gray-500; margin: 0; padding: $grid-size-large $grid-size-xlarge; From ff4701d90457a2f32ed8caa8721a7479b4e984fc Mon Sep 17 00:00:00 2001 From: Marek Hrabe Date: Wed, 5 Feb 2020 17:31:41 -0800 Subject: [PATCH 099/170] add e2e test for creating page from nav --- .../__snapshots__/navigation.test.js.snap | 6 ++ .../specs/experiments/navigation.test.js | 75 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index b13411df2531c4..c34c7761b20c28 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -17,3 +17,9 @@ exports[`Navigation allows a navigation menu to be created using existing pages " `; + +exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` +" + +" +`; diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index fd3ab2202f636a..0572d49e001256 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -54,6 +54,26 @@ async function mockSearchResponse( items ) { ] ); } +async function mockCreatePageResponse( title, slug ) { + const page = { + id: 1, + title: { raw: title, rendered: title }, + type: 'page', + link: `https://this/is/a/test/url/${ slug }`, + slug, + }; + + await setUpResponseMocking( [ + { + match: ( request ) => + request.url().includes( `rest_route` ) && + request.url().includes( `pages` ) && + request.method() === 'POST', + onRequestMatch: createJSONResponse( page ), + }, + ] ); +} + async function updateActiveNavigationLink( { url, label } ) { if ( url ) { await page.type( 'input[placeholder="Search or type url"]', url ); @@ -188,4 +208,59 @@ describe( 'Navigation', () => { // Expect a Navigation Block with two Navigation Links in the snapshot. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'allows pages to be created from the navigation block and their links added to menu', async () => { + // Add the navigation block. + await insertBlock( 'Navigation' ); + + // Create an empty nav block. + await page.waitForSelector( '.wp-block-navigation-placeholder' ); + const [ createEmptyButton ] = await page.$x( + '//button[text()="Create empty"]' + ); + await createEmptyButton.click(); + + // After adding a new block, search input should be shown immediately. + const isInURLInput = await page.evaluate( + () => !! document.activeElement.closest( '.block-editor-url-input' ) + ); + expect( isInURLInput ).toBe( true ); + + // Insert name for the new page. + await page.type( + 'input[placeholder="Search or type url"]', + 'My New Page' + ); + + // Mock request for creating pages. + await mockCreatePageResponse( 'My New Page', 'my-new-page' ); + + // Wait for the create button to appear and click it. + await page.waitForSelector( + '.block-editor-link-control__search-create' + ); + const [ createPageButton ] = await page.$x( + '//button[contains(concat(" ", @class, " "), " block-editor-link-control__search-create ")]' + ); + await createPageButton.click(); + + // Wait until the link editor disappears. + await page.waitForSelector( + '.block-editor-link-control__search-input-wrapper', + { hidden: true } + ); + + // Confirm the new link is focused. + const isInLinkRichText = await page.evaluate( + () => + document.activeElement.classList.contains( 'rich-text' ) && + !! document.activeElement.closest( + '.block-editor-block-list__block' + ) + ); + expect( isInLinkRichText ).toBe( true ); + + // Expect a Navigation Block with two Navigation Links in the snapshot. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 435778c957766e00cacaf6c385567b99876288bd Mon Sep 17 00:00:00 2001 From: Marek Hrabe Date: Wed, 5 Feb 2020 17:40:10 -0800 Subject: [PATCH 100/170] accomodate for self-closing link popover after selecting link (prevents react error) --- packages/block-editor/src/components/link-control/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4232bf37383e0b..5a17788196d25f 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -301,10 +301,9 @@ function LinkControl( { setIsResolvingLink( false ); + // Only set link if request is resolved, otherwise enable edit mode. if ( newSuggestion ) { - // only set if request is resolved onChange( newSuggestion ); - setIsEditingLink( false ); } else { setIsEditingLink( true ); } From 53ec1d59df9dc058b7fd28fa96cb9cef05e5bbb8 Mon Sep 17 00:00:00 2001 From: Marek Hrabe Date: Wed, 5 Feb 2020 17:44:46 -0800 Subject: [PATCH 101/170] update comment to match expected test result --- packages/e2e-tests/specs/experiments/navigation.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 0572d49e001256..5079ae17984c36 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -260,7 +260,7 @@ describe( 'Navigation', () => { ); expect( isInLinkRichText ).toBe( true ); - // Expect a Navigation Block with two Navigation Links in the snapshot. + // Expect a Navigation Block with a link for "My New Page". expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); From ac3eb3da4732fdc1fb239f2e09c6cb04e5ca50d3 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 11:26:36 +0000 Subject: [PATCH 102/170] Fix to cancel pending promises on unmount to avoid state updates Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r374807839 --- .../src/components/link-control/index.js | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 5a17788196d25f..92f9571aef527a 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -42,6 +42,35 @@ import LinkControlSearchItem from './search-item'; import LinkControlSearchInput from './search-input'; import LinkControlSearchCreate from './search-create-button'; const CREATE_TYPE = '__CREATE__'; +let cancelableOnCreate; +let cancelableCreateSuggestion; + +/** + * Creates a wrapper around a promise which allows it to be programmatically + * cancelled. + * See: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html + * + * @param {Promise} promise the Promise to make cancelable + */ +const makeCancelable = ( promise ) => { + let hasCanceled_ = false; + + const wrappedPromise = new Promise( ( resolve, reject ) => { + promise.then( + ( val ) => + hasCanceled_ ? reject( { isCanceled: true } ) : resolve( val ), + ( error ) => + hasCanceled_ ? reject( { isCanceled: true } ) : reject( error ) + ); + } ); + + return { + promise: wrappedPromise, + cancel() { + hasCanceled_ = true; + }, + }; +}; /** * Default properties associated with a link control value. * @@ -167,6 +196,21 @@ function LinkControl( { isEndingEditWithFocus.current = false; }, [ isEditingLink ] ); + /** + * Handles cancelling any pending Promises that have been made cancelable. + */ + useEffect( () => { + return () => { + // componentDidUnmount + if ( cancelableOnCreate ) { + cancelableOnCreate.cancel(); + } + if ( cancelableCreateSuggestion ) { + cancelableCreateSuggestion.cancel(); + } + }; + }, [] ); + /** * onChange LinkControlSearchInput event handler * @@ -288,9 +332,20 @@ function LinkControl( { setIsResolvingLink( true ); setErrorMessage( null ); + + // Make cancellable in order that we can avoid setting State + // if the component unmounts during the call to `createSuggestion` + cancelableCreateSuggestion = makeCancelable( + createSuggestion( suggestionTitle ) + ); + try { - newSuggestion = await createSuggestion( suggestionTitle ); + newSuggestion = await cancelableCreateSuggestion.promise; } catch ( error ) { + if ( error.isCanceled ) { + return; // bail out of state updates if the promise was cancelled + } + setErrorMessage( error.msg || __( @@ -439,20 +494,28 @@ function LinkControl( { { + onSelect={ ( suggestion ) => { if ( suggestion.type && CREATE_TYPE === suggestion.type ) { - // Await the promise to ensure `stopEditing` is not called prematurely. - await handleOnCreate( inputValue ); + cancelableOnCreate = makeCancelable( + handleOnCreate( inputValue ) + ) + .promise.then( () => stopEditing() ) + .catch( ( error ) => { + if ( error.isCanceled ) { + return; // bail if canceled to avoid setting state + } + + stopEditing(); + } ); } else { handleSelectSuggestion( suggestion, value ); + // Must be called after handling to ensure focus is + // managed correctly. + stopEditing(); } - - // Must be called after handling to ensure focus is - // managed correctly. - stopEditing(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } From 9f004ca18c9030ac61788d7a4612dc42acd614df Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 11:31:43 +0000 Subject: [PATCH 103/170] Make arbitary CSS value correspond to standardised var Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375769831 --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 1a0f792a4eb569..25270a94ae8146 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -213,7 +213,7 @@ $block-editor-link-control-number-of-actions: 1; &::before { content: ""; position: absolute; - top: -10px; + top: -#{$block-selected-child-margin*2}; left: 0; display: block; width: 100%; From 0aaa082b8d7cd803908775c8c789e7eaae25bd69 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 11:56:31 +0000 Subject: [PATCH 104/170] Removes manual aria live in favour of speak and @wordpress/a11y Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r374809191 --- .../components/link-control/search-input.js | 28 +++++++++++++++++-- .../src/components/link-control/test/index.js | 28 +++++++++++++------ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 4aec3dce450e41..a7693a21a6647b 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -1,9 +1,14 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useState, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Button, Notice } from '@wordpress/components'; +import { Button, Notice, withSpokenMessages } from '@wordpress/components'; /** * Internal dependencies @@ -18,9 +23,16 @@ const LinkControlSearchInput = ( { fetchSuggestions, showInitialSuggestions, errorMessage, + speak = noop, } ) => { const [ selectedSuggestion, setSelectedSuggestion ] = useState(); + useEffect( () => { + if ( errorMessage ) { + speak( errorMessage, 'assertive' ); + } + }, [ errorMessage ] ); + /** * Handles the user moving between different suggestions. Does not handle * choosing an individual item. @@ -78,8 +90,18 @@ const LinkControlSearchInput = ( { ) }
+ + { errorMessage && ( + + { errorMessage } + + ) } ); }; -export default LinkControlSearchInput; +export default withSpokenMessages( LinkControlSearchInput ); diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 41943a66d96fb3..208df879ec5cba 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -840,7 +840,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { } ); describe( 'Error handling', () => { - it( 'should display human readable error notice and re-show create button and search input if page creation request fails', async () => { + it( 'should display human-friendly, perceivable error notice and re-show create button and search input if page creation request fails', async () => { const searchText = 'This page to be created'; let searchInput; @@ -849,10 +849,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { }; const createSuggestion = () => Promise.reject( throwsError() ); + const speakSpy = jest.fn(); act( () => { render( - , + , container ); } ); @@ -879,6 +883,9 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ) ); + // Catch the error in the test to avoid test failures + expect( throwsError ).toThrow( Error ); + await act( async () => { Simulate.click( createButton ); } ); @@ -887,19 +894,24 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { searchInput = container.querySelector( 'input[aria-label="URL"]' ); - // This is a Notice component wrapped in an aria-live div with role of "alert". - const errorNotice = container.querySelector( '[role="alert"]' ); - - // Catch the error in the test to avoid test failures - expect( throwsError ).toThrow( Error ); + // This is a Notice component + // we allow selecting by className here as an edge case because the + // a11y is handled via `speak`. + // See: https://github.com/WordPress/gutenberg/tree/master/packages/a11y#speak. + const errorNotice = container.querySelector( + '.block-editor-link-control__search-error' + ); - // Check human readable error notice is displayed + // Check human readable error notice is perceivable expect( errorNotice ).not.toBeFalsy(); expect( errorNotice.innerHTML ).toEqual( expect.stringContaining( 'An unknown error occurred during creation. Please try again.' ) ); + expect( speakSpy ).toHaveBeenCalledWith( + 'An unknown error occurred during creation. Please try again.' + ); // Verify input is repopulated with original search text expect( searchInput ).not.toBeFalsy(); From 5615e7f733a6396bd83706099ce61cea850cdc83 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 14:15:31 +0000 Subject: [PATCH 105/170] Remove unwanted whitespace. Co-Authored-By: Andrew Duthie --- packages/block-editor/src/components/link-control/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 92f9571aef527a..7b3be1c22f2668 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -31,7 +31,6 @@ import { } from '@wordpress/url'; import { useInstanceId } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; - import { focus } from '@wordpress/dom'; /** From 9a04df7beee5330d20c21c2fbbe7927ebd43c231 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 14:20:55 +0000 Subject: [PATCH 106/170] Rename var to lowercase Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375857444 --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 7b3be1c22f2668..701951dee07397 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -405,7 +405,7 @@ function LinkControl( { ? __( 'Recently updated' ) : sprintf( __( 'Search results for %s' ), inputValue ); const ariaLabel = isInitialSuggestions ? undefined : labelText; - const SearchResultsLabel = ( + const searchResultsLabel = ( - { isInitialSuggestions && SearchResultsLabel } + { isInitialSuggestions && searchResultsLabel }
Date: Thu, 6 Feb 2020 14:33:08 +0000 Subject: [PATCH 107/170] Avoid unwanted arial-label* roles for visually hidden elements `aria-labelledby` is only suitable when the referenced element is visible. `aria-label` should not be used to duplicate content already accessible within the element. --- packages/block-editor/src/components/link-control/index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 701951dee07397..24486bc1630570 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -526,15 +526,12 @@ function LinkControl( { { value && ! isEditingLink && ! isResolvingLink && ( -

+

{ __( 'Currently selected' ) }:

Date: Thu, 6 Feb 2020 14:39:41 +0000 Subject: [PATCH 108/170] Move cancellable refs to component scope. Partially addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375834839 --- packages/block-editor/src/components/link-control/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 24486bc1630570..54675af30d7674 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -41,8 +41,6 @@ import LinkControlSearchItem from './search-item'; import LinkControlSearchInput from './search-input'; import LinkControlSearchCreate from './search-create-button'; const CREATE_TYPE = '__CREATE__'; -let cancelableOnCreate; -let cancelableCreateSuggestion; /** * Creates a wrapper around a promise which allows it to be programmatically @@ -136,6 +134,9 @@ function LinkControl( { forceIsEditingLink, createSuggestion, } ) { + let cancelableOnCreate; + let cancelableCreateSuggestion; + const wrapperNode = useRef(); const instanceId = useInstanceId( LinkControl ); const [ inputValue, setInputValue ] = useState( From 56fabd5c8ce450979cb7e785d92364ebb07a73af Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 11:46:41 -0600 Subject: [PATCH 109/170] Lightened border color between link control suggestions and create new page button --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 25270a94ae8146..a385822df73ee7 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -217,7 +217,7 @@ $block-editor-link-control-number-of-actions: 1; left: 0; display: block; width: 100%; - border-top: 1px solid $dark-gray-150; + border-top: 1px solid $light-gray-500; } } From 483ecc1b02ea94feb6f21e3d5e90570f1cba8517 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 14:13:06 -0600 Subject: [PATCH 110/170] Changed Create new Page text to New Page to match design --- .../src/components/link-control/search-create-button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 8ca44502e65ce3..6a9909496deb54 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -33,7 +33,7 @@ export const LinkControlSearchCreate = ( { - { sprintf( __( 'Create new Page: %s' ), searchTerm ) } + { sprintf( __( 'New page: %s' ), searchTerm ) } From 7e25eea02dcb11be1c743bc061c1d93a666feca6 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 14:34:56 -0600 Subject: [PATCH 111/170] Added to New page title when creating a new page in the link control --- .../src/components/link-control/search-create-button.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 6a9909496deb54..039f59f352c859 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -8,6 +8,7 @@ import classnames from 'classnames'; */ import { __, sprintf } from '@wordpress/i18n'; import { Button, Icon } from '@wordpress/components'; +import { __experimentalCreateInterpolateElement } from '@wordpress/element'; export const LinkControlSearchCreate = ( { searchTerm = '', @@ -33,7 +34,13 @@ export const LinkControlSearchCreate = ( { - { sprintf( __( 'New page: %s' ), searchTerm ) } + { __experimentalCreateInterpolateElement( + sprintf( + __( 'New page: %s' ), + searchTerm + ), + { mark: } + ) } From bf754e4ede751f2652f363c0f8aca0da4d5c7330 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 14:36:05 -0600 Subject: [PATCH 112/170] Make page title of new page creation darker --- packages/block-editor/src/components/link-control/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index a385822df73ee7..0569bd6d3b46a5 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -160,6 +160,10 @@ $block-editor-link-control-number-of-actions: 1; white-space: nowrap; } + .block-editor-link-control__search-item-title mark { + color: $dark-gray-900; + } + .block-editor-link-control__search-item-title { display: block; margin-bottom: 0.2em; From a1b09d3274fdfa675164e4c23b48053625a109d9 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 14:46:55 -0600 Subject: [PATCH 113/170] Adjusted positioning on the create new page button padding and centering --- packages/block-editor/src/components/link-control/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 0569bd6d3b46a5..bc3555b395b1ee 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -145,6 +145,7 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__search-item-header { display: block; margin-right: $grid-size-xlarge; + margin-top: 2px; } .block-editor-link-control__search-item-icon { @@ -211,6 +212,7 @@ $block-editor-link-control-number-of-actions: 1; .components-button + .block-editor-link-control__search-create { margin-top: 20px; overflow: visible; + padding: 12px 15px; // Create fake border. We cannot use border because the button has a border // radius applied to it From 9ea5142d8b699eed98d4df270a7b3233109e8533 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 6 Feb 2020 15:30:34 -0600 Subject: [PATCH 114/170] Added clarifying comment on negative margin CSS --- packages/block-editor/src/components/link-control/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index bc3555b395b1ee..9cc009e6c0ff6f 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -38,7 +38,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__search-error { - margin: -$grid-size-large/2 $grid-size-large $grid-size-large; + margin: -$grid-size-large/2 $grid-size-large $grid-size-large; // negative margin to bring the error a bit closer to the button } .block-editor-link-control__search-actions { From a23925780e77885b5af6805214f8764af08a8ddd Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 6 Feb 2020 16:51:07 +0000 Subject: [PATCH 115/170] Correct tests to select by aria-label now aria-labelledby is removed. --- .../src/components/link-control/test/index.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 208df879ec5cba..7b32ec6d75ecfe 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -535,7 +535,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { async ( entityNameText ) => { let resolver; let resolvedEntity; - let currentLinkLabel; const createSuggestion = ( title ) => new Promise( ( resolve ) => { @@ -608,7 +607,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const loadingIndicator = container.querySelector( '.block-editor-link-control__loading' ); - currentLinkLabel = container.querySelector( + const currentLinkLabel = container.querySelector( '[aria-label="Currently selected"]' ); @@ -624,12 +623,8 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - // Test the new entity was displayed. - currentLinkLabel = container.querySelector( - '[aria-label="Currently selected"]' - ); const currentLink = container.querySelector( - `[aria-labelledby="${ currentLinkLabel.id }"]` + '[aria-label="Currently selected"]' ); const currentLinkHTML = currentLink.innerHTML; @@ -711,12 +706,8 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - const currentLinkLabel = container.querySelector( - '[aria-label="Currently selected"]' - ); - const currentLink = container.querySelector( - `[aria-labelledby="${ currentLinkLabel.id }"]` + '[aria-label="Currently selected"]' ); const currentLinkHTML = currentLink.innerHTML; From a93821d867ed3d04a9fde3a5b19c9b893f81a815 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 7 Feb 2020 16:54:42 +0000 Subject: [PATCH 116/170] Correct test to look for new Create button wording Changed due to Design request to change text but test was not updated. --- .../src/components/link-control/test/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 7b32ec6d75ecfe..0b27a660a6c462 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -586,7 +586,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -687,7 +687,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const form = container.querySelector( 'form' ); const createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -741,7 +741,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ); const createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -775,7 +775,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ); const createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -821,7 +821,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -870,7 +870,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ); let createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); @@ -914,7 +914,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ); createButton = first( Array.from( searchResultElements ).filter( ( result ) => - result.innerHTML.includes( 'Create new' ) + result.innerHTML.includes( 'New page' ) ) ); From 86dfc0ddeda9df8be66be1dc334c79f0bb0bb7ad Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Sat, 8 Feb 2020 13:17:42 +0000 Subject: [PATCH 117/170] Fix to use cancelable async handler and call stopEditing Previously only the keyboard version of handleCreate was cancellable and treated as async. Now the onClick mouse version is also cancellable. Moreover we call stopEditing() correctly on resolve. --- .../src/components/link-control/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 54675af30d7674..32bc550e198306 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -434,7 +434,17 @@ function LinkControl( { { - handleOnCreate( suggestion.title ); + cancelableOnCreate = makeCancelable( + handleOnCreate( suggestion.title ) + ) + .promise.then( () => stopEditing() ) + .catch( ( error ) => { + if ( error.isCanceled ) { + return; // bail if canceled to avoid setting state + } + + stopEditing(); + } ); } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( From b115c9f0c82373b6d87150a4c74eac8f7c06a8b1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Sat, 8 Feb 2020 13:23:48 +0000 Subject: [PATCH 118/170] Refactor select handlers to use async/await --- .../src/components/link-control/index.js | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 32bc550e198306..db495117ccccd2 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -433,18 +433,23 @@ function LinkControl( { return ( { - cancelableOnCreate = makeCancelable( - handleOnCreate( suggestion.title ) - ) - .promise.then( () => stopEditing() ) - .catch( ( error ) => { - if ( error.isCanceled ) { - return; // bail if canceled to avoid setting state - } - - stopEditing(); - } ); + onClick={ async () => { + try { + cancelableOnCreate = makeCancelable( + handleOnCreate( + suggestion.title + ) + ); + + await cancelableOnCreate.promise; + } catch ( error ) { + if ( error && error.isCanceled ) { + return; // bail if canceled to avoid setting state + } + } + + // Only setState if not cancelled + stopEditing(); } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( @@ -504,28 +509,29 @@ function LinkControl( { { + onSelect={ async ( suggestion ) => { if ( suggestion.type && CREATE_TYPE === suggestion.type ) { - cancelableOnCreate = makeCancelable( - handleOnCreate( inputValue ) - ) - .promise.then( () => stopEditing() ) - .catch( ( error ) => { - if ( error.isCanceled ) { - return; // bail if canceled to avoid setting state - } - - stopEditing(); - } ); + try { + cancelableOnCreate = makeCancelable( + handleOnCreate( inputValue ) + ); + + await cancelableOnCreate.promise; + } catch ( error ) { + if ( error && error.isCanceled ) { + return; // bail if canceled to avoid setting state + } + } } else { handleSelectSuggestion( suggestion, value ); - // Must be called after handling to ensure focus is - // managed correctly. - stopEditing(); } + + // Must be called after handling to ensure focus is + // managed correctly. + stopEditing(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } From 0842cc4d81d2406863e2bef75a8c9c6a9196d3f5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 10:29:52 +0000 Subject: [PATCH 119/170] Move function invocation within try/catch boundary --- .../src/components/link-control/index.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index db495117ccccd2..45ebd1b5ad9897 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -333,19 +333,17 @@ function LinkControl( { setIsResolvingLink( true ); setErrorMessage( null ); - // Make cancellable in order that we can avoid setting State - // if the component unmounts during the call to `createSuggestion` - cancelableCreateSuggestion = makeCancelable( - createSuggestion( suggestionTitle ) - ); - try { + // Make cancellable in order that we can avoid setting State + // if the component unmounts during the call to `createSuggestion` + cancelableCreateSuggestion = makeCancelable( + createSuggestion( suggestionTitle ) + ); newSuggestion = await cancelableCreateSuggestion.promise; } catch ( error ) { if ( error.isCanceled ) { return; // bail out of state updates if the promise was cancelled } - setErrorMessage( error.msg || __( From 219af6bb285c86d9efbe92fad54efe348821e009 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 10:30:51 +0000 Subject: [PATCH 120/170] =?UTF-8?q?Remove=20test=20spying=20on=20speak=20H?= =?UTF-8?q?OC=20as=20this=20isn=E2=80=99t=20possible=20via=20`LinkControl`?= =?UTF-8?q?=20directly.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/link-control/test/index.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 0b27a660a6c462..68f5f1a8a033bc 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -840,14 +840,10 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { }; const createSuggestion = () => Promise.reject( throwsError() ); - const speakSpy = jest.fn(); act( () => { render( - , + , container ); } ); @@ -874,9 +870,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { ) ); - // Catch the error in the test to avoid test failures - expect( throwsError ).toThrow( Error ); - await act( async () => { Simulate.click( createButton ); } ); @@ -893,6 +886,9 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { '.block-editor-link-control__search-error' ); + // Catch the error in the test to avoid test failures + expect( throwsError ).toThrow( Error ); + // Check human readable error notice is perceivable expect( errorNotice ).not.toBeFalsy(); expect( errorNotice.innerHTML ).toEqual( @@ -900,9 +896,6 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { 'An unknown error occurred during creation. Please try again.' ) ); - expect( speakSpy ).toHaveBeenCalledWith( - 'An unknown error occurred during creation. Please try again.' - ); // Verify input is repopulated with original search text expect( searchInput ).not.toBeFalsy(); From e3db29cb4682cd69fb833050d5bb0b22ff015abb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 10:57:11 +0000 Subject: [PATCH 121/170] Remove superflous visually hidden text in favour of aria-label Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r375984169 --- .../src/components/link-control/index.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 45ebd1b5ad9897..bae4958fb07f25 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -7,12 +7,7 @@ import { noop, startsWith } from 'lodash'; /** * WordPress dependencies */ -import { - Button, - ExternalLink, - Spinner, - VisuallyHidden, -} from '@wordpress/components'; +import { Button, ExternalLink, Spinner } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useRef, @@ -540,11 +535,6 @@ function LinkControl( { { value && ! isEditingLink && ! isResolvingLink && ( - -

- { __( 'Currently selected' ) }: -

-
Date: Mon, 10 Feb 2020 11:26:07 +0000 Subject: [PATCH 122/170] Fix space vs tabs linting issue --- packages/block-library/src/navigation-link/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index cc25285619aae1..1ef90ebc068973 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -373,7 +373,7 @@ export default compose( [ showSubmenuIcon, textColor: navigationBlockAttributes.textColor, backgroundColor: navigationBlockAttributes.backgroundColor, - navigationBlockAttributes, + navigationBlockAttributes, userCanCreatePages, rgbTextColor: getColorObjectByColorSlug( colors, From 2978a24e808e5db7cdfcac7cbf83623990e4c16e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 12:25:28 +0000 Subject: [PATCH 123/170] Correct spelling typos --- .../src/components/link-control/index.js | 2 +- .../src/components/link-control/test/index.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bae4958fb07f25..ab89eeeecdc531 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -262,7 +262,7 @@ function LinkControl( { : results[ 0 ]; // Here we append a faux suggestion to represent a "CREATE" option. This - // is detected in the rendering of the search results and handeled as a + // is detected in the rendering of the search results and handled as a // special case. This is currently necessary because the suggestions // dropdown will only appear if there are valid suggestions and // therefore unless the create option is a suggestion it will not diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 68f5f1a8a033bc..85108b0431ce6a 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -214,6 +214,7 @@ describe( 'Searching for a link', () => { // fetchFauxEntitySuggestions resolves on next "tick" of event loop await eventLoopTick(); + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = getSearchResults(); @@ -261,6 +262,7 @@ describe( 'Searching for a link', () => { // fetchFauxEntitySuggestions resolves on next "tick" of event loop await eventLoopTick(); + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = getSearchResults(); @@ -579,7 +581,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); @@ -680,7 +682,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); @@ -735,7 +737,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { 'input[aria-label="URL"]' ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); @@ -769,7 +771,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { 'input[aria-label="URL"]' ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); @@ -814,7 +816,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); @@ -860,7 +862,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. let searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); From 05c49829c48be74260915fdc1fa550575c4687a1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 15:10:03 +0000 Subject: [PATCH 124/170] Correct prop rename error from rebase --- packages/block-editor/src/components/link-control/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index ab89eeeecdc531..4b6ddeb3c1e8b3 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -580,7 +580,7 @@ function LinkControl( { renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } showInitialSuggestions={ showInitialSuggestions } - errorMsg={ errorMsg } + errorMessage={ errorMessage } /> ) } From c2ff26888e90221547bf4ef5f3c6e2c53ce33b8b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 15:59:35 +0000 Subject: [PATCH 125/170] Fix missing references --- packages/block-editor/src/components/link-control/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 4b6ddeb3c1e8b3..42736972618768 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -467,8 +467,8 @@ function LinkControl( { suggestion, index ) } - suggestion - index + suggestion={ suggestion } + index={ index } onClick={ () => { stopEditing(); onChange( { ...value, ...suggestion } ); From 6de5fa150494979822ca99aba5c8d002d7160154 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 10 Feb 2020 18:06:43 +0000 Subject: [PATCH 126/170] Remove duplicate render of LinkControlSearchInput introduced during rebase --- .../src/components/link-control/index.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 42736972618768..d1022579bca46c 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -570,20 +570,6 @@ function LinkControl( { ) } - { isEditingLink && ! isResolvingLink && ( - { - handleSelectSuggestion( suggestion, value )(); - } } - renderSuggestions={ renderSearchResults } - fetchSuggestions={ getSearchHandler } - showInitialSuggestions={ showInitialSuggestions } - errorMessage={ errorMessage } - /> - ) } - { ! isEditingLink && ! isResolvingLink && ( Date: Tue, 11 Feb 2020 09:46:07 +0000 Subject: [PATCH 127/170] Removes unnecessary closing of Link UI Calling setIsLinkOpen in the useEffect was causing setIsLinkOpen to be called twice. The act of moving focus back to the label is enough to trigger setIsLinkOpen to be called by the `Popover` onClose handler. Trying to call it again in the effect caused an error to be thrown regarding setting state on the unmounted component. --- packages/block-library/src/navigation-link/edit.js | 3 --- .../specs/experiments/__snapshots__/navigation.test.js.snap | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 1ef90ebc068973..724e743af805e5 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -86,9 +86,6 @@ function NavigationLinkEdit( { // If the LinkControl popover is open and the URL has changed, close the LinkControl and focus the label text. useEffect( () => { if ( isLinkOpen && url ) { - // Close the link. - setIsLinkOpen( false ); - // Does this look like a URL and have something TLD-ish? if ( isURL( prependHTTP( label ) ) && diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index c34c7761b20c28..eff8034bdb93bf 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -2,7 +2,7 @@ exports[`Navigation allows a navigation menu to be created from an empty menu using a mixture of internal and external links 1`] = ` " - + " @@ -20,6 +20,6 @@ exports[`Navigation allows a navigation menu to be created using existing pages exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` " - + " `; From 50f436db80b9da7a2a0536163cced7c4d6de3deb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 11 Feb 2020 10:13:22 +0000 Subject: [PATCH 128/170] Adds documentation for createSuggestion prop --- .../src/components/link-control/README.md | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 4b590885ae8be5..72865e1e188dba 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -25,7 +25,7 @@ Default properties include: - Type: `Array` - Required: No -- Default: +- Default: ``` [ { @@ -54,7 +54,7 @@ Value change handler, called with the updated value if the user selects a new li onChange={ ( nextValue ) => { console.log( `The selected item URL: ${ nextValue.url }.` ); } -/> +/> ``` ### showInitialSuggestions @@ -71,3 +71,47 @@ Whether to present initial suggestions immediately. - Required: No If passed as either `true` or `false`, controls the internal editing state of the component to respective show or not show the URL input field. + + +### createSuggestion + +- Type: `function` +- Required: No + +Used to handle the dynamic creation of new suggestions within the Link UI. When +the prop is provided, an option is added to the end of all search +results requests which when clicked will call `createSuggestion` callback +(passing the current value of the search ``) in +order to afford the parent component the opportunity to dynamically create a new +link `value` (see above). + +This is often used to allow on-the-fly creation of new entities (eg: `Posts`, +`Pages`) based on the text the user has entered into the link search UI. For +example, the Navigation Block uses this to create Pages on demand. + +When called, `createSuggestion` should return a `Promise` which resolves to a +new link `value` (see above) with the shape: + +```js +{ + id: // unique identifier + type: // "url", "page", "post"...etc + title: // "My new suggestion" + url: // any string representing the URL value +} +``` + +#### Example +```jsx + { + // Hard coded values. These could be dynamically created by calling out to an API which creates an entity (eg: https://developer.wordpress.org/rest-api/reference/pages/#create-a-page). + return { + id: 1234, + type: 'page', + title: inputText, + url: '/some-url-here' + } + }} +/> +``` \ No newline at end of file From e30d545d149efd90ab79eac6c4034222cb6f0a47 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 11 Feb 2020 13:58:16 +0000 Subject: [PATCH 129/170] Restore Link Settings to be always visable as per https://github.com/WordPress/gutenberg/commit/6c591f4518218e992ed85ef3d0e15a5e92f57af8 --- .../src/components/link-control/index.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index d1022579bca46c..088dc10d1ef35d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -569,14 +569,11 @@ function LinkControl( {
) } - - { ! isEditingLink && ! isResolvingLink && ( - - ) } +
); } From 10659834215272c2a032380cf51d69ba015f34f4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 11 Feb 2020 14:21:38 +0000 Subject: [PATCH 130/170] Update e2e snapshot --- .../specs/experiments/__snapshots__/navigation.test.js.snap | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index eff8034bdb93bf..aa3d39736e316e 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -17,9 +17,3 @@ exports[`Navigation allows a navigation menu to be created using existing pages " `; - -exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` -" - -" -`; From 8df3050a88f6e6262f82592d3ff1c73ae1c41127 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 11 Feb 2020 16:03:54 +0000 Subject: [PATCH 131/170] Revert "Update e2e snapshot" This reverts commit 98f7e9e8c1ba4ff499233e92129924fb90d3423b. --- .../specs/experiments/__snapshots__/navigation.test.js.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index aa3d39736e316e..eff8034bdb93bf 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -17,3 +17,9 @@ exports[`Navigation allows a navigation menu to be created using existing pages " `; + +exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` +" + +" +`; From d42cc272ab6ca1c19359405fb7fdad92befb7749 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 11 Feb 2020 12:53:30 -0600 Subject: [PATCH 132/170] Wait for rich-text to be focused instead of waiting for link control to disappear in e2e test We can't rely on the .block-editor-link-control__search-results-wrapper to be hidden, as there's a quick loading state between when that disappears and the link is focused. Waiting for the link to be focused allows this test to be passed while also checking that focus is placed correctly. --- packages/e2e-tests/specs/experiments/navigation.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 5079ae17984c36..8ca5ccc391d5de 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -244,11 +244,8 @@ describe( 'Navigation', () => { ); await createPageButton.click(); - // Wait until the link editor disappears. - await page.waitForSelector( - '.block-editor-link-control__search-input-wrapper', - { hidden: true } - ); + // wait for the creating confirmation to go away, and we should now be focused on our text input + await page.waitForSelector( ':focus.rich-text' ); // Confirm the new link is focused. const isInLinkRichText = await page.evaluate( From dd5d124413d422407329c2a342efcfc4680a018d Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 11 Feb 2020 13:07:41 -0600 Subject: [PATCH 133/170] Added a check on the active element to make sure the focused block matches our newly created page title --- packages/e2e-tests/specs/experiments/navigation.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 8ca5ccc391d5de..5e2aaca810aedc 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -253,7 +253,8 @@ describe( 'Navigation', () => { document.activeElement.classList.contains( 'rich-text' ) && !! document.activeElement.closest( '.block-editor-block-list__block' - ) + ) && + document.activeElement.innerText === 'My New Page' ); expect( isInLinkRichText ).toBe( true ); From 58b3823d6188cb11c1504e5f522d3d66cf8923ee Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 12 Feb 2020 09:34:38 +0000 Subject: [PATCH 134/170] Update test to target specific input field for focus state rather than wrapper Previously the test was checking for focus on a
. Amended to target the element as that is what actually will receive focus. --- packages/block-editor/src/components/url-input/index.js | 1 + packages/e2e-tests/specs/experiments/navigation.test.js | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 4816e343ae2a76..89348662a94a7e 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -428,6 +428,7 @@ class URLInput extends Component { } ) } > { ); await createEmptyButton.click(); + // Wait for URL input to be focused + await page.waitForSelector( ':focus.block-editor-url-input__input' ); + // After adding a new block, search input should be shown immediately. const isInURLInput = await page.evaluate( - () => !! document.activeElement.closest( '.block-editor-url-input' ) + () => + !! document.activeElement.matches( + '.block-editor-url-input__input' + ) ); expect( isInURLInput ).toBe( true ); From 3c5b712c49d36a6ac9c384b1a5423c3b7e53954a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 12 Feb 2020 10:34:04 +0000 Subject: [PATCH 135/170] =?UTF-8?q?Refactor=20e2e=20test=20to=20be=20absol?= =?UTF-8?q?utelty=20100%=20sure=20we=E2=80=99re=20in=20the=20input=20befor?= =?UTF-8?q?e=20typing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the tests were less than strict about whether the focus was in the input element. Improve this across all tests. --- .../specs/experiments/navigation.test.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index fd8aae0e4170db..9545b3e02fbd4f 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -176,8 +176,17 @@ describe( 'Navigation', () => { // After adding a new block, search input should be shown immediately. // Verify that Escape would close the popover. // Regression: https://github.com/WordPress/gutenberg/pull/19885 + // Wait for URL input to be focused + await page.waitForSelector( + 'input.block-editor-url-input__input:focus' + ); + + // After adding a new block, search input should be shown immediately. const isInURLInput = await page.evaluate( - () => !! document.activeElement.closest( '.block-editor-url-input' ) + () => + !! document.activeElement.matches( + 'input.block-editor-url-input__input' + ) ); expect( isInURLInput ).toBe( true ); await page.keyboard.press( 'Escape' ); @@ -221,13 +230,15 @@ describe( 'Navigation', () => { await createEmptyButton.click(); // Wait for URL input to be focused - await page.waitForSelector( ':focus.block-editor-url-input__input' ); + await page.waitForSelector( + 'input.block-editor-url-input__input:focus' + ); // After adding a new block, search input should be shown immediately. const isInURLInput = await page.evaluate( () => !! document.activeElement.matches( - '.block-editor-url-input__input' + 'input.block-editor-url-input__input' ) ); expect( isInURLInput ).toBe( true ); @@ -245,6 +256,7 @@ describe( 'Navigation', () => { await page.waitForSelector( '.block-editor-link-control__search-create' ); + const [ createPageButton ] = await page.$x( '//button[contains(concat(" ", @class, " "), " block-editor-link-control__search-create ")]' ); From 2867ebf69167b31b151a5033d48414b126235168 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 12 Feb 2020 11:27:40 +0000 Subject: [PATCH 136/170] Attempt to fix e2e test via simplifying selection of create button --- .../e2e-tests/specs/experiments/navigation.test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 9545b3e02fbd4f..628ffb7d2df53c 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -252,14 +252,20 @@ describe( 'Navigation', () => { // Mock request for creating pages. await mockCreatePageResponse( 'My New Page', 'my-new-page' ); + // Wait for URL input to be focused + await page.waitForSelector( + 'input.block-editor-url-input__input:focus' + ); + // Wait for the create button to appear and click it. await page.waitForSelector( '.block-editor-link-control__search-create' ); - const [ createPageButton ] = await page.$x( - '//button[contains(concat(" ", @class, " "), " block-editor-link-control__search-create ")]' + const createPageButton = await page.$( + '.block-editor-link-control__search-create' ); + await createPageButton.click(); // wait for the creating confirmation to go away, and we should now be focused on our text input From bd8f05c15ff13a3a96b9d66dc21c816f0e089324 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 12 Feb 2020 11:37:14 +0000 Subject: [PATCH 137/170] =?UTF-8?q?Ensure=20Create=20button=20is=20?= =?UTF-8?q?=E2=80=9Cin=20view=E2=80=9D=20by=20removing=20other=20results.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s possible that on certain screen sizes there is not enough room to display the Create button without having to scroll the LinkContorl seaerch results panel. This could cause the selection of the button to fail. Testing if this fixes the broken e2e tests or at least makes them more resilient. --- .../__snapshots__/navigation.test.js.snap | 2 +- .../specs/experiments/navigation.test.js | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index eff8034bdb93bf..d40c430e87ec8d 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -20,6 +20,6 @@ exports[`Navigation allows a navigation menu to be created using existing pages exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` " - + " `; diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 628ffb7d2df53c..2ae0c77a2a22e2 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -219,6 +219,15 @@ describe( 'Navigation', () => { } ); it( 'allows pages to be created from the navigation block and their links added to menu', async () => { + // Ensure that no Pages are returned + await mockSearchResponse( [] ); + + // Mock request for creating pages. + await mockCreatePageResponse( + 'A really long page name that will not exist', + 'my-new-page' + ); + // Add the navigation block. await insertBlock( 'Navigation' ); @@ -246,12 +255,9 @@ describe( 'Navigation', () => { // Insert name for the new page. await page.type( 'input[placeholder="Search or type url"]', - 'My New Page' + 'A really long page name that will not exist' ); - // Mock request for creating pages. - await mockCreatePageResponse( 'My New Page', 'my-new-page' ); - // Wait for URL input to be focused await page.waitForSelector( 'input.block-editor-url-input__input:focus' @@ -278,11 +284,12 @@ describe( 'Navigation', () => { !! document.activeElement.closest( '.block-editor-block-list__block' ) && - document.activeElement.innerText === 'My New Page' + document.activeElement.innerText === + 'A really long page name that will not exist' ); expect( isInLinkRichText ).toBe( true ); - // Expect a Navigation Block with a link for "My New Page". + // Expect a Navigation Block with a link for "A really long page name that will not exist". expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); From 156312d6e4f50de1940c89106fa6774e65947277 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 12 Feb 2020 16:07:10 +0000 Subject: [PATCH 138/170] Provide more context on why we mock search and create API requests in e2e test --- .../e2e-tests/specs/experiments/navigation.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 2ae0c77a2a22e2..29ec5d71787533 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -219,10 +219,14 @@ describe( 'Navigation', () => { } ); it( 'allows pages to be created from the navigation block and their links added to menu', async () => { - // Ensure that no Pages are returned + // Mock request for creating pages and the page search response. + // We mock the page search to return no results and we use a very long + // page name because if the search returns existing pages then the + // "Create" suggestion might be below the scroll fold within the + // `LinkControl` search suggestions UI. If this happens then it's not + // possible to wait for the element to appear and the test will + // erroneously fail. await mockSearchResponse( [] ); - - // Mock request for creating pages. await mockCreatePageResponse( 'A really long page name that will not exist', 'my-new-page' From e5240603b05e55ab8b0fe129dd5e951d812b1f4d Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 12 Feb 2020 10:13:17 -0600 Subject: [PATCH 139/170] Updated routes on mock responses in e2e navigation tests and snapshots to hopefully make e2e tests easier to debug --- .../experiments/__snapshots__/navigation.test.js.snap | 10 +++++----- .../e2e-tests/specs/experiments/navigation.test.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index d40c430e87ec8d..1e43d43fc4ea67 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -4,22 +4,22 @@ exports[`Navigation allows a navigation menu to be created from an empty menu us " - + " `; exports[`Navigation allows a navigation menu to be created using existing pages 1`] = ` " - + - + - + " `; exports[`Navigation allows pages to be created from the navigation block and their links added to menu 1`] = ` " - + " `; diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 29ec5d71787533..f182bfd8c48a20 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -15,7 +15,7 @@ async function mockPagesResponse( pages ) { const mappedPages = pages.map( ( { title, slug }, index ) => ( { id: index + 1, type: 'page', - link: `https://this/is/a/test/url/${ slug }`, + link: `https://this/is/a/test/page/${ slug }`, title: { rendered: title, raw: title, @@ -41,7 +41,7 @@ async function mockSearchResponse( items ) { subtype: 'page', title, type: 'post', - url: `https://this/is/a/test/url/${ slug }`, + url: `https://this/is/a/test/search/${ slug }`, } ) ); await setUpResponseMocking( [ @@ -59,7 +59,7 @@ async function mockCreatePageResponse( title, slug ) { id: 1, title: { raw: title, rendered: title }, type: 'page', - link: `https://this/is/a/test/url/${ slug }`, + link: `https://this/is/a/test/create/page/${ slug }`, slug, }; From b4c932f63ee650c6cf6cc4a63ef7e524906d8229 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 12 Feb 2020 13:38:19 -0600 Subject: [PATCH 140/170] Added description and documentation to updateActiveNavigatinoLink --- packages/e2e-tests/specs/experiments/navigation.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index f182bfd8c48a20..caab5b25438adf 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -74,6 +74,11 @@ async function mockCreatePageResponse( title, slug ) { ] ); } +/** + * Interacts with the LinkControl to perform a search and select a returned suggestion + * @param {string} url What will be typed in the search input + * @param {string} label What the resulting label will be in the creating Navigation Link Block after the block is created. + */ async function updateActiveNavigationLink( { url, label } ) { if ( url ) { await page.type( 'input[placeholder="Search or type url"]', url ); From 73eeefc56f5a40169fd5053e65c7f4f307439e67 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 12 Feb 2020 13:54:11 -0600 Subject: [PATCH 141/170] More explicity select which suggestion we want in updateActiveNavigationLink --- .../specs/experiments/navigation.test.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index caab5b25438adf..348c6d05e130f9 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -78,18 +78,27 @@ async function mockCreatePageResponse( title, slug ) { * Interacts with the LinkControl to perform a search and select a returned suggestion * @param {string} url What will be typed in the search input * @param {string} label What the resulting label will be in the creating Navigation Link Block after the block is created. + * @param {string} type What kind of suggestion should be clicked, ie. 'url', 'create', or 'entity' */ -async function updateActiveNavigationLink( { url, label } ) { +async function updateActiveNavigationLink( { url, label, type } ) { + const typeClasses = { + create: 'block-editor-link-control__search-create', + entity: 'is-entity', + url: 'is-url', + }; + if ( url ) { await page.type( 'input[placeholder="Search or type url"]', url ); // Wait for the autocomplete suggestion item to appear. await page.waitForXPath( - `//span[@class="block-editor-link-control__search-item-title"]/mark[text()="${ url }"]` + `//span[contains(@class, 'block-editor-link-control__search-item-title') and contains(@class, ${ typeClasses[ type ] })]/mark[text()="${ url }"]` + ); + // Set the suggestion + const [ suggestion ] = await page.$x( + `//span[contains(@class, 'block-editor-link-control__search-item-title') and contains(@class, ${ typeClasses[ type ] })]/mark[text()="${ url }"]` ); - // Navigate to the first suggestion. - await page.keyboard.press( 'ArrowDown' ); - // Select the suggestion. - await page.keyboard.press( 'Enter' ); + // Select it (so we're clicking the right one, even if it's further down the list) + await suggestion.click(); } if ( label ) { @@ -168,6 +177,7 @@ describe( 'Navigation', () => { await updateActiveNavigationLink( { url: 'https://wordpress.org', label: 'WP', + type: 'url', } ); // Move the mouse to reveal the block movers. Without this the test seems to fail. @@ -217,6 +227,7 @@ describe( 'Navigation', () => { await updateActiveNavigationLink( { url: 'Contact Us', label: 'Get in touch', + type: 'entity', } ); // Expect a Navigation Block with two Navigation Links in the snapshot. From 0087c57a4d8b99c376fc5df46b38548dc31bc500 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 12 Feb 2020 13:57:38 -0600 Subject: [PATCH 142/170] Await input focus before selecting the label on updateActiveNavigationLink --- packages/e2e-tests/specs/experiments/navigation.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 348c6d05e130f9..a86b079f95c862 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -102,6 +102,9 @@ async function updateActiveNavigationLink( { url, label, type } ) { } if ( label ) { + // Wait for rich text editor input to be focused before we start typing the label + await page.waitForSelector( ':focus.rich-text' ); + // With https://github.com/WordPress/gutenberg/pull/19686, we're auto-selecting the label if the label is URL-ish. // In this case, it means we have to select and delete the label if it's _not_ the url. if ( label !== url ) { From 24c3680723104b6811332f23381b4aa8cd639d50 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 12 Feb 2020 15:51:45 -0600 Subject: [PATCH 143/170] Fixed xpath selector in updateActiveNavigationLink and made the search page term more unique --- .../__snapshots__/navigation.test.js.snap | 2 +- .../specs/experiments/navigation.test.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index 1e43d43fc4ea67..1912fba1de3e19 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -4,7 +4,7 @@ exports[`Navigation allows a navigation menu to be created from an empty menu us " - + " `; diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index a86b079f95c862..aabda57d9d4087 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -89,14 +89,14 @@ async function updateActiveNavigationLink( { url, label, type } ) { if ( url ) { await page.type( 'input[placeholder="Search or type url"]', url ); + + const suggestionPath = `//button[contains(@class, 'block-editor-link-control__search-item') and contains(@class, '${ typeClasses[ type ] }')]/span/span[@class='block-editor-link-control__search-item-title']/mark[text()="${ url }"]`; + // Wait for the autocomplete suggestion item to appear. - await page.waitForXPath( - `//span[contains(@class, 'block-editor-link-control__search-item-title') and contains(@class, ${ typeClasses[ type ] })]/mark[text()="${ url }"]` - ); + await page.waitForXPath( suggestionPath ); // Set the suggestion - const [ suggestion ] = await page.$x( - `//span[contains(@class, 'block-editor-link-control__search-item-title') and contains(@class, ${ typeClasses[ type ] })]/mark[text()="${ url }"]` - ); + const [ suggestion ] = await page.$x( suggestionPath ); + // Select it (so we're clicking the right one, even if it's further down the list) await suggestion.click(); } @@ -223,13 +223,13 @@ describe( 'Navigation', () => { // For the second nav link block use an existing internal page. // Mock the api response so that it's consistent. await mockSearchResponse( [ - { title: 'Contact Us', slug: 'contact-us' }, + { title: 'Get in Touch', slug: 'get-in-touch' }, ] ); // Add a link to the default Navigation Link block. await updateActiveNavigationLink( { - url: 'Contact Us', - label: 'Get in touch', + url: 'Get in Touch', + label: 'Contact', type: 'entity', } ); From 8a3e9f2334a83e4105b6212aad7fa306354c9045 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 14:30:58 +0000 Subject: [PATCH 144/170] Remove duplicate error notice --- .../src/components/link-control/search-input.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index a7693a21a6647b..051a4c67b47066 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -78,17 +78,6 @@ const LinkControlSearchInput = ( { className="block-editor-link-control__search-submit" />
-
- { errorMessage && ( - - { errorMessage } - - ) } -
{ errorMessage && ( From 6137f6fa39cb6063ca87bae0a9bd89dec88c499c Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 14:32:50 +0000 Subject: [PATCH 145/170] Remove superflous prop Introduced via rebase (again). --- packages/block-library/src/navigation-link/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 724e743af805e5..f4d5d583abed74 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -370,7 +370,6 @@ export default compose( [ showSubmenuIcon, textColor: navigationBlockAttributes.textColor, backgroundColor: navigationBlockAttributes.backgroundColor, - navigationBlockAttributes, userCanCreatePages, rgbTextColor: getColorObjectByColorSlug( colors, From a2c76676e629097f3c5fc79ea5eca02d831274fc Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 14:43:17 +0000 Subject: [PATCH 146/170] Simply listbox labelling for a11y MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per this thread in WPOrg Slack (https://wordpress.slack.com/archives/C02RP4X03/p1581500184181100) it’s better to have a HTML based label over aria labels under all circumstances. Therefore despite what it says on MDN docs for listbox (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role) we simplify to only use a HTML label and refer to that with aria-labelledby. --- .../src/components/link-control/index.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 088dc10d1ef35d..f1b2b2c24b254d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -392,18 +392,20 @@ function LinkControl( { // According to guidelines aria-label should be added if the label // itself is not visible. // See: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role - const searchResultsLabelId = isInitialSuggestions - ? `block-editor-link-control-search-results-label-${ instanceId }` - : undefined; + const searchResultsLabelId = `block-editor-link-control-search-results-label-${ instanceId }`; const labelText = isInitialSuggestions ? __( 'Recently updated' ) - : sprintf( __( 'Search results for %s' ), inputValue ); - const ariaLabel = isInitialSuggestions ? undefined : labelText; + : sprintf( __( 'Search results for "%s"' ), inputValue ); + const searchResultsLabel = ( { labelText } @@ -411,12 +413,11 @@ function LinkControl( { return (
- { isInitialSuggestions && searchResultsLabel } + { searchResultsLabel }
{ suggestions.map( ( suggestion, index ) => { if ( From 8b29067658fa25b398e5df7539d9655bbed435ef Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 14:49:04 +0000 Subject: [PATCH 147/170] =?UTF-8?q?Remove=20manual=20speak=20of=20Notice?= =?UTF-8?q?=20as=20it=E2=80=99s=20now=20built=20in=20to=20the=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses https://github.com/WordPress/gutenberg/pull/15745 --- .../components/link-control/search-input.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 051a4c67b47066..f903e6fd17da33 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -1,14 +1,9 @@ -/** - * External dependencies - */ -import { noop } from 'lodash'; - /** * WordPress dependencies */ -import { useState, useEffect } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Button, Notice, withSpokenMessages } from '@wordpress/components'; +import { Button, Notice } from '@wordpress/components'; /** * Internal dependencies @@ -23,16 +18,9 @@ const LinkControlSearchInput = ( { fetchSuggestions, showInitialSuggestions, errorMessage, - speak = noop, } ) => { const [ selectedSuggestion, setSelectedSuggestion ] = useState(); - useEffect( () => { - if ( errorMessage ) { - speak( errorMessage, 'assertive' ); - } - }, [ errorMessage ] ); - /** * Handles the user moving between different suggestions. Does not handle * choosing an individual item. @@ -85,6 +73,7 @@ const LinkControlSearchInput = ( { className="block-editor-link-control__search-error" status="error" isDismissible={ false } + politeness="assertive" > { errorMessage } @@ -93,4 +82,4 @@ const LinkControlSearchInput = ( { ); }; -export default withSpokenMessages( LinkControlSearchInput ); +export default LinkControlSearchInput; From cd9ab52685168f0a06370594e3d2a88816cb0f76 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 15:53:56 +0000 Subject: [PATCH 148/170] Remove requirement to pass a Promise for `createSuggestion` prop Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r378866968 --- .../src/components/link-control/README.md | 15 +++- .../src/components/link-control/index.js | 4 +- .../src/components/link-control/test/index.js | 69 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 72865e1e188dba..9de2a77ec62f62 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -89,7 +89,7 @@ This is often used to allow on-the-fly creation of new entities (eg: `Posts`, `Pages`) based on the text the user has entered into the link search UI. For example, the Navigation Block uses this to create Pages on demand. -When called, `createSuggestion` should return a `Promise` which resolves to a +When called, `createSuggestion` may return either a new link `value` or a `Promise` which resolves to a new link `value` (see above) with the shape: ```js @@ -103,6 +103,7 @@ new link `value` (see above) with the shape: #### Example ```jsx +// Promise example { // Hard coded values. These could be dynamically created by calling out to an API which creates an entity (eg: https://developer.wordpress.org/rest-api/reference/pages/#create-a-page). @@ -114,4 +115,16 @@ new link `value` (see above) with the shape: } }} /> + +// Non-Promise example + ( + { + id: 1234, + type: 'page', + title: inputText, + url: '/some-url-here' + } + )} +/> ``` \ No newline at end of file diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index f1b2b2c24b254d..c1627665479c22 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -332,7 +332,9 @@ function LinkControl( { // Make cancellable in order that we can avoid setting State // if the component unmounts during the call to `createSuggestion` cancelableCreateSuggestion = makeCancelable( - createSuggestion( suggestionTitle ) + // Using Promise.resolve to allow createSuggestion to return a + // non-Promise based value. + Promise.resolve( createSuggestion( suggestionTitle ) ) ); newSuggestion = await cancelableCreateSuggestion.promise; } catch ( error ) { diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 85108b0431ce6a..a975611012c237 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -640,6 +640,75 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { } ); + it( 'should allow createSuggestion prop to return a non-Promise value', async () => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + { + setLink( suggestion ); + } } + createSuggestion={ ( title ) => ( { + title, + id: 123, + url: '/?p=123', + type: 'page', + } ) } + /> + ); + }; + + act( () => { + render( , container ); + } ); + + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: 'Some new page to create' }, + } ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitrary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + + const createButton = first( + Array.from( searchResultElements ).filter( ( result ) => + result.innerHTML.includes( 'New page' ) + ) + ); + + await act( async () => { + Simulate.click( createButton ); + } ); + + await eventLoopTick(); + + const currentLink = container.querySelector( + '[aria-label="Currently selected"]' + ); + + const currentLinkHTML = currentLink.innerHTML; + + expect( currentLinkHTML ).toEqual( + expect.stringContaining( 'Some new page to create' ) + ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( '/?p=123' ) + ); + } ); + it( 'should allow creation of entities via the keyboard', async () => { const entityNameText = 'A new page to be created'; From 2fe9a5cf5f8a6e1cd2bf8ba38be00b72634e57a9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 13 Feb 2020 16:13:35 +0000 Subject: [PATCH 149/170] Handle cancellable handler props using refs Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r378869842. Previously we were treating a functional component as though it would live forever when in fact it could easily be unmounted and all internal var references wiped. Using refs solves this as they persiste between renders. --- .../src/components/link-control/index.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c1627665479c22..1ecea4962f322f 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -129,8 +129,8 @@ function LinkControl( { forceIsEditingLink, createSuggestion, } ) { - let cancelableOnCreate; - let cancelableCreateSuggestion; + const cancelableOnCreate = useRef(); + const cancelableCreateSuggestion = useRef(); const wrapperNode = useRef(); const instanceId = useInstanceId( LinkControl ); @@ -197,11 +197,11 @@ function LinkControl( { useEffect( () => { return () => { // componentDidUnmount - if ( cancelableOnCreate ) { - cancelableOnCreate.cancel(); + if ( cancelableOnCreate.current ) { + cancelableOnCreate.current.cancel(); } - if ( cancelableCreateSuggestion ) { - cancelableCreateSuggestion.cancel(); + if ( cancelableCreateSuggestion.current ) { + cancelableCreateSuggestion.current.cancel(); } }; }, [] ); @@ -331,12 +331,12 @@ function LinkControl( { try { // Make cancellable in order that we can avoid setting State // if the component unmounts during the call to `createSuggestion` - cancelableCreateSuggestion = makeCancelable( + cancelableCreateSuggestion.current = makeCancelable( // Using Promise.resolve to allow createSuggestion to return a // non-Promise based value. Promise.resolve( createSuggestion( suggestionTitle ) ) ); - newSuggestion = await cancelableCreateSuggestion.promise; + newSuggestion = await cancelableCreateSuggestion.current.promise; } catch ( error ) { if ( error.isCanceled ) { return; // bail out of state updates if the promise was cancelled @@ -431,13 +431,14 @@ function LinkControl( { searchTerm={ inputValue } onClick={ async () => { try { - cancelableOnCreate = makeCancelable( + cancelableOnCreate.current = makeCancelable( handleOnCreate( suggestion.title ) ); - await cancelableOnCreate.promise; + await cancelableOnCreate.current + .promise; } catch ( error ) { if ( error && error.isCanceled ) { return; // bail if canceled to avoid setting state @@ -511,11 +512,11 @@ function LinkControl( { CREATE_TYPE === suggestion.type ) { try { - cancelableOnCreate = makeCancelable( + cancelableOnCreate.current = makeCancelable( handleOnCreate( inputValue ) ); - await cancelableOnCreate.promise; + await cancelableOnCreate.current.promise; } catch ( error ) { if ( error && error.isCanceled ) { return; // bail if canceled to avoid setting state From 415d4d5473e9aa2d47d67af6bd40b5b99c0750ca Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 12:14:42 +0000 Subject: [PATCH 150/170] Fix Promise flow so that stopEditing is not called on createSuggestion error and error message is shown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were several problems here. * Originally the handleCreate method did `return` with a undefined value when the error was cancelled. This caused incorrect logic flows. * The `stopEditing` method was being called even if the Promise flow was handling an error state. This caused the “Currently selected” UI to show with an `undefined` value. * The `errorMessage` state was being reset ever time the input “changed”. This meant the error set in the Promise chain was being reset before it could be displayed. Fixing all the above issues has resolved the errors. --- .../src/components/link-control/index.js | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 1ecea4962f322f..9b282b5b4e91a9 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -212,7 +212,6 @@ function LinkControl( { * @param {string} val Current value returned by the search. */ const onInputChange = ( val = '' ) => { - setErrorMessage( null ); // remove lingering error messages setInputValue( val ); }; @@ -323,32 +322,21 @@ function LinkControl( { ); const handleOnCreate = async ( suggestionTitle ) => { - let newSuggestion; - setIsResolvingLink( true ); setErrorMessage( null ); - try { - // Make cancellable in order that we can avoid setting State - // if the component unmounts during the call to `createSuggestion` - cancelableCreateSuggestion.current = makeCancelable( - // Using Promise.resolve to allow createSuggestion to return a - // non-Promise based value. - Promise.resolve( createSuggestion( suggestionTitle ) ) - ); - newSuggestion = await cancelableCreateSuggestion.current.promise; - } catch ( error ) { - if ( error.isCanceled ) { - return; // bail out of state updates if the promise was cancelled - } - setErrorMessage( - error.msg || - __( - 'An unknown error occurred during creation. Please try again.' - ) - ); - } + // Make cancellable in order that we can avoid setting State + // if the component unmounts during the call to `createSuggestion` + cancelableCreateSuggestion.current = makeCancelable( + // Using Promise.resolve to allow createSuggestion to return a + // non-Promise based value. + Promise.resolve( createSuggestion( suggestionTitle ) ) + ); + const newSuggestion = await cancelableCreateSuggestion.current.promise; + // ******** + // NOTE: if the above Promise rejects then code below here will never run + // ******** setIsResolvingLink( false ); // Only set link if request is resolved, otherwise enable edit mode. @@ -439,14 +427,21 @@ function LinkControl( { await cancelableOnCreate.current .promise; + // Only stop editing if not rejected + stopEditing(); } catch ( error ) { if ( error && error.isCanceled ) { return; // bail if canceled to avoid setting state } - } - // Only setState if not cancelled - stopEditing(); + setErrorMessage( + __( + 'An unknown error occurred during creation. Please try again.' + ) + ); + setIsResolvingLink( false ); + setIsEditingLink( true ); + } } } key={ `${ suggestion.id }-${ suggestion.type }` } itemProps={ buildSuggestionItemProps( @@ -515,20 +510,25 @@ function LinkControl( { cancelableOnCreate.current = makeCancelable( handleOnCreate( inputValue ) ); - await cancelableOnCreate.current.promise; + stopEditing(); } catch ( error ) { if ( error && error.isCanceled ) { return; // bail if canceled to avoid setting state } + + setErrorMessage( + __( + 'An unknown error occurred during creation. Please try again.' + ) + ); + setIsResolvingLink( false ); + setIsEditingLink( true ); } } else { handleSelectSuggestion( suggestion, value ); + stopEditing(); } - - // Must be called after handling to ensure focus is - // managed correctly. - stopEditing(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } From c6305e64ac655137bf03e5ce3cd83e18c69a27c1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 15:17:14 +0000 Subject: [PATCH 151/170] Fix rebase regressions. --- .../block-editor/src/components/link-control/search-input.js | 4 ++-- .../components/link-control/test/__snapshots__/index.js.snap | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index f903e6fd17da33..576e91b74df0dd 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -4,7 +4,7 @@ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button, Notice } from '@wordpress/components'; - +import { keyboardReturn } from '@wordpress/icons'; /** * Internal dependencies */ @@ -62,7 +62,7 @@ const LinkControlSearchInput = ( {
diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index 9049884131f8e4..db2b039353fb7d 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render 1`] = `""`; +exports[`Basic rendering should render 1`] = `""`; From 6289a0cccfafe333cadf003959cd80ecfed62a3f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 15:29:48 +0000 Subject: [PATCH 152/170] Remove cleanForSlug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was used to try and “clean” the id primarily to make it valid as a React key. However we’ve discovered that any string is potentially valid. Moreover cleanForSlug might end up making two urls that are otherwise distinct become identical via stripping out of various parts of the full URL. See https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316 --- packages/block-editor/src/components/link-control/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 9b282b5b4e91a9..cb77dcabb4f154 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -17,7 +17,6 @@ import { useEffect, } from '@wordpress/element'; import { - cleanForSlug, safeDecodeURI, filterURLForDisplay, isURL, @@ -234,7 +233,7 @@ function LinkControl( { return Promise.resolve( [ { - id: cleanForSlug( val ), + id: val, title: val, url: type === 'URL' ? prependHTTP( val ) : val, type, @@ -277,7 +276,7 @@ function LinkControl( { return maybeURL( val ) ? results : results.concat( { - id: cleanForSlug( val ), + id: val, title: val, // must match the existing ``s text value url: val, // must match the existing ``s text value type: CREATE_TYPE, From e9845da20e095ed16b74b8811701efbe5e549d79 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 15:36:13 +0000 Subject: [PATCH 153/170] =?UTF-8?q?Removes=20`id`=20prop=20from=20?= =?UTF-8?q?=E2=80=9CCreate=E2=80=9D=20result=20and=20uses=20static=20strin?= =?UTF-8?q?g=20for=20React=20key=20prop.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316 --- .../block-editor/src/components/link-control/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index cb77dcabb4f154..b835b209ec1f21 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -276,7 +276,9 @@ function LinkControl( { return maybeURL( val ) ? results : results.concat( { - id: val, + // the `id` prop is intentionally ommitted here because it + // is never exposed as part of the component's public API. + // see: https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316. title: val, // must match the existing ``s text value url: val, // must match the existing ``s text value type: CREATE_TYPE, @@ -442,7 +444,10 @@ function LinkControl( { setIsEditingLink( true ); } } } - key={ `${ suggestion.id }-${ suggestion.type }` } + // Intentionally only using `type` here as + // the constant is enough to uniquely + // identify the single "CREATE" suggestion. + key={ suggestion.type } itemProps={ buildSuggestionItemProps( suggestion, index From 4b105508e771cbd856e9543ea083b29e3bf4755f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 15:55:06 +0000 Subject: [PATCH 154/170] Fix e2e test snapshot to account for using full URL as the suggestion prop without cleanForSlug --- .../specs/experiments/__snapshots__/navigation.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap index 1912fba1de3e19..9bef34b8e46192 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation.test.js.snap @@ -2,7 +2,7 @@ exports[`Navigation allows a navigation menu to be created from an empty menu using a mixture of internal and external links 1`] = ` " - + " From d54a682579d4a01a6217c7d51d2aadc8816273a7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 14 Feb 2020 16:00:23 +0000 Subject: [PATCH 155/170] =?UTF-8?q?Remove=20explicit=20=E2=80=9Cpoliteness?= =?UTF-8?q?=E2=80=9D=20and=20use=20default=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/WordPress/gutenberg/pull/19775#pullrequestreview-359012820 --- .../block-editor/src/components/link-control/search-input.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 576e91b74df0dd..622984af2091d9 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -73,7 +73,6 @@ const LinkControlSearchInput = ( { className="block-editor-link-control__search-error" status="error" isDismissible={ false } - politeness="assertive" > { errorMessage } From c11ff36327227f3f5786e452b0587fcb17b2fd98 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 17 Feb 2020 10:18:18 +0000 Subject: [PATCH 156/170] Update docs to distinguish suggestion from value. --- packages/block-editor/src/components/link-control/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 9de2a77ec62f62..ad82cae83d2b64 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -89,8 +89,8 @@ This is often used to allow on-the-fly creation of new entities (eg: `Posts`, `Pages`) based on the text the user has entered into the link search UI. For example, the Navigation Block uses this to create Pages on demand. -When called, `createSuggestion` may return either a new link `value` or a `Promise` which resolves to a -new link `value` (see above) with the shape: +When called, `createSuggestion` may return either a new suggestion directly or a `Promise` which resolves to a +new suggestion. Suggestions have the following shape: ```js { From 3a6e8e3655e4f3b487904984d30b5f1ae66f306b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 17 Feb 2020 10:22:19 +0000 Subject: [PATCH 157/170] =?UTF-8?q?Adds=20comment=20for=20=E2=80=9CCreate?= =?UTF-8?q?=E2=80=9D=20constant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r379591014 --- packages/block-editor/src/components/link-control/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index b835b209ec1f21..203f92d0d94771 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -34,6 +34,10 @@ import LinkControlSettingsDrawer from './settings-drawer'; import LinkControlSearchItem from './search-item'; import LinkControlSearchInput from './search-input'; import LinkControlSearchCreate from './search-create-button'; + +// Used as a unique identifier for the "Create" option within search results. +// Used to help distinguish the "Create" suggestion within the search results in +// order to handle it as a unique case. const CREATE_TYPE = '__CREATE__'; /** From 4b515336101d7a404061d91ffbf3b4a50590950e Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 17 Feb 2020 10:31:35 +0000 Subject: [PATCH 158/170] Adds type def for suggestion and reformats type defs Addresses https://github.com/WordPress/gutenberg/pull/19775#discussion_r379590663 --- .../src/components/link-control/index.js | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 203f92d0d94771..073c11ed839f58 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -102,19 +102,33 @@ const makeCancelable = ( promise ) => { /** @typedef {(nextValue:WPLinkControlValue)=>void} WPLinkControlOnChangeProp */ +/** + * Properties associated with a search suggestion used within the LinkControl. + * + * @typedef WPLinkControlSuggestion + * + * @property {string} id Identifier to use to uniquely identify the suggestion. + * @property {string} type Identifies the type of the suggestion (eg: `post`, + * `page`, `url`...etc) + * @property {string} title Human-readable label to show in user interface. + * @property {string} url A URL for the suggestion. + */ + +/** @typedef {(title:string)=>WPLinkControlSuggestion} WPLinkControlCreateSuggestionProp */ + /** * @typedef WPLinkControlProps * - * @property {(WPLinkControlSetting[])=} settings An array of settings objects. Each object will used to - * render a `ToggleControl` for that setting. - * @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the - * internal editing state of the component to respective - * show or not show the URL input field. - * @property {WPLinkControlValue=} value Current link value. - * @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if - * the user selects a new link or updates settings. - * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. - * @property {(title:string)=>WPLinkControlValue=} createSuggestion Handler to manage creation of link value from suggestion. + * @property {(WPLinkControlSetting[])=} settings An array of settings objects. Each object will used to + * render a `ToggleControl` for that setting. + * @property {boolean=} forceIsEditingLink If passed as either `true` or `false`, controls the + * internal editing state of the component to respective + * show or not show the URL input field. + * @property {WPLinkControlValue=} value Current link value. + * @property {WPLinkControlOnChangeProp=} onChange Value change handler, called with the updated value if + * the user selects a new link or updates settings. + * @property {boolean=} showInitialSuggestions Whether to present initial suggestions immediately. + * @property {WPLinkControlCreateSuggestionProp=} createSuggestion Handler to manage creation of link value from suggestion. */ /** From d081116839d74042070098452cd8653f4189affc Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 17 Feb 2020 10:35:15 +0000 Subject: [PATCH 159/170] Fix to not render create button if there is no search term. This was a throw back to a previous state of the `LinkControl` component whereby we wanted to render a create option to allow the user to create blank pages. This was removed as a requirement but the component was not fixed to account for that. Addresses: https://github.com/WordPress/gutenberg/pull/19775#discussion_r379590227 --- .../src/components/link-control/search-create-button.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 039f59f352c859..0b9f48394107ce 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -11,11 +11,15 @@ import { Button, Icon } from '@wordpress/components'; import { __experimentalCreateInterpolateElement } from '@wordpress/element'; export const LinkControlSearchCreate = ( { - searchTerm = '', + searchTerm, onClick, itemProps, isSelected, } ) => { + if ( ! searchTerm ) { + return null; + } + return (