diff --git a/.wp-env.json b/.wp-env.json index 9fcd523a6a..a03d020903 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -47,7 +47,8 @@ "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/simulate-setting-dependency.php": "./tests/e2e/src/wordpress-files/test-plugins/simulate-setting-dependency.php" + "wp-content/plugins/enable-did-you-mean-block.php": "./tests/e2e/src/wordpress-files/test-plugins/enable-did-you-mean-block.php", + "wp-content/plugins/simulate-setting-dependency.php": "./tests/e2e/src/wordpress-files/test-plugins/simulate-setting-dependency.php" } } } diff --git a/assets/js/blocks/did-you-mean/Edit.js b/assets/js/blocks/did-you-mean/Edit.js new file mode 100644 index 0000000000..70a87a02b0 --- /dev/null +++ b/assets/js/blocks/did-you-mean/Edit.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies. + */ +import { useBlockProps } from '@wordpress/block-editor'; +import { createInterpolateElement } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Edit component. + * + * @returns {Function} Component. + */ +const Edit = () => { + const blockProps = useBlockProps({ + className: 'wp-block-elasticpress-did-you-mean', + }); + + return ( +
+ {createInterpolateElement(__('Did you mean Hello?', 'elasticpress'), { + a: , // eslint-disable-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label, jsx-a11y/anchor-is-valid + })} +
+ ); +}; + +export default Edit; diff --git a/assets/js/blocks/did-you-mean/block.json b/assets/js/blocks/did-you-mean/block.json new file mode 100644 index 0000000000..0edc395cdd --- /dev/null +++ b/assets/js/blocks/did-you-mean/block.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "elasticpress/did-you-mean", + "title": "Did You Mean", + "category": "elasticpress", + "description": "Display ElasticPress spelling suggestions for search queries.", + "textdomain": "elasticpress", + "editorScript": "ep-did-you-mean-block-script" +} diff --git a/assets/js/blocks/did-you-mean/index.js b/assets/js/blocks/did-you-mean/index.js new file mode 100644 index 0000000000..7d30205142 --- /dev/null +++ b/assets/js/blocks/did-you-mean/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies. + */ +import { registerBlockType } from '@wordpress/blocks'; +import { search } from '@wordpress/icons'; + +/** + * Internal dependencies. + */ +import Edit from './Edit'; +import { name } from './block.json'; + +registerBlockType(name, { + icon: search, + edit: () => , + save: () => null, +}); diff --git a/includes/classes/Feature/DidYouMean/DidYouMean.php b/includes/classes/Feature/DidYouMean/DidYouMean.php index 8c084d990f..649d4a187f 100644 --- a/includes/classes/Feature/DidYouMean/DidYouMean.php +++ b/includes/classes/Feature/DidYouMean/DidYouMean.php @@ -8,7 +8,7 @@ namespace ElasticPress\Feature\DidYouMean; -use ElasticPress\{Elasticsearch, Feature, FeatureRequirementsStatus, Features }; +use ElasticPress\{Elasticsearch, Feature, FeatureRequirementsStatus, Utils }; /** * Did You Mean feature class. @@ -61,6 +61,91 @@ public function setup() { add_filter( 'ep_integrate_search_queries', [ $this, 'set_ep_suggestion' ], 10, 2 ); add_action( 'template_redirect', [ $this, 'automatically_redirect_user' ] ); add_action( 'ep_suggestions', [ $this, 'the_output' ] ); + + if ( $this->is_block_enabled_in_editor() ) { + add_action( 'init', [ $this, 'register_block' ] ); + } + } + + /** + * Check if Did You Mean block should be enabled in the editor. + * + * @since 5.4.0 + * @return bool + */ + protected function is_block_enabled_in_editor(): bool { + global $pagenow; + + $in_editor = in_array( $pagenow, [ 'post-new.php', 'post.php' ], true ); + + /** + * Filter if Did You Mean block should be enabled in the post editor. + * + * @since 5.4.0 + * @hook ep_did_you_mean_enabled_in_editor + * @param {bool} $enabled If enabled or not. + * @return {bool} If enabled or not. + */ + return ! ( $in_editor && ! apply_filters( 'ep_did_you_mean_enabled_in_editor', false ) ); + } + + /** + * Register Did You Mean block. + * + * @return void + * @since 5.4.0 + */ + public function register_block(): void { + /** + * Registering it here so translation works + * + * @see https://core.trac.wordpress.org/ticket/54797#comment:20 + */ + wp_register_script( + 'ep-did-you-mean-block-script', + EP_URL . 'dist/js/did-you-mean-block-script.js', + Utils\get_asset_info( 'did-you-mean-block-script', 'dependencies' ), + Utils\get_asset_info( 'did-you-mean-block-script', 'version' ), + true + ); + + wp_set_script_translations( 'ep-did-you-mean-block-script', 'elasticpress' ); + + register_block_type_from_metadata( + EP_PATH . 'assets/js/blocks/did-you-mean', + [ + 'render_callback' => [ $this, 'render_block' ], + ] + ); + } + + /** + * Render Did You Mean block. + * + * @param array $attributes Block attributes. + * @return string + * @since 5.4.0 + */ + public function render_block( array $attributes = [] ): string { + ob_start(); + do_action( 'ep_suggestions' ); + $output = ob_get_clean(); + + if ( empty( $output ) ) { + return ''; + } + + $wrapper_attributes = get_block_wrapper_attributes( + [ + 'class' => 'wp-block-elasticpress-did-you-mean', + ] + ); + + return sprintf( + '
%2$s
', + wp_kses_data( $wrapper_attributes ), + wp_kses_post( $output ) + ); } /** diff --git a/package.json b/package.json index e98acea487..eb53b96e0d 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "comments-script": "./assets/js/comments.js", "comments-block-script": "./assets/js/blocks/comments/index.js", "dashboard-script": "./assets/js/dashboard.js", + "did-you-mean-block-script": "./assets/js/blocks/did-you-mean/index.js", "facets-script": "./assets/js/facets.js", "facets-block-script": "./assets/js/blocks/facets/taxonomy/index.js", "facets-date-block-script": "./assets/js/blocks/facets/date/index.js", diff --git a/tests/e2e/src/specs/did-you-mean.spec.ts b/tests/e2e/src/specs/did-you-mean.spec.ts new file mode 100644 index 0000000000..68463c92eb --- /dev/null +++ b/tests/e2e/src/specs/did-you-mean.spec.ts @@ -0,0 +1,49 @@ +import { test, expect } from '../fixtures.js'; +import { + activatePlugin, + deactivatePlugin, + getEditorFrame, + goToAdminPage, + maybeEnableFeature, +} from '../utils.js'; +import { + closeBlockInserter, + getBlocksList, + insertBlock, + openBlockInserter, +} from '../block-editor.js'; + +test.describe('Did You Mean Feature', { tag: '@group2' }, () => { + test.beforeAll(async () => { + await maybeEnableFeature('did-you-mean'); + }); + + test.beforeEach(async ({ loggedInPage }) => { + await activatePlugin(loggedInPage, 'enable-did-you-mean-block', 'wpCli'); + }); + + test.afterEach(async ({ loggedInPage }) => { + await deactivatePlugin(loggedInPage, 'enable-did-you-mean-block', 'wpCli'); + }); + + /** + * The block is gated behind the `ep_did_you_mean_enabled_in_editor` filter + * in the post editor (it is intended for the FSE Site Editor). The test + * plugin flips that filter so we can exercise the inserter and the + * placeholder rendering without spinning up a block theme. + */ + test('Can insert the Did You Mean block and see its placeholder', async ({ loggedInPage }) => { + await goToAdminPage(loggedInPage, 'post-new.php'); + + await openBlockInserter(loggedInPage); + await expect(await getBlocksList(loggedInPage)).toContainText('Did You Mean'); + await insertBlock(loggedInPage, 'Did You Mean'); + await closeBlockInserter(loggedInPage); + + const editorFrame = await getEditorFrame(loggedInPage); + const block = editorFrame.locator('.wp-block.wp-block-elasticpress-did-you-mean').first(); + + await expect(block).toBeVisible(); + await expect(block).toContainText('Did you mean Hello?'); + }); +}); diff --git a/tests/e2e/src/wordpress-files/test-plugins/enable-did-you-mean-block.php b/tests/e2e/src/wordpress-files/test-plugins/enable-did-you-mean-block.php new file mode 100644 index 0000000000..db9e34e01c --- /dev/null +++ b/tests/e2e/src/wordpress-files/test-plugins/enable-did-you-mean-block.php @@ -0,0 +1,12 @@ +assertStringContainsString( 'Showing results for: teet', $output ); } + /** + * Test Did You Mean block rendering. + */ + public function testRenderBlock() { + global $wp_the_query, $wp_query; + + $this->ep_factory->post->create( [ 'post_content' => 'Test post' ] ); + + ElasticPress\Elasticsearch::factory()->refresh_indices(); + + $query = new \WP_Query( + [ + 's' => 'teet', + ] + ); + + $wp_the_query = $query; + $wp_query = $query; + + $feature = ElasticPress\Features::factory()->get_registered_feature( 'did-you-mean' ); + $feature->register_block(); + + $blocks = parse_blocks( '' ); + $output = render_block( $blocks[0] ); + + $expected = sprintf( 'Did you mean:
test?', get_search_link( 'test' ) ); + + $this->assertStringContainsString( 'wp-block-elasticpress-did-you-mean', $output ); + $this->assertStringContainsString( $expected, $output ); + } + + /** + * Test Did You Mean block rendering when no suggestion is available. + */ + public function testRenderBlockWithoutSuggestion() { + global $wp_the_query, $wp_query; + + $this->ep_factory->post->create( [ 'post_content' => 'Test post' ] ); + + ElasticPress\Elasticsearch::factory()->refresh_indices(); + + $query = new \WP_Query( + [ + 's' => 'test', + ] + ); + + $wp_the_query = $query; + $wp_query = $query; + + $output = ElasticPress\Features::factory()->get_registered_feature( 'did-you-mean' )->render_block(); + + $this->assertSame( '', $output ); + } + /** * Test maybe_sanitize_suggestion method. */