From d4c4f8d493e1f6a4bcc2871f8313e90b38d8315a Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 13:36:31 +0100 Subject: [PATCH 01/10] =?UTF-8?q?Restirct=20what=20matches=20as=20potentia?= =?UTF-8?q?lly=20being=20a=20=E2=80=9Curl=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/link-control/is-url-like.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/link-control/is-url-like.js b/packages/block-editor/src/components/link-control/is-url-like.js index 3021ace38a26e..896a69e34fab4 100644 --- a/packages/block-editor/src/components/link-control/is-url-like.js +++ b/packages/block-editor/src/components/link-control/is-url-like.js @@ -1,8 +1,3 @@ -/** - * WordPress dependencies - */ -import { isURL } from '@wordpress/url'; - /** * 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. For @@ -16,5 +11,8 @@ import { isURL } from '@wordpress/url'; */ export default function isURLLike( val ) { const isInternal = val?.startsWith( '#' ); - return isURL( val ) || ( val && val.includes( 'www.' ) ) || isInternal; + const isWWW = val?.startsWith( 'www.' ); + const isHTTPProtocol = /^(http|https)/.test( val ); + + return isHTTPProtocol || isWWW || isInternal; } From 8c9d7ece335cf5275a9d41e33f18a6665902f559 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 13:51:17 +0100 Subject: [PATCH 02/10] Remove direct entry results from coming up in entity search suggestions --- .../link-control/use-search-handler.js | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/link-control/use-search-handler.js b/packages/block-editor/src/components/link-control/use-search-handler.js index 47c20ed60b8e3..77b91901bac5c 100644 --- a/packages/block-editor/src/components/link-control/use-search-handler.js +++ b/packages/block-editor/src/components/link-control/use-search-handler.js @@ -51,23 +51,16 @@ const handleEntitySearch = async ( val, suggestionsQuery, fetchSearchSuggestions, - directEntryHandler, withCreateSuggestion, - withURLSuggestion, pageOnFront ) => { const { isInitialSuggestions } = suggestionsQuery; - let resultsIncludeFrontPage = false; - let results = await Promise.all( [ - fetchSearchSuggestions( val, suggestionsQuery ), - directEntryHandler( val ), - ] ); + const results = await fetchSearchSuggestions( val, suggestionsQuery ); // Identify front page and update type to match. - results[ 0 ] = results[ 0 ].map( ( result ) => { + results.map( ( result ) => { if ( Number( result.id ) === pageOnFront ) { - resultsIncludeFrontPage = true; result.isFrontPage = true; return result; } @@ -75,22 +68,6 @@ const handleEntitySearch = async ( return result; } ); - const couldBeURL = ! val.includes( ' ' ); - - // 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. - if ( - ! resultsIncludeFrontPage && - couldBeURL && - withURLSuggestion && - ! isInitialSuggestions - ) { - results = results[ 0 ].concat( results[ 1 ] ); - } else { - results = results[ 0 ]; - } - // If displaying initial suggestions just return plain results. if ( isInitialSuggestions ) { return results; @@ -150,12 +127,18 @@ export default function useSearchHandler( val, { ...suggestionsQuery, isInitialSuggestions }, fetchSearchSuggestions, - directEntryHandler, withCreateSuggestion, withURLSuggestion, pageOnFront ); }, - [ directEntryHandler, fetchSearchSuggestions, withCreateSuggestion ] + [ + directEntryHandler, + fetchSearchSuggestions, + pageOnFront, + suggestionsQuery, + withCreateSuggestion, + withURLSuggestion, + ] ); } From 1c0a55fda3296f6f7a334c02203cb91ffb30ef50 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 13:51:41 +0100 Subject: [PATCH 03/10] Make is-url-like stricter --- .../components/link-control/is-url-like.js | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/link-control/is-url-like.js b/packages/block-editor/src/components/link-control/is-url-like.js index 896a69e34fab4..b5012e76acb18 100644 --- a/packages/block-editor/src/components/link-control/is-url-like.js +++ b/packages/block-editor/src/components/link-control/is-url-like.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { getProtocol, isValidProtocol, isValidFragment } from '@wordpress/url'; + /** * 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. For @@ -10,9 +15,20 @@ * @return {boolean} whether or not the value is potentially a URL. */ export default function isURLLike( val ) { - const isInternal = val?.startsWith( '#' ); + const hasSpaces = val.includes( ' ' ); + + if ( hasSpaces ) { + return false; + } + + const protocol = getProtocol( val ); + const protocolIsValid = isValidProtocol( protocol ); + const isWWW = val?.startsWith( 'www.' ); - const isHTTPProtocol = /^(http|https)/.test( val ); + const isHTTPProtocol = /^(http|https)/.test( val ) && protocolIsValid; + const isMailTo = val?.startsWith( 'mailto:' ) && protocolIsValid; + const isTel = val?.startsWith( 'tel:' ) && protocolIsValid; + const isInternal = val?.startsWith( '#' ) && isValidFragment( val ); - return isHTTPProtocol || isWWW || isInternal; + return isHTTPProtocol || isWWW || isMailTo || isTel || isInternal; } From 42fe981fa9ddd9f35fc9d38bdd4d407a948fed92 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 14:01:55 +0100 Subject: [PATCH 04/10] Add initial tests for isURLLike --- .../link-control/test/is-url-like.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/block-editor/src/components/link-control/test/is-url-like.js diff --git a/packages/block-editor/src/components/link-control/test/is-url-like.js b/packages/block-editor/src/components/link-control/test/is-url-like.js new file mode 100644 index 0000000000000..0f1fc538a5c18 --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/is-url-like.js @@ -0,0 +1,43 @@ +/** + * Internal dependencies + */ +import isURLLike from '../is-url-like'; + +describe( 'isURLLike', () => { + it.each( [ 'https://wordpress.org', 'http://wordpress.org' ] )( + 'returns true for a string that starts with an http(s) protocol', + ( testString ) => { + expect( isURLLike( testString ) ).toBe( true ); + } + ); + + it.each( [ + 'hello world', + 'https:// has spaces even though starts with protocol', + 'www. wordpress . org', + ] )( + 'returns false for any string with spaces (e.g. "%s")', + ( testString ) => { + expect( isURLLike( testString ) ).toBe( false ); + } + ); + + it( 'returns false for a string without a protocol or a TLD', () => { + expect( isURLLike( 'somedirectentryhere' ) ).toBe( false ); + } ); + + it( 'returns true for a string beginning with www.', () => { + expect( isURLLike( 'www.wordpress.org' ) ).toBe( true ); + } ); + + it.each( [ 'mailto:test@wordpress.org', 'tel:123456' ] )( + 'returns true for common protocols', + ( testString ) => { + expect( isURLLike( testString ) ).toBe( true ); + } + ); + + it( 'returns true for internal anchor ("hash") links.', () => { + expect( isURLLike( '#someinternallink' ) ).toBe( true ); + } ); +} ); From b0777c0274f73ed6ca7dbb733e89979076907a07 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 14:50:56 +0100 Subject: [PATCH 05/10] Improve code with tests and adding check for TLDs --- .../components/link-control/is-url-like.js | 29 ++++++++++++++++++- .../link-control/test/is-url-like.js | 23 +++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/link-control/is-url-like.js b/packages/block-editor/src/components/link-control/is-url-like.js index b5012e76acb18..5edbcce432979 100644 --- a/packages/block-editor/src/components/link-control/is-url-like.js +++ b/packages/block-editor/src/components/link-control/is-url-like.js @@ -24,11 +24,38 @@ export default function isURLLike( val ) { const protocol = getProtocol( val ); const protocolIsValid = isValidProtocol( protocol ); + const mayBeTLD = hasPossibleTLD( val ); + const isWWW = val?.startsWith( 'www.' ); const isHTTPProtocol = /^(http|https)/.test( val ) && protocolIsValid; const isMailTo = val?.startsWith( 'mailto:' ) && protocolIsValid; const isTel = val?.startsWith( 'tel:' ) && protocolIsValid; const isInternal = val?.startsWith( '#' ) && isValidFragment( val ); - return isHTTPProtocol || isWWW || isMailTo || isTel || isInternal; + return ( + isHTTPProtocol || isWWW || isMailTo || isTel || isInternal || mayBeTLD + ); +} + +/** + * Checks if a given URL has a valid Top-Level Domain (TLD). + * + * @param {string} url - The URL to check. + * @param {number} maxLength - The maximum length of the TLD. + * @return {boolean} Returns true if the URL has a valid TLD, false otherwise. + */ +function hasPossibleTLD( url, maxLength = 6 ) { + // Clean the URL by removing anything after the first occurrence of "?" or "#". + const cleanedURL = url.split( /[?#]/ )[ 0 ]; + + // Regular expression explanation: + // - (?<=\S) : Positive lookbehind assertion to ensure there is at least one non-whitespace character before the TLD + // - \. : Matches a literal dot (.) + // - [a-zA-Z_]{2,maxLength} : Matches 2 to maxLength letters or underscores, representing the TLD + // - (?:\/|$) : Non-capturing group that matches either a forward slash (/) or the end of the string + const regex = new RegExp( + `(?<=\\S)\\.(?:[a-zA-Z_]{2,${ maxLength }})(?:\\/|$)` + ); + + return regex.test( cleanedURL ); } diff --git a/packages/block-editor/src/components/link-control/test/is-url-like.js b/packages/block-editor/src/components/link-control/test/is-url-like.js index 0f1fc538a5c18..37bec08993193 100644 --- a/packages/block-editor/src/components/link-control/test/is-url-like.js +++ b/packages/block-editor/src/components/link-control/test/is-url-like.js @@ -40,4 +40,27 @@ describe( 'isURLLike', () => { it( 'returns true for internal anchor ("hash") links.', () => { expect( isURLLike( '#someinternallink' ) ).toBe( true ); } ); + + // use .each to test multiple cases + it.each( [ + [ true, 'http://example.com' ], + [ true, 'https://test.co.uk?query=param' ], + [ true, 'ftp://openai.ai?param=value#section' ], + [ true, 'example.com' ], + [ true, 'http://example.com?query=param#section' ], + [ true, 'https://test.co.uk/some/path' ], + [ true, 'ftp://openai.ai/some/path' ], + [ true, 'example.org/some/path' ], + [ true, 'example_test.tld' ], + [ true, 'example_test.com' ], + [ false, 'example' ], + [ false, '.com' ], + [ true, '_test.com' ], + [ true, 'http://example_test.com' ], + ] )( + 'returns %s when testing against string "%s" for a valid TLD', + ( expected, testString ) => { + expect( isURLLike( testString ) ).toBe( expected ); + } + ); } ); From c30d9954f52e542d355e16622adf5a60d27f581b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 26 May 2023 14:54:46 +0100 Subject: [PATCH 06/10] Simply implementation --- .../src/components/link-control/is-url-like.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/link-control/is-url-like.js b/packages/block-editor/src/components/link-control/is-url-like.js index 5edbcce432979..58861b71b837b 100644 --- a/packages/block-editor/src/components/link-control/is-url-like.js +++ b/packages/block-editor/src/components/link-control/is-url-like.js @@ -27,14 +27,10 @@ export default function isURLLike( val ) { const mayBeTLD = hasPossibleTLD( val ); const isWWW = val?.startsWith( 'www.' ); - const isHTTPProtocol = /^(http|https)/.test( val ) && protocolIsValid; - const isMailTo = val?.startsWith( 'mailto:' ) && protocolIsValid; - const isTel = val?.startsWith( 'tel:' ) && protocolIsValid; + const isInternal = val?.startsWith( '#' ) && isValidFragment( val ); - return ( - isHTTPProtocol || isWWW || isMailTo || isTel || isInternal || mayBeTLD - ); + return protocolIsValid || isWWW || isInternal || mayBeTLD; } /** From 45dca15eacbf74a529637b30ef084b76d1229bd5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 6 Jun 2023 11:13:20 +0100 Subject: [PATCH 07/10] Fix tests --- .../src/components/link-control/test/index.js | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 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 55aab3816b27e..e4d3ed31e5bdd 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -196,8 +196,7 @@ describe( 'Basic rendering', () => { within( resultsList ).getAllByRole( 'option' ); expect( searchResultElements ).toHaveLength( - // The fauxEntitySuggestions length plus the 'Press ENTER to add this link' button. - fauxEntitySuggestions.length + 1 + fauxEntitySuggestions.length ); // Step down into the search results, highlighting the first result item. @@ -504,8 +503,7 @@ describe( 'Searching for a link', () => { .flat() .filter( Boolean ); - // Given we're mocking out the results we should always have 4 mark elements. - expect( searchResultTextHighlightElements ).toHaveLength( 4 ); + expect( searchResultTextHighlightElements ).toHaveLength( 3 ); // Make sure there are no `mark` elements which contain anything other // than the trimmed search term (ie: no whitespace). @@ -565,16 +563,15 @@ describe( 'Searching for a link', () => { const lastSearchResultItem = searchResultElements[ searchResultElements.length - 1 ]; - // We should see a search result for each of the expect search suggestions - // plus 1 additional one for the fallback URL suggestion. + // We should see a search result for each of the expect search suggestions. expect( searchResultElements ).toHaveLength( - fauxEntitySuggestions.length + 1 + fauxEntitySuggestions.length ); - // The last item should be a URL search suggestion. - expect( lastSearchResultItem ).toHaveTextContent( searchTerm ); - expect( lastSearchResultItem ).toHaveTextContent( 'URL' ); - expect( lastSearchResultItem ).toHaveTextContent( + // The URL search suggestion should not exist. + expect( lastSearchResultItem ).not.toHaveTextContent( searchTerm ); + expect( lastSearchResultItem ).not.toHaveTextContent( 'URL' ); + expect( lastSearchResultItem ).not.toHaveTextContent( 'Press ENTER to add this link' ); } @@ -952,8 +949,7 @@ describe( 'Default search suggestions', () => { } ) ).getAllByRole( 'option' ); - // It should match any url that's like ?p= and also include a URL option. - expect( searchResultElements ).toHaveLength( 5 ); + expect( searchResultElements ).toHaveLength( 4 ); expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); From ca177f5c0aa0b5f090f27c188bc0d70238468ec4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 6 Jun 2023 11:28:43 +0100 Subject: [PATCH 08/10] Test for only showing URL result when searching for URL like --- .../src/components/link-control/test/index.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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 e4d3ed31e5bdd..7566a901c074e 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -439,7 +439,7 @@ describe( 'Searching for a link', () => { expect( screen.queryByRole( 'presentation' ) ).not.toBeInTheDocument(); } ); - it( 'should display only search suggestions when current input value is not URL-like', async () => { + it( 'should display only search suggestions (and not URL result type) when current input value is not URL-like', async () => { const user = userEvent.setup(); const searchTerm = 'Hello world'; const firstSuggestion = fauxEntitySuggestions[ 0 ]; @@ -478,6 +478,36 @@ describe( 'Searching for a link', () => { ).not.toHaveTextContent( 'URL' ); } ); + it( 'should display only URL result when current input value is URL-like', async () => { + const user = userEvent.setup(); + const searchTerm = 'www.wordpress.org'; + + render( ); + + // Search Input UI. + const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); + + // Simulate searching for a term. + await user.type( searchInput, searchTerm ); + + const searchResultElement = within( + await screen.findByRole( 'listbox', { + name: /Search results for.*/, + } ) + ).getByRole( 'option' ); + + expect( searchResultElement ).toBeInTheDocument(); + + // Should only be the `URL` suggestion. + expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); + + expect( searchResultElement ).toHaveTextContent( searchTerm ); + expect( searchResultElement ).toHaveTextContent( 'URL' ); + expect( searchResultElement ).toHaveTextContent( + 'Press ENTER to add this link' + ); + } ); + it( 'should trim search term', async () => { const user = userEvent.setup(); const searchTerm = ' Hello '; From b0e07c777d1c86a61b9f073597f20f7b40e5fad4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 6 Jun 2023 11:35:24 +0100 Subject: [PATCH 09/10] Improve test criteria for URL-like in tests --- .../src/components/link-control/test/index.js | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 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 7566a901c074e..df7f1d78a197e 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -478,35 +478,46 @@ describe( 'Searching for a link', () => { ).not.toHaveTextContent( 'URL' ); } ); - it( 'should display only URL result when current input value is URL-like', async () => { - const user = userEvent.setup(); - const searchTerm = 'www.wordpress.org'; + it.each( [ + [ 'https://wordpress.org', 'URL' ], + [ 'http://wordpress.org', 'URL' ], + [ 'www.wordpress.org', 'URL' ], + [ 'wordpress.org', 'URL' ], + [ 'ftp://wordpress.org', 'URL' ], + [ 'mailto:hello@wordpress.org', 'mailto' ], + [ 'tel:123456789', 'tel' ], + [ '#internal', 'internal' ], + ] )( + 'should display only URL result when current input value is URL-like (e.g. %s)', + async ( searchTerm, type ) => { + const user = userEvent.setup(); - render( ); + render( ); - // Search Input UI. - const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); + // Search Input UI. + const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); - // Simulate searching for a term. - await user.type( searchInput, searchTerm ); + // Simulate searching for a term. + await user.type( searchInput, searchTerm ); - const searchResultElement = within( - await screen.findByRole( 'listbox', { - name: /Search results for.*/, - } ) - ).getByRole( 'option' ); + const searchResultElement = within( + await screen.findByRole( 'listbox', { + name: /Search results for.*/, + } ) + ).getByRole( 'option' ); - expect( searchResultElement ).toBeInTheDocument(); + expect( searchResultElement ).toBeInTheDocument(); - // Should only be the `URL` suggestion. - expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); + // Should only be the `URL` suggestion. + expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); - expect( searchResultElement ).toHaveTextContent( searchTerm ); - expect( searchResultElement ).toHaveTextContent( 'URL' ); - expect( searchResultElement ).toHaveTextContent( - 'Press ENTER to add this link' - ); - } ); + expect( searchResultElement ).toHaveTextContent( searchTerm ); + expect( searchResultElement ).toHaveTextContent( type ); + expect( searchResultElement ).toHaveTextContent( + 'Press ENTER to add this link' + ); + } + ); it( 'should trim search term', async () => { const user = userEvent.setup(); From 3d43b28d8b65623191bd4d2cb7e4a1cc5ae68e62 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 6 Jun 2023 11:37:35 +0100 Subject: [PATCH 10/10] Augment tests for entity search --- .../src/components/link-control/test/index.js | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 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 df7f1d78a197e..a0cf1cd1d23e7 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -439,44 +439,46 @@ describe( 'Searching for a link', () => { expect( screen.queryByRole( 'presentation' ) ).not.toBeInTheDocument(); } ); - it( 'should display only search suggestions (and not URL result type) when current input value is not URL-like', async () => { - const user = userEvent.setup(); - const searchTerm = 'Hello world'; - const firstSuggestion = fauxEntitySuggestions[ 0 ]; + it.each( [ 'With spaces', 'Uppercase', 'lowercase' ] )( + 'should display only search suggestions (and not URL result type) when current input value (e.g. %s) is not URL-like', + async ( searchTerm ) => { + const user = userEvent.setup(); + const firstSuggestion = fauxEntitySuggestions[ 0 ]; - render( ); + render( ); - // Search Input UI. - const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); + // Search Input UI. + const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); - // Simulate searching for a term. - await user.type( searchInput, searchTerm ); + // Simulate searching for a term. + await user.type( searchInput, searchTerm ); - const searchResultElements = within( - await screen.findByRole( 'listbox', { - name: /Search results for.*/, - } ) - ).getAllByRole( 'option' ); + const searchResultElements = within( + await screen.findByRole( 'listbox', { + name: /Search results for.*/, + } ) + ).getAllByRole( 'option' ); - expect( searchResultElements ).toHaveLength( - fauxEntitySuggestions.length - ); + expect( searchResultElements ).toHaveLength( + fauxEntitySuggestions.length + ); - expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); + expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' ); - // Check that a search suggestion shows up corresponding to the data. - expect( searchResultElements[ 0 ] ).toHaveTextContent( - firstSuggestion.title - ); - expect( searchResultElements[ 0 ] ).toHaveTextContent( - firstSuggestion.type - ); + // Check that a search suggestion shows up corresponding to the data. + expect( searchResultElements[ 0 ] ).toHaveTextContent( + firstSuggestion.title + ); + expect( searchResultElements[ 0 ] ).toHaveTextContent( + firstSuggestion.type + ); - // The fallback URL suggestion should not be shown when input is not URL-like. - expect( - searchResultElements[ searchResultElements.length - 1 ] - ).not.toHaveTextContent( 'URL' ); - } ); + // The fallback URL suggestion should not be shown when input is not URL-like. + expect( + searchResultElements[ searchResultElements.length - 1 ] + ).not.toHaveTextContent( 'URL' ); + } + ); it.each( [ [ 'https://wordpress.org', 'URL' ],