Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"wp-content/plugins/unsupported-elasticsearch-version.php": "./tests/e2e/src/wordpress-files/test-plugins/unsupported-elasticsearch-version.php",
"wp-content/plugins/multiple-required-features.php": "./tests/e2e/src/wordpress-files/test-plugins/multiple-required-features.php",
"wp-content/uploads/content-example.xml": "./tests/e2e/src/wordpress-files/test-docs/content-example.xml",
"wp-content/plugins/echo-shortcode.php": "./tests/e2e/src/wordpress-files/test-plugins/echo-shortcode.php"
"wp-content/plugins/echo-shortcode.php": "./tests/e2e/src/wordpress-files/test-plugins/echo-shortcode.php",
"wp-content/plugins/custom-search-form.php": "./tests/e2e/src/wordpress-files/test-plugins/custom-search-form.php"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion assets/js/api-search/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default (state, action) => {
newState.args = { ...newState.args, ...args, offset: 0 };
newState.isOn = true;

if (updateDefaults && args.post_type.length) {
if (updateDefaults && args.post_type?.length) {
newState.argsSchema.post_type.default = args.post_type;
}

Expand Down
9 changes: 4 additions & 5 deletions assets/js/instant-results/apps/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies.
*/
import { useApiSearch } from '../../api-search';
import { facets } from '../config';
import { getPostTypesFromForm } from '../utilities';
import { argsSchema, facets } from '../config';
import { getArgsFromForm } from '../utilities';
import Modal from '../components/common/modal';
import Layout from '../components/layout';

Expand Down Expand Up @@ -59,11 +59,10 @@ export default () => {
return;
}

const { value } = inputRef.current;
const post_type = getPostTypesFromForm(inputRef.current.form);
const args = getArgsFromForm(inputRef.current.form, argsSchema);
const updateDefaults = !facets.some((f) => f.name === 'post_type');

search({ post_type, search: value, updateDefaults });
search({ ...args, updateDefaults });
},
[inputRef, search],
);
Expand Down
75 changes: 63 additions & 12 deletions assets/js/instant-results/utilities.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* WordPress dependencies.
*/
import { applyFilters } from '@wordpress/hooks';

/**
* Internal dependencies.
*/
import { sanitizeArg } from '../api-search/src/utilities';

/**
* Format a date.
*
Expand Down Expand Up @@ -27,21 +37,62 @@ export const formatPrice = (number, options) => {
};

/**
* Get the post types from a search form.
* Get search args from a search form for Instant Results.
*
* @param {HTMLFormElement} form Form element.
* @returns {Array} Post types.
* @param {HTMLFormElement} form Form element.
* @param {object} argsSchema Search args schema.
* @returns {object} Search args.
*/
export const getPostTypesFromForm = (form) => {
const data = new FormData(form);
export const getArgsFromForm = (form, argsSchema) => {
/**
* Filter the map of query variable names to Instant Results arg names.
*
* @filter ep.instantResults.queryVarMap
* @since 5.4.0
*
* @param {object} map Map of WordPress query var names to Instant Results arg names.
* @param {HTMLFormElement} form Form element.
* @param {object} argsSchema Search args schema.
* @returns {object} Map of WordPress query var names to Instant Results arg names.
*/
const QUERY_VAR_MAP = applyFilters(
'ep.instantResults.queryVarMap',
{
s: 'search',
cat: 'tax-category',
tag_id: 'tax-post_tag',
},
{ form, argsSchema },
);

const formData = new FormData(form);
const params = new URLSearchParams();
const formEntries = Array.from(formData.entries());

if (data.has('post_type')) {
return data.getAll('post_type').slice(-1);
}
formEntries.forEach(([key, value]) => {
// Strip trailing [] from array-style field names.
const cleanKey = key.replace(/\[\]$/, '');
// Resolve the EP arg name: explicit map → direct schema match → tax- prefix.
const argName =
QUERY_VAR_MAP[cleanKey] ||
(argsSchema[cleanKey] ? cleanKey : null) ||
(argsSchema[`tax-${cleanKey}`] ? `tax-${cleanKey}` : null);

if (value && argName) {
const existing = params.get(argName);
params.set(argName, existing ? `${existing},${value}` : value);
}
});

if (data.has('post_type[]')) {
return data.getAll('post_type[]');
}
return Object.entries(argsSchema).reduce((args, [arg, options]) => {
const param = params.get(arg);
if (param !== null) {
const value = sanitizeArg(param, options, false);
if (value !== null) {
args[arg] = value;
}
}

return [];
return args;
}, {});
};
70 changes: 65 additions & 5 deletions tests/e2e/src/specs/instant-results.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,20 @@ test.describe('Instant Results Feature', { tag: '@group1' }, () => {
});
};

