From 3869e85d29785593a78155e5fc56842d87688ba2 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 13 Nov 2024 16:25:51 -0500 Subject: [PATCH 01/17] add the filter and test --- lib/interactivity-api.php | 21 + .../interactivity/instant-search.spec.ts | 469 ++++++++++++++++++ 2 files changed, 490 insertions(+) create mode 100644 test/e2e/specs/interactivity/instant-search.spec.ts diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index ff68936f054a7e..9483c4894b8782 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -31,3 +31,24 @@ function ( $data ) { } } add_action( 'after_setup_theme', 'gutenberg_register_interactivity_script_module_data_hooks', 20 ); + +function gutenberg_block_core_query_add_url_filtering( $context ) { + if ( empty( $context['queryId'] ) ) { + return $context; + } + + // if the parent block is NOT a Query Loop block, then we bail out + if ( empty( $context['query']['inherit'] ) ) { + return $context; + } + + $search_key = 'query-' . $context['queryId'] . '-s'; + if ( ! isset( $_GET[ $search_key ] ) ) { + return $context; + } + + $context['query']['search'] = sanitize_text_field( $_GET[ $search_key ] ); + + return $context; +} +add_filter( 'render_block_context', 'gutenberg_block_core_query_add_url_filtering' ); diff --git a/test/e2e/specs/interactivity/instant-search.spec.ts b/test/e2e/specs/interactivity/instant-search.spec.ts new file mode 100644 index 00000000000000..9a1f6cba317b1a --- /dev/null +++ b/test/e2e/specs/interactivity/instant-search.spec.ts @@ -0,0 +1,469 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; +/** + * External dependencies + */ +import type { Page } from '@playwright/test'; + +/** + * Go to the next page of the query. + * @param page - The page object. + * @param pageNumber - The page number to navigate to. + * @param args - Query arguments: ['default'] or ['custom', number] + */ +async function goToNextPage( + page: Page, + pageNumber: number, + ...args: [ 'default' ] | [ 'custom', number ] +) { + const [ queryType, queryId ] = args; + await page + .getByTestId( `${ queryType }-query` ) + .getByRole( 'link', { name: 'Next Page' } ) + .click(); + + // Wait for the response + return page.waitForResponse( ( response ) => + queryType === 'default' + ? response.url().includes( `paged=${ pageNumber }` ) || + response.url().includes( `/page/${ pageNumber }/` ) + : response + .url() + .includes( `query-${ queryId }-page=${ pageNumber }` ) + ); +} + +test.describe( 'Instant Search', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.setGutenbergExperiments( [ + 'gutenberg-search-query-block', + ] ); + await requestUtils.deleteAllPosts(); + + // Create test posts + // Make sure to create them last-to-first to avoid flakiness + await requestUtils.createPost( { + title: 'Unique Post', + content: 'This post has unique content.', + status: 'publish', + date_gmt: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 5 + ).toISOString(), + } ); + await requestUtils.createPost( { + title: 'Fourth Test Post', + content: 'This is the fourth test post content.', + status: 'publish', + date_gmt: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 4 + ).toISOString(), + } ); + await requestUtils.createPost( { + title: 'Third Test Post', + content: 'This is the third test post content.', + status: 'publish', + date_gmt: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 3 + ).toISOString(), + } ); + await requestUtils.createPost( { + title: 'Second Test Post', + content: 'This is the second test post content.', + status: 'publish', + date_gmt: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 2 + ).toISOString(), + } ); + await requestUtils.createPost( { + title: 'First Test Post', + content: 'This is the first test post content.', + status: 'publish', + date_gmt: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 1 + ).toISOString(), + } ); + + // Set the Blog pages show at most 2 posts + await requestUtils.updateSiteSettings( { + posts_per_page: 2, + } ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deleteAllPosts(); + await requestUtils.deleteAllPages(); + await requestUtils.deleteAllTemplates( 'wp_template' ); + + // Reset the Blog pages show at most 10 posts + await requestUtils.updateSiteSettings( { + posts_per_page: 10, + } ); + } ); + + test.describe( 'Custom Query', () => { + let pageId: number; + + const queryId = 123; + + test.beforeAll( async ( { requestUtils } ) => { + // Create page with custom query + const { id } = await requestUtils.createPage( { + status: 'publish', + date_gmt: new Date().toISOString(), + title: 'Custom Query', + content: ` + +
+ + + + + + + + + + + + +

No results found.

+ + +
+`, + } ); + + pageId = id; + } ); + + test.beforeEach( async ( { page } ) => { + await page.goto( `/?p=${ pageId }` ); + } ); + + test( 'should update search results without page reload', async ( { + page, + } ) => { + // Check that the first post is shown initially + await expect( + page.getByText( 'First Test Post', { exact: true } ) + ).toBeVisible(); + + // Type in search input and verify results update + await page.locator( 'input[type="search"]' ).fill( 'Unique' ); + await page.waitForResponse( ( response ) => + response.url().includes( `instant-search-${ queryId }=Unique` ) + ); + + // Verify only the unique post is shown + await expect( + page.getByText( 'Unique Post', { exact: true } ) + ).toBeVisible(); + + // Check that there is only one post + const posts = page + .getByTestId( 'custom-query' ) + .getByRole( 'heading', { level: 3 } ); + await expect( posts ).toHaveCount( 1 ); + + // Verify that the other posts are hidden + await expect( + page.getByText( 'First Test Post', { exact: true } ) + ).toBeHidden(); + } ); + + test( 'should update URL with search parameter', async ( { page } ) => { + // Test global query search parameter + await page.locator( 'input[type="search"]' ).fill( 'Test' ); + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ queryId }=Test` ) + ); + + // Clear search and verify parameter is removed + await page.locator( 'input[type="search"]' ).fill( '' ); + await expect( page ).not.toHaveURL( + new RegExp( `instant-search-${ queryId }=` ) + ); + } ); + + test( 'should handle search debouncing', async ( { page } ) => { + let responseCount = 0; + + // Monitor the number of requests + page.on( 'response', ( res ) => { + if ( res.url().includes( `instant-search-${ queryId }=` ) ) { + responseCount++; + } + } ); + + // Type quickly and wait for the response + let responsePromise = page.waitForResponse( ( response ) => { + return ( + response + .url() + .includes( `instant-search-${ queryId }=Test` ) && + response.status() === 200 + ); + } ); + await page + .locator( 'input[type="search"]' ) + .pressSequentially( 'Test', { delay: 100 } ); + await responsePromise; + + // Check that only one request was made + expect( responseCount ).toBe( 1 ); + + // Verify URL is updated after debounce + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ queryId }=Test` ) + ); + + responsePromise = page.waitForResponse( ( response ) => { + return response + .url() + .includes( `instant-search-${ queryId }=Test1234` ); + } ); + // Type again with a large delay and verify that a request is made + // for each character + await page + .locator( 'input[type="search"]' ) + .pressSequentially( '1234', { delay: 500 } ); + await responsePromise; + + // Check that five requests were made (Test, Test1, Test12, Test123, Test1234) + expect( responseCount ).toBe( 5 ); + } ); + + test( 'should reset pagination when searching', async ( { page } ) => { + // Navigate to second page + await page.click( 'a.wp-block-query-pagination-next' ); + + await expect( page ).toHaveURL( + new RegExp( `query-${ queryId }-page=2` ) + ); + + // Search and verify we're back to first page + await page.locator( 'input[type="search"]' ).fill( 'Test' ); + await expect( page ).not.toHaveURL( + new RegExp( `query-${ queryId }-page=2` ) + ); + + // The url should now contain `?paged=1` because we're on the first page + // We cannot remove the `paged` param completely because the pathname + // might contain the `/page/2` suffix so we need to set `paged` to `1` to + // override it. + await expect( page ).toHaveURL( + new RegExp( `query-${ queryId }-page=1` ) + ); + } ); + + test( 'should show no-results block when search has no matches', async ( { + page, + } ) => { + await page + .locator( 'input[type="search"]' ) + .fill( 'NonexistentContent' ); + await page.waitForResponse( ( response ) => + response + .url() + .includes( + `instant-search-${ queryId }=NonexistentContent` + ) + ); + + // Verify no-results block is shown + await expect( page.getByText( 'No results found.' ) ).toBeVisible(); + } ); + + test( 'should update pagination numbers based on search results', async ( { + page, + } ) => { + // Initially should show pagination numbers for 3 pages + await expect( + page.locator( '.wp-block-query-pagination-numbers' ) + ).toBeVisible(); + await expect( + page.getByRole( 'link', { name: '2' } ) + ).toBeVisible(); + await expect( + page.getByRole( 'link', { name: '3' } ) + ).toBeVisible(); + + // Search for unique post + await page.locator( 'input[type="search"]' ).fill( 'Unique' ); + await page.waitForResponse( ( response ) => + response.url().includes( `instant-search-${ queryId }=Unique` ) + ); + + // Pagination numbers should not be visible with single result + await expect( + page.locator( '.wp-block-query-pagination-numbers' ) + ).toBeHidden(); + } ); + } ); + + test.describe( 'Multiple Queries', () => { + const customQueryId = 1234; + + test.beforeAll( async ( { requestUtils } ) => { + // Edit the Home template to include both query types + await requestUtils.deleteAllTemplates( 'wp_template' ); + await requestUtils.createTemplate( 'wp_template', { + slug: 'home', + title: 'Home', + content: ` + +
+ +