const addInstantResultFilter = async (page: Page, filterName: string) => {
const addInstantResultFilter = async (
page: Page,
filterName: string,
options?: { keepExisting?: boolean },
) => {
await page.locator('.components-form-token-field__input').focus();
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');

if (!options?.keepExisting) {
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
}

await page.locator('.components-form-token-field__input').fill(filterName);
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Enter');
Expand Down Expand Up @@ -598,6 +606,58 @@ test.describe('Instant Results Feature', { tag: '@group1' }, () => {
'wpCli',
);
});

test('Is possible to search with taxonomies and tags in search form', async ({
loggedInPage,
}) => {
await maybeEnableFeature('instant-results');
await activatePlugin(loggedInPage, 'custom-search-form', 'wpCli');

await goToAdminPage(loggedInPage, 'admin.php?page=elasticpress');
const apiResponsePromise = loggedInPage.waitForResponse(
'**/wp-json/elasticpress/v1/features*',
);

await loggedInPage.getByRole('button', { name: 'Live Search' }).click();
await loggedInPage.getByRole('button', { name: 'Instant Results' }).click();
await addInstantResultFilter(loggedInPage, '(category)');
await addInstantResultFilter(loggedInPage, '(post_tag)', {
keepExisting: true,
});
await loggedInPage.getByRole('button', { name: 'Save changes' }).click();

await apiResponsePromise;

await publishPost(
loggedInPage,
{
title: 'Custom Search Form Page',
content: '[ep_custom_search_form]',
},
true,
);

await expect(loggedInPage.locator('form.searchform')).toBeVisible();
await expect(loggedInPage.locator('form.searchform input[name="s"]')).toBeVisible();
await expect(
loggedInPage.locator('form.searchform .search-tags-checkboxes'),
).toBeVisible();

const responsePromise = instantResultRequestPromise(loggedInPage, 'search=');

await loggedInPage.locator('#cat').selectOption({ label: 'aciform' });
await loggedInPage.locator('form.searchform').getByLabel('edge case').check();
await loggedInPage.locator('form.searchform').getByLabel('categories').check();
await loggedInPage.locator('#searchform #s').click();
await loggedInPage.keyboard.press('Enter');

await expect(loggedInPage.locator('.ep-search-modal')).toBeVisible();
await responsePromise;

await expect(loggedInPage.locator('.ep-search-tokens')).toContainText('aciform');
await expect(loggedInPage.locator('.ep-search-tokens')).toContainText('edge case');
await expect(loggedInPage.locator('.ep-search-tokens')).toContainText('categories');
});
});

test('Is possible to filter the arguments schema', async ({ loggedInPage }) => {
Expand Down
72 changes: 72 additions & 0 deletions tests/e2e/src/wordpress-files/test-plugins/custom-search-form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
/**
* Plugin Name: Custom Search Form
* Description: Custom search form for test purposes.
* Version: 1.0.0
* Author: 10up Inc.
* License: GPLv2 or later
*
* @package ElasticPress_Tests_E2e
*/

/**
* Shortcode for custom search form with category dropdown and tag checkboxes.
*
* @return string HTML output for the search form.
*/
function ep_custom_search_form_shortcode() {
ob_start();
?>
<form role="search" method="get" id="searchform" class="searchform" action="<?php echo esc_url( home_url( '/' ) ); ?>">
<label class="screen-reader-text" for="s"><?php esc_html_e( 'Search for:', 'custom-search-form' ); ?></label>
<input type="search" value="<?php echo esc_attr( get_search_query() ); ?>" name="s" id="s" placeholder="<?php esc_attr_e( 'Search…', 'custom-search-form' ); ?>" />
<?php
wp_dropdown_categories(
[
'show_option_all' => __( 'All Categories', 'custom-search-form' ),
'name' => 'cat',
],
);
$tag_terms = get_terms(
[
'taxonomy' => 'post_tag',
'hide_empty' => true,
],
);
?>
<fieldset class="search-tags-checkboxes">
<legend><?php esc_html_e( 'Filter by Tags:', 'custom-search-form' ); ?></legend>
<?php
foreach ( $tag_terms as $term ) :
?>
<label>
<input type="checkbox" name="custom_tag_id[]" value="<?php echo esc_attr( $term->term_id ); ?>" />
<?php echo esc_html( $term->name ); ?>
</label><br />
<?php endforeach; ?>
</fieldset>
<input type="submit" id="searchsubmit" value="<?php esc_attr_e( 'Search', 'custom-search-form' ); ?>" />
</form>
<?php
return ob_get_clean();
}
add_shortcode( 'ep_custom_search_form', 'ep_custom_search_form_shortcode' );

/**
* Filter the Instant Results query variable map.
*/
add_action(
'wp_footer',
function (): void {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
wp.hooks.addFilter('ep.instantResults.queryVarMap', 'ep-test', (queryVarMap) => {
queryVarMap['custom_tag_id'] = 'tax-post_tag';
return queryVarMap;
});
});
</script>
<?php
}
);
Loading