Default Query

+ + + + + + + + + + + + + +

No results found.

+ + +
+ + + +
+ +

Custom Query

+ + + + + + + + + + + + + +

No results found.

+ + +
+`, + } ); + } ); + + test.beforeEach( async ( { page } ) => { + await page.goto( '/' ); + } ); + + test( 'should handle searches independently', async ( { page } ) => { + // Get search inputs + const defaultQuerySearch = page.getByLabel( + 'default-instant-search' + ); + + const customQuerySearch = page.getByLabel( + 'custom-instant-search' + ); + + // Search in default query + await defaultQuerySearch.fill( 'Unique' ); + await page.waitForResponse( ( response ) => + response.url().includes( 'instant-search=Unique' ) + ); + + // Verify only default query ONLY shows the unique post + await expect( + page + .getByTestId( 'default-query' ) + .getByText( 'Unique Post', { exact: true } ) + ).toBeVisible(); + + // Verify that the custom query shows exactly 2 posts: First Test Post and Second Test Post + const customQuery = page.getByTestId( 'custom-query' ); + const posts = customQuery.getByRole( 'heading', { level: 3 } ); + await expect( posts ).toHaveCount( 2 ); + await expect( posts ).toContainText( [ + 'First Test Post', + 'Second Test Post', + ] ); + + // Search in custom query + await customQuerySearch.fill( 'Third' ); + await page.waitForResponse( ( response ) => + response + .url() + .includes( `instant-search-${ customQueryId }=Third` ) + ); + + // Verify URL contains both search parameters + await expect( page ).toHaveURL( /instant-search=Unique/ ); + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ customQueryId }=Third` ) + ); + + // Clear default query search + await defaultQuerySearch.fill( '' ); + await expect( page ).not.toHaveURL( /instant-search=/ ); + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ customQueryId }=Third` ) + ); + + // Clear custom query search + await customQuerySearch.fill( '' ); + await expect( page ).not.toHaveURL( + new RegExp( `instant-search-${ customQueryId }=` ) + ); + } ); + + test( 'should handle pagination independently', async ( { page } ) => { + const defaultQuerySearch = page.getByLabel( + 'default-instant-search' + ); + const customQuerySearch = page.getByLabel( + 'custom-instant-search' + ); + + // Navigate to second page in default query + await goToNextPage( page, 2, 'default' ); + + // Navigate to second page in custom query + await goToNextPage( page, 2, 'custom', customQueryId ); + + // Navigate to third page in custom query + await goToNextPage( page, 3, 'custom', customQueryId ); + + // Verify URL contains both pagination parameters + await expect( page ).toHaveURL( /(?:paged=2|\/page\/2\/)/ ); + await expect( page ).toHaveURL( + new RegExp( `query-${ customQueryId }-page=3` ) + ); + + // Search in default query and verify only its pagination resets + await defaultQuerySearch.fill( 'Test' ); + await expect( page ).toHaveURL( /paged=1/ ); + await expect( page ).toHaveURL( + new RegExp( `query-${ customQueryId }-page=3` ) + ); + + // Verify that the + + // Search in custom query and verify only its pagination resets + await customQuerySearch.fill( 'Test' ); + await expect( page ).toHaveURL( /paged=1/ ); + await expect( page ).toHaveURL( + new RegExp( `query-${ customQueryId }-page=1` ) + ); + } ); + } ); +} ); From 4d4cd20a41bf89ef1b86a6863b0e4cd34c6a4c20 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 14 Nov 2024 09:09:38 -0500 Subject: [PATCH 02/17] remove --- lib/interactivity-api.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index 9483c4894b8782..ff68936f054a7e 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -31,24 +31,3 @@ function ( $data ) { } } add_action( 'after_setup_theme', 'gutenberg_register_interactivity_script_module_data_hooks', 20 ); - -function gutenberg_block_core_query_add_url_filtering( $context ) { - if ( empty( $context['queryId'] ) ) { - return $context; - } - - // if the parent block is NOT a Query Loop block, then we bail out - if ( empty( $context['query']['inherit'] ) ) { - return $context; - } - - $search_key = 'query-' . $context['queryId'] . '-s'; - if ( ! isset( $_GET[ $search_key ] ) ) { - return $context; - } - - $context['query']['search'] = sanitize_text_field( $_GET[ $search_key ] ); - - return $context; -} -add_filter( 'render_block_context', 'gutenberg_block_core_query_add_url_filtering' ); From a09e0dd799f43360e31fdfec728ced13253c38c7 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 14 Nov 2024 11:47:57 -0500 Subject: [PATCH 03/17] Create instant search using `render_block_context` filter. --- lib/blocks.php | 29 +++++++ lib/experiments-page.php | 12 +++ packages/block-library/src/search/block.json | 1 + packages/block-library/src/search/index.php | 69 ++++++++++++---- packages/block-library/src/search/view.js | 85 +++++++++++++++++++- 5 files changed, 177 insertions(+), 19 deletions(-) diff --git a/lib/blocks.php b/lib/blocks.php index c3fdb26700c58c..0d485b74c1503e 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -509,3 +509,32 @@ function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) { add_action( 'init', '_gutenberg_footnotes_kses_init' ); add_action( 'set_current_user', '_gutenberg_footnotes_kses_init' ); add_filter( 'force_filtered_html_on_import', '_gutenberg_footnotes_force_filtered_html_on_import_filter', 999 ); + + +function gutenberg_block_core_query_add_url_filtering( $context ) { + + // Make sure it only runs for blocks with a queryId + if ( empty( $context['queryId'] ) ) { + return $context; + } + + // Check if the instant search gutenberg experiment is enabled + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + $instant_search_enabled = $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ); + if ( ! $instant_search_enabled ) { + return $context; + } + + // Get the search key from the URL + $search_key = 'instant-search-' . $context['queryId']; + if ( ! isset( $_GET[ $search_key ] ) ) { + return $context; + } + + // Add the search query to the context, it will be picked up by all the blocks that + // use the `query` context like `post-template` or `query-pagination`. + $context['query']['search'] = sanitize_text_field( $_GET[ $search_key ] ); + + return $context; +} +add_filter( 'render_block_context', 'gutenberg_block_core_query_add_url_filtering', 10, 2 ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 946b68283a3e0b..b7db4b7f795d50 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -187,6 +187,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-search-query-block', + __( 'Instant Search and Query Block', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable instant search functionality of the Search + Query blocks.', 'gutenberg' ), + 'id' => 'gutenberg-search-query-block', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json index c5af5a29d21beb..e7207498125d6e 100644 --- a/packages/block-library/src/search/block.json +++ b/packages/block-library/src/search/block.json @@ -48,6 +48,7 @@ "default": false } }, + "usesContext": [ "enhancedPagination", "query", "queryId" ], "supports": { "align": [ "left", "center", "right" ], "color": { diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index b74daf548a8025..d0046c2963c898 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -16,7 +16,7 @@ * * @return string The search block markup. */ -function render_block_core_search( $attributes ) { +function render_block_core_search( $attributes, $content, $block ) { // Older versions of the Search block defaulted the label and buttonText // attributes to `__( 'Search' )` meaning that many posts contain ``. Support these by defaulting an undefined label and @@ -31,8 +31,8 @@ function render_block_core_search( $attributes ) { $input_id = wp_unique_id( 'wp-block-search__input-' ); $classnames = classnames_for_block_core_search( $attributes ); - $show_label = ! empty( $attributes['showLabel'] ); - $use_icon_button = ! empty( $attributes['buttonUseIcon'] ); + $show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false; + $use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false; $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true; $button_position = $show_button ? $attributes['buttonPosition'] : null; $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array(); @@ -48,6 +48,12 @@ function render_block_core_search( $attributes ) { // This variable is a constant and its value is always false at this moment. // It is defined this way because some values depend on it, in case it changes in the future. $open_by_default = false; + // Check if the block is using the enhanced pagination. + $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; + + // Check if the block is using the instant search experiment. + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + $instant_search_enabled = $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ); $label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] ); $label = new WP_HTML_Tag_Processor( sprintf( '', $inline_styles['label'], $label_inner_html ) ); @@ -90,6 +96,16 @@ function render_block_core_search( $attributes ) { $input->set_attribute( 'aria-hidden', 'true' ); $input->set_attribute( 'tabindex', '-1' ); } + + // Instant search is only available when using the enhanced pagination. + if ( $enhanced_pagination ) { + wp_enqueue_script_module( '@wordpress/block-library/search/view' ); + + if ( $instant_search_enabled ) { + $input->set_attribute( 'data-wp-bind--value', 'context.search' ); + $input->set_attribute( 'data-wp-on-async--input', 'actions.updateSearch' ); + } + } } if ( count( $query_params ) > 0 ) { @@ -163,28 +179,51 @@ function render_block_core_search( $attributes ) { array( 'class' => $classnames ) ); $form_directives = ''; + $form_context = array(); // If it's interactive, add the directives. + if ( $is_expandable_searchfield || ( $enhanced_pagination && $instant_search_enabled ) ) { + $form_directives = 'data-wp-interactive="core/search"'; + } + if ( $is_expandable_searchfield ) { $aria_label_expanded = __( 'Submit Search' ); $aria_label_collapsed = __( 'Expand search field' ); - $form_context = wp_interactivity_data_wp_context( + $form_context = array( + 'isSearchInputInitiallyVisible' => $open_by_default, + 'inputId' => $input_id, + 'ariaLabelExpanded' => $aria_label_expanded, + 'ariaLabelCollapsed' => $aria_label_collapsed, + ); + $form_directives .= + 'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" + data-wp-on-async--keydown="actions.handleSearchKeydown" + data-wp-on-async--focusout="actions.handleSearchFocusout" + '; + } + + if ( $enhanced_pagination && $instant_search_enabled ) { + $is_inherited = isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] && ! empty( $block->context['queryId'] ); + $search = ''; + + if ( $is_inherited ) { + $search = empty( $_GET['instant-search'] ) ? '' : sanitize_text_field( $_GET['instant-search'] ); + } else { + $search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? '' : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] ); + } + + $form_context = array_merge( + $form_context, array( - 'isSearchInputVisible' => $open_by_default, - 'inputId' => $input_id, - 'ariaLabelExpanded' => $aria_label_expanded, - 'ariaLabelCollapsed' => $aria_label_collapsed, + 'search' => $search, + 'isInherited' => $is_inherited, + 'queryId' => $block->context['queryId'], ) ); - $form_directives = ' - data-wp-interactive="core/search"' - . $form_context . - 'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" - data-wp-on-async--keydown="actions.handleSearchKeydown" - data-wp-on-async--focusout="actions.handleSearchFocusout" - '; } + $form_directives .= wp_interactivity_data_wp_context( $form_context ); + return sprintf( '
%4s
', esc_url( home_url( '/' ) ), diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js index 0e4c462a2e3213..b3494c6f1a8364 100644 --- a/packages/block-library/src/search/view.js +++ b/packages/block-library/src/search/view.js @@ -3,7 +3,10 @@ */ import { store, getContext, getElement } from '@wordpress/interactivity'; -const { actions } = store( +/** @type {( () => void ) | null} */ +let supersedePreviousSearch = null; + +const { state, actions } = store( 'core/search', { state: { @@ -29,14 +32,25 @@ const { actions } = store( const { isSearchInputVisible } = getContext(); return isSearchInputVisible ? '0' : '-1'; }, + get isSearchInputVisible() { + const ctx = getContext(); + + // `ctx.isSearchInputVisible` is a client-side-only context value, so + // if it's not set, it means that it's an initial page load, so we need + // to return the value of `ctx.isSearchInputInitiallyVisible`. + if ( typeof ctx.isSearchInputVisible === 'undefined' ) { + return ctx.isSearchInputInitiallyVisible; + } + return ctx.isSearchInputVisible; + }, }, actions: { openSearchInput( event ) { - const ctx = getContext(); - const { ref } = getElement(); - if ( ! ctx.isSearchInputVisible ) { + if ( ! state.isSearchInputVisible ) { event.preventDefault(); + const ctx = getContext(); ctx.isSearchInputVisible = true; + const { ref } = getElement(); ref.parentElement.querySelector( 'input' ).focus(); } }, @@ -66,6 +80,69 @@ const { actions } = store( actions.closeSearchInput(); } }, + *updateSearch( e ) { + const { value } = e.target; + + const ctx = getContext(); + + // Don't navigate if the search didn't really change. + if ( value === ctx.search ) { + return; + } + + ctx.search = value; + + // Debounce the search by 300ms to prevent multiple navigations. + supersedePreviousSearch?.(); + const { promise, resolve, reject } = Promise.withResolvers(); + const timeout = setTimeout( resolve, 300 ); + supersedePreviousSearch = () => { + clearTimeout( timeout ); + reject(); + }; + try { + yield promise; + } catch { + return; + } + + const url = new URL( window.location.href ); + + if ( value ) { + if ( ctx.isInherited ) { + url.searchParams.set( 'instant-search', value ); + + // Make sure we reset the pagination. + url.searchParams.set( 'paged', '1' ); + } else { + // Set the instant-search parameter using the query ID and search value + const queryId = ctx.queryId; + url.searchParams.set( + `instant-search-${ queryId }`, + value + ); + + // Make sure we reset the pagination. + url.searchParams.set( `query-${ queryId }-page`, '1' ); + } + } else if ( ctx.isInherited ) { + // Reset global search for inherited queries + url.searchParams.delete( 'instant-search' ); + url.searchParams.delete( 'paged' ); + } else { + // Reset specific search for non-inherited queries + url.searchParams.delete( + `instant-search-${ ctx.queryId }` + ); + url.searchParams.delete( `query-${ ctx.queryId }-page` ); + } + + const { actions: routerActions } = yield import( + '@wordpress/interactivity-router' + ); + + routerActions.navigate( url.href ); + }, }, }, { lock: true } From 55a41d8c384e046d5da03473d3567ecb07c03feb Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 14 Nov 2024 18:02:29 -0500 Subject: [PATCH 04/17] Remove handling of inheritd query from `search/index.php` --- packages/block-library/src/search/index.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index d0046c2963c898..d13b62f51a8e0b 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -203,21 +203,14 @@ function render_block_core_search( $attributes, $content, $block ) { } if ( $enhanced_pagination && $instant_search_enabled ) { - $is_inherited = isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] && ! empty( $block->context['queryId'] ); - $search = ''; - if ( $is_inherited ) { - $search = empty( $_GET['instant-search'] ) ? '' : sanitize_text_field( $_GET['instant-search'] ); - } else { - $search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? '' : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] ); - } + $search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? '' : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] ); $form_context = array_merge( $form_context, array( - 'search' => $search, - 'isInherited' => $is_inherited, - 'queryId' => $block->context['queryId'], + 'search' => $search, + 'queryId' => $block->context['queryId'], ) ); } From 4baafc42cee5beb24bc57941b44623b23a479c4e Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 14 Nov 2024 18:49:21 -0500 Subject: [PATCH 05/17] Handle case when query is defined in block context in DB. --- packages/block-library/src/search/index.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index d13b62f51a8e0b..a3e1d1ba39b772 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -204,7 +204,15 @@ function render_block_core_search( $attributes, $content, $block ) { if ( $enhanced_pagination && $instant_search_enabled ) { - $search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? '' : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] ); + $search = ''; + + // If the query is defined in the block context, use it + if ( isset( $block->context['query']['search'] ) && '' !== $block->context['query']['search'] ) { + $search = $block->context['query']['search']; + } + + // If the query is defined in the URL, it overrides the block context value if defined + $search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? $search : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] ); $form_context = array_merge( $form_context, From 949e8c23f3db81a82cbc86e05e4e4943f6d2eab8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 18 Nov 2024 17:58:47 -0500 Subject: [PATCH 06/17] Guard against queryId being undefined in block context --- packages/block-library/src/search/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index a3e1d1ba39b772..e407fa04a46169 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -202,7 +202,7 @@ function render_block_core_search( $attributes, $content, $block ) { '; } - if ( $enhanced_pagination && $instant_search_enabled ) { + if ( $enhanced_pagination && $instant_search_enabled && isset( $block->context['queryId'] ) ) { $search = ''; From f4e2a939e6bf59072d4f4889a56cfa4c02d8cee4 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 19 Nov 2024 18:28:20 -0500 Subject: [PATCH 07/17] Fix the e2e test suite --- .../interactivity/instant-search.spec.ts | 154 ++++++++++-------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/test/e2e/specs/interactivity/instant-search.spec.ts b/test/e2e/specs/interactivity/instant-search.spec.ts index 9a1f6cba317b1a..b766dae1b576ec 100644 --- a/test/e2e/specs/interactivity/instant-search.spec.ts +++ b/test/e2e/specs/interactivity/instant-search.spec.ts @@ -11,27 +11,23 @@ import type { Page } from '@playwright/test'; * Go to the next page of the query. * @param page - The page object. * @param pageNumber - The page number to navigate to. - * @param args - Query arguments: ['default'] or ['custom', number] + * @param testId - The test ID of the query. + * @param queryId - The query ID. */ async function goToNextPage( page: Page, pageNumber: number, - ...args: [ 'default' ] | [ 'custom', number ] + testId: string, + queryId: number ) { - const [ queryType, queryId ] = args; await page - .getByTestId( `${ queryType }-query` ) + .getByTestId( testId ) .getByRole( 'link', { name: 'Next Page' } ) .click(); // Wait for the response return page.waitForResponse( ( response ) => - queryType === 'default' - ? response.url().includes( `paged=${ pageNumber }` ) || - response.url().includes( `/page/${ pageNumber }/` ) - : response - .url() - .includes( `query-${ queryId }-page=${ pageNumber }` ) + response.url().includes( `query-${ queryId }-page=${ pageNumber }` ) ); } @@ -305,21 +301,22 @@ test.describe( 'Instant Search', () => { } ); test.describe( 'Multiple Queries', () => { - const customQueryId = 1234; + const firstQueryId = 1234; + const secondQueryId = 5678; test.beforeAll( async ( { requestUtils } ) => { - // Edit the Home template to include both query types + // Edit the Home template to include two custom queries await requestUtils.deleteAllTemplates( 'wp_template' ); await requestUtils.createTemplate( 'wp_template', { slug: 'home', title: 'Home', content: ` - -
+ +
-

Default Query

+

First Query

- + @@ -337,12 +334,12 @@ test.describe( 'Instant Search', () => {
- -
+ +
-

Custom Query

+

Second Query

- + @@ -368,101 +365,116 @@ test.describe( 'Instant Search', () => { test( 'should handle searches independently', async ( { page } ) => { // Get search inputs - const defaultQuerySearch = page.getByLabel( - 'default-instant-search' - ); - - const customQuerySearch = page.getByLabel( - 'custom-instant-search' - ); + const firstQuerySearch = page.getByLabel( '1st-instant-search' ); + const secondQuerySearch = page.getByLabel( '2nd-instant-search' ); - // Search in default query - await defaultQuerySearch.fill( 'Unique' ); + // Search in first query + await firstQuerySearch.fill( 'Unique' ); await page.waitForResponse( ( response ) => - response.url().includes( 'instant-search=Unique' ) + response + .url() + .includes( `instant-search-${ firstQueryId }=Unique` ) ); - // Verify only default query ONLY shows the unique post + // Verify first query ONLY shows the unique post await expect( page - .getByTestId( 'default-query' ) + .getByTestId( 'first-query' ) .getByText( 'Unique Post', { exact: true } ) ).toBeVisible(); - // Verify that the custom query shows exactly 2 posts: First Test Post and Second Test Post - const customQuery = page.getByTestId( 'custom-query' ); - const posts = customQuery.getByRole( 'heading', { level: 3 } ); + // Verify that the second query shows exactly 2 posts: First Test Post and Second Test Post + const secondQuery = page.getByTestId( 'second-query' ); + const posts = secondQuery.getByRole( 'heading', { level: 3 } ); await expect( posts ).toHaveCount( 2 ); await expect( posts ).toContainText( [ 'First Test Post', 'Second Test Post', ] ); - // Search in custom query - await customQuerySearch.fill( 'Third' ); + // Search in second query + await secondQuerySearch.fill( 'Third' ); await page.waitForResponse( ( response ) => response .url() - .includes( `instant-search-${ customQueryId }=Third` ) + .includes( `instant-search-${ secondQueryId }=Third` ) ); // Verify URL contains both search parameters - await expect( page ).toHaveURL( /instant-search=Unique/ ); await expect( page ).toHaveURL( - new RegExp( `instant-search-${ customQueryId }=Third` ) + new RegExp( `instant-search-${ firstQueryId }=Unique` ) ); + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ secondQueryId }=Third` ) + ); + + // Verify that the first query has only one post which is the "Unique" post + const firstQueryPosts = page + .getByTestId( 'first-query' ) + .getByRole( 'heading', { level: 3 } ); + await expect( firstQueryPosts ).toHaveCount( 1 ); + await expect( firstQueryPosts ).toContainText( 'Unique Post' ); + + // Verify that the second query has only one post which is the "Third Test Post" + const secondQueryPosts = page + .getByTestId( 'second-query' ) + .getByRole( 'heading', { level: 3 } ); + await expect( secondQueryPosts ).toHaveCount( 1 ); + await expect( secondQueryPosts ).toContainText( 'Third Test Post' ); - // Clear default query search - await defaultQuerySearch.fill( '' ); - await expect( page ).not.toHaveURL( /instant-search=/ ); + // Clear first query search + await firstQuerySearch.fill( '' ); + await expect( page ).not.toHaveURL( + new RegExp( `instant-search-${ firstQueryId }=` ) + ); await expect( page ).toHaveURL( - new RegExp( `instant-search-${ customQueryId }=Third` ) + new RegExp( `instant-search-${ secondQueryId }=Third` ) ); - // Clear custom query search - await customQuerySearch.fill( '' ); + // Clear second query search + await secondQuerySearch.fill( '' ); await expect( page ).not.toHaveURL( - new RegExp( `instant-search-${ customQueryId }=` ) + new RegExp( `instant-search-${ secondQueryId }=` ) ); } ); test( 'should handle pagination independently', async ( { page } ) => { - const defaultQuerySearch = page.getByLabel( - 'default-instant-search' - ); - const customQuerySearch = page.getByLabel( - 'custom-instant-search' - ); + const firstQuerySearch = page.getByLabel( '1st-instant-search' ); + const secondQuerySearch = page.getByLabel( '2nd-instant-search' ); - // Navigate to second page in default query - await goToNextPage( page, 2, 'default' ); + // Navigate to second page in first query + await goToNextPage( page, 2, 'first-query', firstQueryId ); - // Navigate to second page in custom query - await goToNextPage( page, 2, 'custom', customQueryId ); + // Navigate to second page in second query + await goToNextPage( page, 2, 'second-query', secondQueryId ); - // Navigate to third page in custom query - await goToNextPage( page, 3, 'custom', customQueryId ); + // Navigate to third page in second query + await goToNextPage( page, 3, 'second-query', secondQueryId ); // Verify URL contains both pagination parameters - await expect( page ).toHaveURL( /(?:paged=2|\/page\/2\/)/ ); await expect( page ).toHaveURL( - new RegExp( `query-${ customQueryId }-page=3` ) + new RegExp( `query-${ firstQueryId }-page=2` ) ); - - // Search in default query and verify only its pagination resets - await defaultQuerySearch.fill( 'Test' ); - await expect( page ).toHaveURL( /paged=1/ ); await expect( page ).toHaveURL( - new RegExp( `query-${ customQueryId }-page=3` ) + new RegExp( `query-${ secondQueryId }-page=3` ) ); - // Verify that the + // Search in first query and verify only its pagination resets + await firstQuerySearch.fill( 'Test' ); + await expect( page ).toHaveURL( + new RegExp( `query-${ firstQueryId }-page=1` ) + ); + await expect( page ).toHaveURL( + new RegExp( `query-${ secondQueryId }-page=3` ) + ); - // Search in custom query and verify only its pagination resets - await customQuerySearch.fill( 'Test' ); - await expect( page ).toHaveURL( /paged=1/ ); + // Search in second query and verify only its pagination resets + await secondQuerySearch.fill( 'Test' ); + await expect( page ).toHaveURL( + new RegExp( `query-${ firstQueryId }-page=1` ) + ); await expect( page ).toHaveURL( - new RegExp( `query-${ customQueryId }-page=1` ) + new RegExp( `query-${ secondQueryId }-page=1` ) ); } ); } ); From 5931b6d16cae1924426962fb225dbf43776e6e45 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 20 Nov 2024 12:54:38 -0500 Subject: [PATCH 08/17] Move the filter to `/experimental` folder. --- lib/blocks.php | 29 ----------------------------- lib/experimental/blocks.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/blocks.php b/lib/blocks.php index 0d485b74c1503e..c3fdb26700c58c 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -509,32 +509,3 @@ function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) { add_action( 'init', '_gutenberg_footnotes_kses_init' ); add_action( 'set_current_user', '_gutenberg_footnotes_kses_init' ); add_filter( 'force_filtered_html_on_import', '_gutenberg_footnotes_force_filtered_html_on_import_filter', 999 ); - - -function gutenberg_block_core_query_add_url_filtering( $context ) { - - // Make sure it only runs for blocks with a queryId - if ( empty( $context['queryId'] ) ) { - return $context; - } - - // Check if the instant search gutenberg experiment is enabled - $gutenberg_experiments = get_option( 'gutenberg-experiments' ); - $instant_search_enabled = $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ); - if ( ! $instant_search_enabled ) { - return $context; - } - - // Get the search key from the URL - $search_key = 'instant-search-' . $context['queryId']; - if ( ! isset( $_GET[ $search_key ] ) ) { - return $context; - } - - // Add the search query to the context, it will be picked up by all the blocks that - // use the `query` context like `post-template` or `query-pagination`. - $context['query']['search'] = sanitize_text_field( $_GET[ $search_key ] ); - - return $context; -} -add_filter( 'render_block_context', 'gutenberg_block_core_query_add_url_filtering', 10, 2 ); diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 68113276ec1c06..3bf7a53f1c2de7 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -117,3 +117,37 @@ function gutenberg_register_block_style( $block_name, $style_properties ) { return $result; } + +/** + * Adds the search query to the context if the instant search gutenberg experiment is enabled. + * + * @param array $context The block context. + * @return array The block context. + */ +function gutenberg_block_core_query_add_url_filtering( $context ) { + + // Make sure it only runs for blocks with a queryId + if ( empty( $context['queryId'] ) ) { + return $context; + } + + // Check if the instant search gutenberg experiment is enabled + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + $instant_search_enabled = $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ); + if ( ! $instant_search_enabled ) { + return $context; + } + + // Get the search key from the URL + $search_key = 'instant-search-' . $context['queryId']; + if ( ! isset( $_GET[ $search_key ] ) ) { + return $context; + } + + // Add the search query to the context, it will be picked up by all the blocks that + // use the `query` context like `post-template` or `query-pagination`. + $context['query']['search'] = sanitize_text_field( $_GET[ $search_key ] ); + + return $context; +} +add_filter( 'render_block_context', 'gutenberg_block_core_query_add_url_filtering', 10, 2 ); From 997f8c099cf1c477a32a44e1d60a1dfd6c28df6c Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 20 Nov 2024 16:30:56 -0500 Subject: [PATCH 09/17] Add an e2e test case if query.search attribute is present --- .../interactivity/instant-search.spec.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/e2e/specs/interactivity/instant-search.spec.ts b/test/e2e/specs/interactivity/instant-search.spec.ts index b766dae1b576ec..8b83d4ca0c869c 100644 --- a/test/e2e/specs/interactivity/instant-search.spec.ts +++ b/test/e2e/specs/interactivity/instant-search.spec.ts @@ -298,6 +298,75 @@ test.describe( 'Instant Search', () => { page.locator( '.wp-block-query-pagination-numbers' ) ).toBeHidden(); } ); + + test( 'should handle pre-defined search from query attributes', async ( { + requestUtils, + page, + } ) => { + // Create page with custom query that includes a search parameter + const { id } = await requestUtils.createPage( { + status: 'publish', + title: 'Query with Search', + content: ` + +
+ + + + + + + + + + + + +

No results found.

+ + +
+`, + } ); + + // Navigate to the page + await page.goto( `/?p=${ id }` ); + + // Verify the search input has the initial value + await expect( page.locator( 'input[type="search"]' ) ).toHaveValue( + 'Unique' + ); + + // Verify only the unique post is shown + await expect( + page.getByText( 'Unique Post', { exact: true } ) + ).toBeVisible(); + const posts = page + .getByTestId( 'query-with-search' ) + .getByRole( 'heading', { level: 3 } ); + await expect( posts ).toHaveCount( 1 ); + + // Verify URL does not contain the instant-search parameter + await expect( page ).not.toHaveURL( + new RegExp( `instant-search-${ queryId }=` ) + ); + + // Type new search term and verify normal instant search behavior + await page.locator( 'input[type="search"]' ).fill( 'Test' ); + await page.waitForResponse( ( response ) => + response.url().includes( `instant-search-${ queryId }=Test` ) + ); + + // Verify URL now contains the instant-search parameter + await expect( page ).toHaveURL( + new RegExp( `instant-search-${ queryId }=Test` ) + ); + + // Verify search results update + await expect( + page.getByText( 'First Test Post', { exact: true } ) + ).toBeVisible(); + } ); } ); test.describe( 'Multiple Queries', () => { From 0f57df0dee6a88814cd5d798993b6d5db431cb71 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 20 Nov 2024 17:44:31 -0500 Subject: [PATCH 10/17] Remove the search button when instant search is enabled --- packages/block-library/src/search/edit.js | 71 +++++++++++++-------- packages/block-library/src/search/index.php | 15 +++-- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index f193c04e2493aa..fd039a4212b2d4 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -67,6 +67,7 @@ export default function SearchEdit( { toggleSelection, isSelected, clientId, + context, } ) { const { label, @@ -82,6 +83,16 @@ export default function SearchEdit( { style, } = attributes; + const isEnhancedPagination = context?.enhancedPagination; + + useEffect( () => { + if ( isEnhancedPagination ) { + setAttributes( { metadata: { name: 'Instant Search' } } ); + } else { + setAttributes( { metadata: { name: label } } ); + } + }, [ isEnhancedPagination, setAttributes, label ] ); + const wasJustInsertedIntoNavigationBlock = useSelect( ( select ) => { const { getBlockParentsByBlockName, wasBlockJustInserted } = @@ -385,24 +396,28 @@ export default function SearchEdit( { } } className={ showLabel ? 'is-pressed' : undefined } /> - - { ! hasNoButton && ( - { - setAttributes( { - buttonUseIcon: ! buttonUseIcon, - } ); - } } - className={ - buttonUseIcon ? 'is-pressed' : undefined - } - /> + { ! isEnhancedPagination && ( + <> + + { ! hasNoButton && ( + { + setAttributes( { + buttonUseIcon: ! buttonUseIcon, + } ); + } } + className={ + buttonUseIcon ? 'is-pressed' : undefined + } + /> + ) } + ) } @@ -596,16 +611,22 @@ export default function SearchEdit( { } } showHandle={ isSelected } > - { ( isButtonPositionInside || - isButtonPositionOutside || - hasOnlyButton ) && ( + { isEnhancedPagination ? ( + renderTextField() + ) : ( <> - { renderTextField() } - { renderButton() } + { ( isButtonPositionInside || + isButtonPositionOutside || + hasOnlyButton ) && ( + <> + { renderTextField() } + { renderButton() } + + ) } + + { hasNoButton && renderTextField() } ) } - - { hasNoButton && renderTextField() }
); diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index e407fa04a46169..c0eb88a528d5d1 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -29,11 +29,16 @@ function render_block_core_search( $attributes, $content, $block ) { ) ); - $input_id = wp_unique_id( 'wp-block-search__input-' ); - $classnames = classnames_for_block_core_search( $attributes ); - $show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false; - $use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false; - $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true; + $input_id = wp_unique_id( 'wp-block-search__input-' ); + $classnames = classnames_for_block_core_search( $attributes ); + $show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false; + $use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false; + $show_button = true; + if ( isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'] ) { + $show_button = false; + } elseif ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) { + $show_button = false; + } $button_position = $show_button ? $attributes['buttonPosition'] : null; $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array(); $button = ''; From 157a39551e2052157014676c3a261ae0b746370a Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 21 Nov 2024 15:42:36 -0500 Subject: [PATCH 11/17] Do not delete pages and templates in e2e tests --- test/e2e/specs/interactivity/instant-search.spec.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/e2e/specs/interactivity/instant-search.spec.ts b/test/e2e/specs/interactivity/instant-search.spec.ts index 8b83d4ca0c869c..e1eaafd2eeb280 100644 --- a/test/e2e/specs/interactivity/instant-search.spec.ts +++ b/test/e2e/specs/interactivity/instant-search.spec.ts @@ -90,13 +90,7 @@ test.describe( 'Instant Search', () => { test.afterAll( async ( { requestUtils } ) => { await requestUtils.deleteAllPosts(); - await requestUtils.deleteAllPages(); - await requestUtils.deleteAllTemplates( 'wp_template' ); - - // Reset the Blog pages show at most 10 posts - await requestUtils.updateSiteSettings( { - posts_per_page: 10, - } ); + await requestUtils.activateTheme( 'twentytwentyone' ); } ); test.describe( 'Custom Query', () => { @@ -375,9 +369,8 @@ test.describe( 'Instant Search', () => { test.beforeAll( async ( { requestUtils } ) => { // Edit the Home template to include two custom queries - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.createTemplate( 'wp_template', { - slug: 'home', + await requestUtils.createPage( { + status: 'publish', title: 'Home', content: ` From f6d39ad22930b43f7acfda965269cf459d18a369 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 21 Nov 2024 15:45:36 -0500 Subject: [PATCH 12/17] Set the pageId for Multiple Queries tests --- test/e2e/specs/interactivity/instant-search.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/interactivity/instant-search.spec.ts b/test/e2e/specs/interactivity/instant-search.spec.ts index e1eaafd2eeb280..19362ad27d41b6 100644 --- a/test/e2e/specs/interactivity/instant-search.spec.ts +++ b/test/e2e/specs/interactivity/instant-search.spec.ts @@ -364,12 +364,14 @@ test.describe( 'Instant Search', () => { } ); test.describe( 'Multiple Queries', () => { + let pageId: number; + const firstQueryId = 1234; const secondQueryId = 5678; test.beforeAll( async ( { requestUtils } ) => { // Edit the Home template to include two custom queries - await requestUtils.createPage( { + const { id } = await requestUtils.createPage( { status: 'publish', title: 'Home', content: ` @@ -419,10 +421,12 @@ test.describe( 'Instant Search', () => {
`, } ); + + pageId = id; } ); test.beforeEach( async ( { page } ) => { - await page.goto( '/' ); + await page.goto( `/?p=${ pageId }` ); } ); test( 'should handle searches independently', async ( { page } ) => { From 3f29890d55791e519eebab563c0422c817064b89 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 25 Nov 2024 12:56:42 -0500 Subject: [PATCH 13/17] Fix the block name via metadata when Seach is instant. --- packages/block-library/src/search/edit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index fd039a4212b2d4..69aba4416b7a93 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -87,9 +87,12 @@ export default function SearchEdit( { useEffect( () => { if ( isEnhancedPagination ) { + // Add the name to the metadata setAttributes( { metadata: { name: 'Instant Search' } } ); } else { - setAttributes( { metadata: { name: label } } ); + // Remove the name from the metadata + const { name, ...metadata } = attributes.metadata || {}; + setAttributes( { metadata } ); } }, [ isEnhancedPagination, setAttributes, label ] ); From db5f96531203f01d6be786fe90a9c4b500024b5d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 26 Nov 2024 12:44:46 -0500 Subject: [PATCH 14/17] Remove stuff related to Default queries from `view.js` --- packages/block-library/src/search/view.js | 27 +++++++---------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js index b3494c6f1a8364..763cabbf714efc 100644 --- a/packages/block-library/src/search/view.js +++ b/packages/block-library/src/search/view.js @@ -109,26 +109,15 @@ const { state, actions } = store( const url = new URL( window.location.href ); if ( value ) { - if ( ctx.isInherited ) { - url.searchParams.set( 'instant-search', value ); - - // Make sure we reset the pagination. - url.searchParams.set( 'paged', '1' ); - } else { - // Set the instant-search parameter using the query ID and search value - const queryId = ctx.queryId; - url.searchParams.set( - `instant-search-${ queryId }`, - value - ); + // Set the instant-search parameter using the query ID and search value + const queryId = ctx.queryId; + url.searchParams.set( + `instant-search-${ queryId }`, + value + ); - // Make sure we reset the pagination. - url.searchParams.set( `query-${ queryId }-page`, '1' ); - } - } else if ( ctx.isInherited ) { - // Reset global search for inherited queries - url.searchParams.delete( 'instant-search' ); - url.searchParams.delete( 'paged' ); + // Make sure we reset the pagination. + url.searchParams.set( `query-${ queryId }-page`, '1' ); } else { // Reset specific search for non-inherited queries url.searchParams.delete( From 4b20168697f52573ed96e639315003596aca2ce0 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 26 Nov 2024 20:12:25 -0500 Subject: [PATCH 15/17] Add `attributes.metadata` to useEffect dependency array --- packages/block-library/src/search/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 69aba4416b7a93..cb9bcf2709484b 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -94,7 +94,7 @@ export default function SearchEdit( { const { name, ...metadata } = attributes.metadata || {}; setAttributes( { metadata } ); } - }, [ isEnhancedPagination, setAttributes, label ] ); + }, [ isEnhancedPagination, setAttributes, label, attributes.metadata ] ); const wasJustInsertedIntoNavigationBlock = useSelect( ( select ) => { From 2204ed81aea64fc2ab8a3a21c8a19e94a54a157f Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Fri, 29 Nov 2024 18:36:48 +0000 Subject: [PATCH 16/17] Remove `attributes.metadata` & label from dependency array --- packages/block-library/src/search/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index cb9bcf2709484b..9da028f338fab3 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -94,7 +94,7 @@ export default function SearchEdit( { const { name, ...metadata } = attributes.metadata || {}; setAttributes( { metadata } ); } - }, [ isEnhancedPagination, setAttributes, label, attributes.metadata ] ); + }, [ isEnhancedPagination, setAttributes ] ); const wasJustInsertedIntoNavigationBlock = useSelect( ( select ) => { From 40ea84d5dd6083be378ebaa4aacbbeca0e853020 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 3 Dec 2024 17:39:50 +0000 Subject: [PATCH 17/17] Don't use `Promise.withResolvers()` in search block's view.js --- packages/block-library/src/search/view.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js index 763cabbf714efc..d280d355322afa 100644 --- a/packages/block-library/src/search/view.js +++ b/packages/block-library/src/search/view.js @@ -94,7 +94,11 @@ const { state, actions } = store( // Debounce the search by 300ms to prevent multiple navigations. supersedePreviousSearch?.(); - const { promise, resolve, reject } = Promise.withResolvers(); + let resolve, reject; + const promise = new Promise( ( res, rej ) => { + resolve = res; + reject = rej; + } ); const timeout = setTimeout( resolve, 300 ); supersedePreviousSearch = () => { clearTimeout( timeout );