From 54c2f70c1dd162424b4787f14e63db8f2ebc4038 Mon Sep 17 00:00:00 2001 From: Luigi Date: Tue, 6 Dec 2022 17:08:05 +0100 Subject: [PATCH 1/2] Add Inherit Query from template option --- assets/js/blocks/product-query/constants.ts | 5 +- assets/js/blocks/product-query/editor.scss | 4 + .../product-query/inspector-controls.tsx | 118 +++++++----- assets/js/blocks/product-query/types.ts | 2 +- assets/js/blocks/product-query/utils.tsx | 26 ++- .../variations/product-query.tsx | 10 +- src/BlockTypes/ProductQuery.php | 3 +- tests/e2e/config/jest.config.js | 6 +- .../product-query-with-templates.test.ts | 176 ++++++++++++++++++ .../e2e/specs/shopper/product-query/utils.ts | 68 +++++++ 10 files changed, 355 insertions(+), 63 deletions(-) create mode 100644 assets/js/blocks/product-query/editor.scss create mode 100644 tests/e2e/specs/shopper/product-query/product-query-with-templates.test.ts create mode 100644 tests/e2e/specs/shopper/product-query/utils.ts diff --git a/assets/js/blocks/product-query/constants.ts b/assets/js/blocks/product-query/constants.ts index 77c8c3d281e..db842cb4454 100644 --- a/assets/js/blocks/product-query/constants.ts +++ b/assets/js/blocks/product-query/constants.ts @@ -28,6 +28,7 @@ export const ALL_PRODUCT_QUERY_CONTROLS = [ 'presets', 'onSale', 'stockStatus', + 'wooInherit', ]; export const DEFAULT_ALLOWED_CONTROLS = [ @@ -56,8 +57,8 @@ export const QUERY_DEFAULT_ATTRIBUTES: QueryBlockAttributes = { pages: 0, offset: 0, postType: 'product', - order: 'desc', - orderBy: 'date', + order: 'asc', + orderBy: 'title', author: '', search: '', exclude: [], diff --git a/assets/js/blocks/product-query/editor.scss b/assets/js/blocks/product-query/editor.scss new file mode 100644 index 00000000000..91571d4e3cf --- /dev/null +++ b/assets/js/blocks/product-query/editor.scss @@ -0,0 +1,4 @@ +.woo-inherit-query-toggle { + grid-column-start: 1; + grid-column-end: 3; +} diff --git a/assets/js/blocks/product-query/inspector-controls.tsx b/assets/js/blocks/product-query/inspector-controls.tsx index abdbb576cde..e99ec60bf5a 100644 --- a/assets/js/blocks/product-query/inspector-controls.tsx +++ b/assets/js/blocks/product-query/inspector-controls.tsx @@ -26,6 +26,7 @@ import { QueryBlockAttributes, } from './types'; import { + isCustomInheritGlobalQueryImplementationEnabled, isWooQueryBlockVariation, setQueryAttribute, useAllowedControls, @@ -38,6 +39,8 @@ import { import { PopularPresets } from './inspector-controls/popular-presets'; import { AttributesFilter } from './inspector-controls/attributes-filter'; +import './editor.scss'; + const NAMESPACED_CONTROLS = ALL_PRODUCT_QUERY_CONTROLS.map( ( id ) => `__woocommerce${ id[ 0 ].toUpperCase() }${ id.slice( @@ -146,59 +149,82 @@ export const TOOLS_PANEL_CONTROLS = { ); }, - wooInherit: ( props: ProductQueryBlock ) => ( - { - setQueryAttribute( props, { __woocommerceInherit } ); - } } - /> - ), + wooInherit: ( props: ProductQueryBlock ) => { + return ( + { + if ( isCustomInheritGlobalQueryImplementationEnabled ) { + return setQueryAttribute( props, { + __woocommerceInherit: inherit, + } ); + } + return setQueryAttribute( props, { inherit } ); + } } + /> + ); + }, +}; + +const ProductQueryControls = ( props: ProductQueryBlock ) => { + const allowedControls = useAllowedControls( props.attributes ); + const defaultWooQueryParams = useDefaultWooQueryParamsForVariation( + props.attributes.namespace + ); + return ( + <> + + { allowedControls?.includes( 'presets' ) && ( + + ) } + { + setQueryAttribute( props, defaultWooQueryParams ); + } } + > + { Object.entries( TOOLS_PANEL_CONTROLS ).map( + ( [ key, Control ] ) => + allowedControls?.includes( key ) ? ( + + ) : null + ) } + + + { + // Hacky temporary solution to display the feedback prompt + // at the bottom of the inspector controls + } + + + + + ); }; export const withProductQueryControls = < T extends EditorBlock< T > >( BlockEdit: ElementType ) => ( props: ProductQueryBlock ) => { - const allowedControls = useAllowedControls( props.attributes ); - const defaultWooQueryParams = useDefaultWooQueryParamsForVariation( - props.attributes.namespace - ); - return isWooQueryBlockVariation( props ) ? ( <> - - { allowedControls?.includes( 'presets' ) && ( - - ) } - { - setQueryAttribute( props, defaultWooQueryParams ); - } } - > - { Object.entries( TOOLS_PANEL_CONTROLS ).map( - ( [ key, Control ] ) => - allowedControls?.includes( key ) ? ( - - ) : null - ) } - - - { - // Hacky temporary solution to display the feedback prompt - // at the bottom of the inspector controls - } - - - + ) : ( diff --git a/assets/js/blocks/product-query/types.ts b/assets/js/blocks/product-query/types.ts index 4e5067eed7c..8ed59f20c2a 100644 --- a/assets/js/blocks/product-query/types.ts +++ b/assets/js/blocks/product-query/types.ts @@ -94,7 +94,7 @@ export interface QueryBlockQuery { inherit: boolean; offset?: number; order: 'asc' | 'desc'; - orderBy: 'date' | 'relevance'; + orderBy: 'date' | 'relevance' | 'title'; pages?: number; parents?: number[]; perPage?: number; diff --git a/assets/js/blocks/product-query/utils.tsx b/assets/js/blocks/product-query/utils.tsx index 122ffa2251b..6dcd04f0f0c 100644 --- a/assets/js/blocks/product-query/utils.tsx +++ b/assets/js/blocks/product-query/utils.tsx @@ -56,6 +56,19 @@ export function setQueryAttribute( } ); } +// This is a feature flag to enable the custom inherit Global Query implementation. +// This is not intended to be a permanent feature flag, but rather a temporary. +// https://github.com/woocommerce/woocommerce-blocks/pull/7382 +export const isCustomInheritGlobalQueryImplementationEnabled = false; + +export function isWooInheritQueryEnabled( + attributes: ProductQueryBlock[ 'attributes' ] +) { + return isCustomInheritGlobalQueryImplementationEnabled + ? attributes.query.__woocommerceInherit + : attributes.query.inherit; +} + /** * Hook that returns the query properties' names defined by the active * block variation, to determine which block inspector controls to show. @@ -66,13 +79,22 @@ export function setQueryAttribute( export function useAllowedControls( attributes: ProductQueryBlock[ 'attributes' ] ) { - return useSelect( + const isSiteEditor = useSelect( 'core/edit-site' ) !== undefined; + + const controls = useSelect( ( select ) => select( WP_BLOCKS_STORE ).getActiveBlockVariation( QUERY_LOOP_ID, attributes )?.allowedControls, - [ attributes ] ); + + if ( ! isSiteEditor ) { + return controls.filter( ( control ) => control !== 'wooInherit' ); + } + + return isWooInheritQueryEnabled( attributes ) + ? controls.filter( ( control ) => control === 'wooInherit' ) + : controls; } diff --git a/assets/js/blocks/product-query/variations/product-query.tsx b/assets/js/blocks/product-query/variations/product-query.tsx index dc486f7cd0e..74925585892 100644 --- a/assets/js/blocks/product-query/variations/product-query.tsx +++ b/assets/js/blocks/product-query/variations/product-query.tsx @@ -19,12 +19,6 @@ import { const VARIATION_NAME = 'woocommerce/product-query'; -// This is a feature flag to enable the custom inherit Global Query implementation. -// This is not intended to be a permanent feature flag, but rather a temporary. -// It is also necessary to enable this feature flag on the PHP side: `src/BlockTypes/ProductQuery.php:49`. -// https://github.com/woocommerce/woocommerce-blocks/pull/7382 -const isCustomInheritGlobalQueryImplementationEnabled = false; - if ( isFeaturePluginBuild() ) { registerBlockVariation( QUERY_LOOP_ID, { description: __( @@ -50,9 +44,7 @@ if ( isFeaturePluginBuild() ) { // https://github.com/WordPress/gutenberg/pull/43632 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - allowedControls: isCustomInheritGlobalQueryImplementationEnabled - ? [ ...DEFAULT_ALLOWED_CONTROLS, 'wooInherit' ] - : DEFAULT_ALLOWED_CONTROLS, + allowedControls: DEFAULT_ALLOWED_CONTROLS, innerBlocks: INNER_BLOCKS_TEMPLATE, scope: [ 'inserter' ], } ); diff --git a/src/BlockTypes/ProductQuery.php b/src/BlockTypes/ProductQuery.php index bdacef4c896..575e67434ba 100644 --- a/src/BlockTypes/ProductQuery.php +++ b/src/BlockTypes/ProductQuery.php @@ -41,7 +41,7 @@ class ProductQuery extends AbstractBlock { /** This is a feature flag to enable the custom inherit Global Query implementation. * This is not intended to be a permanent feature flag, but rather a temporary. - * It is also necessary to enable this feature flag on the PHP side: `assets/js/blocks/product-query/variations/product-query.tsx:26`. + * It is also necessary to enable this feature flag on the PHP side: `assets/js/blocks/product-query/utils.tsx:83`. * https://github.com/woocommerce/woocommerce-blocks/pull/7382 * * @var boolean @@ -716,4 +716,3 @@ private function get_global_query( $parsed_block ) { } } - diff --git a/tests/e2e/config/jest.config.js b/tests/e2e/config/jest.config.js index 6915c6b6750..223f3c31133 100644 --- a/tests/e2e/config/jest.config.js +++ b/tests/e2e/config/jest.config.js @@ -30,6 +30,10 @@ module.exports = { '/tests/e2e/config/jest.setup.js', 'expect-puppeteer', ], - testPathIgnorePatterns: [ '/tests/e2e/specs/performance' ], + testPathIgnorePatterns: [ + '/tests/e2e/specs/performance', + // Ignore all the files that have utils in the name + 'utils', + ], transformIgnorePatterns: [ 'node_modules/(?!(woocommerce)/)' ], }; diff --git a/tests/e2e/specs/shopper/product-query/product-query-with-templates.test.ts b/tests/e2e/specs/shopper/product-query/product-query-with-templates.test.ts new file mode 100644 index 00000000000..e8a1fc9321e --- /dev/null +++ b/tests/e2e/specs/shopper/product-query/product-query-with-templates.test.ts @@ -0,0 +1,176 @@ +/** + * External dependencies + */ +import { deleteAllTemplates } from '@wordpress/e2e-test-utils'; + +/** + * Internal dependencies + */ +import { + BASE_URL, + goToTemplateEditor, + openBlockEditorSettings, + saveTemplate, + useTheme, +} from '../../../utils'; +import { + addProductQueryBlock, + block, + configurateProductQueryBlock, + getProductsNameFromClassicTemplate, + getProductsNameFromProductQuery, +} from './utils'; + +describe( `${ block.name } Block`, () => { + useTheme( 'emptytheme' ); + afterAll( async () => { + await deleteAllTemplates( 'wp_template' ); + await deleteAllTemplates( 'wp_template_part' ); + } ); + + describe( 'with All Templates', () => { + beforeAll( async () => { + const productCatalogTemplateId = + 'woocommerce/woocommerce//archive-product'; + + await goToTemplateEditor( { + postId: productCatalogTemplateId, + } ); + await openBlockEditorSettings(); + await addProductQueryBlock(); + } ); + + it( 'when Inherit Query from template is disabled all the settings that customize the query should be hide', async () => { + await expect( page ).toMatchElement( + block.selectors.editor.popularFilter + ); + + await expect( page ).toMatchElement( + block.selectors.editor.advanceFilterOptionsButton + ); + } ); + + it( 'when Inherit Query from template is enabled all the settings that customize the query should be hide', async () => { + await configurateProductQueryBlock(); + + const popularFilterEl = await page.$( + block.selectors.editor.popularFilter + ); + const advanceFilterOptionsButton = await page.$( + block.selectors.editor.advanceFilterOptionsButton + ); + + expect( popularFilterEl ).toBeNull(); + expect( advanceFilterOptionsButton ).toBeNull(); + } ); + } ); + + describe( 'with Product Catalog Template', () => { + beforeAll( async () => { + const productCatalogTemplateId = + 'woocommerce/woocommerce//archive-product'; + + await goToTemplateEditor( { + postId: productCatalogTemplateId, + } ); + await addProductQueryBlock(); + await configurateProductQueryBlock(); + await page.waitForNetworkIdle(); + await saveTemplate(); + await page.waitForNetworkIdle(); + await page.goto( BASE_URL + '/shop' ); + await page.waitForNetworkIdle(); + } ); + + it( 'should render the same products in the same position of the classic template', async () => { + const classicProducts = await getProductsNameFromClassicTemplate(); + const products = await getProductsNameFromProductQuery(); + + expect( classicProducts ).toEqual( products ); + } ); + } ); + + describe( 'with Products By Category Template', () => { + beforeAll( async () => { + const taxonomyProductCategory = + 'woocommerce/woocommerce//taxonomy-product_cat'; + + await goToTemplateEditor( { + postId: taxonomyProductCategory, + } ); + await addProductQueryBlock(); + await configurateProductQueryBlock(); + await page.waitForNetworkIdle(); + await saveTemplate(); + await page.waitForNetworkIdle(); + await Promise.all( [ + page.goto( BASE_URL + '/product-category/uncategorized/' ), + page.waitForNavigation( { + waitUntil: 'networkidle0', + } ), + ] ); + } ); + + it( 'should render the same products in the same position of the classic template', async () => { + const classicProducts = await getProductsNameFromClassicTemplate(); + const products = await getProductsNameFromProductQuery(); + + expect( classicProducts ).toEqual( products ); + } ); + } ); + + describe( 'with Products By Tag Template', () => { + beforeAll( async () => { + const tagProductCategory = + 'woocommerce/woocommerce//taxonomy-product_tag'; + await goToTemplateEditor( { + postId: tagProductCategory, + } ); + await addProductQueryBlock(); + await configurateProductQueryBlock(); + await page.waitForNetworkIdle(); + await saveTemplate(); + await page.waitForNetworkIdle(); + await Promise.all( [ + page.goto( BASE_URL + '/product-tag/newest/' ), + page.waitForNavigation( { + waitUntil: 'networkidle0', + } ), + ] ); + } ); + + it( 'should render the same products in the same position of the classic template', async () => { + const classicProducts = await getProductsNameFromClassicTemplate(); + const products = await getProductsNameFromProductQuery(); + + expect( classicProducts ).toEqual( products ); + } ); + } ); + + describe( 'with Products Search Results Template', () => { + beforeAll( async () => { + const productSearchResults = + 'woocommerce/woocommerce//product-search-results'; + await goToTemplateEditor( { + postId: productSearchResults, + } ); + await addProductQueryBlock(); + await configurateProductQueryBlock(); + await page.waitForNetworkIdle(); + await saveTemplate(); + await page.waitForNetworkIdle(); + await Promise.all( [ + page.goto( BASE_URL + '/?s=usb&post_type=product' ), + page.waitForNavigation( { + waitUntil: 'networkidle0', + } ), + ] ); + } ); + + it( 'should render the same products in the same position of the classic template', async () => { + const classicProducts = await getProductsNameFromClassicTemplate(); + const products = await getProductsNameFromProductQuery(); + expect( classicProducts ).toEqual( products ); + } ); + } ); +} ); diff --git a/tests/e2e/specs/shopper/product-query/utils.ts b/tests/e2e/specs/shopper/product-query/utils.ts new file mode 100644 index 00000000000..6fe1fddf5a0 --- /dev/null +++ b/tests/e2e/specs/shopper/product-query/utils.ts @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import { insertBlock } from '@wordpress/e2e-test-utils'; +/** + * Internal dependencies + */ + +import { openBlockEditorSettings } from '../../../utils'; + +export const block = { + name: 'Products (Beta)', + slug: 'woocommerce/product-query', + selectors: { + editor: { + inheritQueryFromTemplateSetting: + "//label[text()='Inherit query from template']", + popularFilter: '.woocommerce-product-query-panel__sort', + advanceFilterOptionsButton: + "button[aria-label='Advanced Filters options']", + }, + frontend: { + classicProductsListName: '.woocommerce-loop-product__title', + productQueryProductsListName: + '.wp-block-query .wp-block-post-title', + }, + }, +}; + +export const addProductQueryBlock = async () => { + await insertBlock( block.name ); + await page.waitForNetworkIdle(); +}; + +const enableInheritQueryFromTemplateSetting = async () => { + const [ button ] = await page.$x( + block.selectors.editor.inheritQueryFromTemplateSetting + ); + await button.click(); +}; + +export const configurateProductQueryBlock = async () => { + await openBlockEditorSettings(); + await enableInheritQueryFromTemplateSetting(); +}; + +export const getProductsNameFromClassicTemplate = async () => { + const products = await page.$$( + block.selectors.frontend.classicProductsListName + ); + return Promise.all( + products.map( ( el ) => + page.evaluate( ( node ) => node.textContent, el ) + ) + ); +}; + +export const getProductsNameFromProductQuery = async () => { + const products = await page.$$( + block.selectors.frontend.productQueryProductsListName + ); + + return Promise.all( + products.map( ( el ) => + page.evaluate( ( element ) => element.textContent, el ) + ) + ); +}; From 49be1e79990b84de2baa308e855da62597b52bbb Mon Sep 17 00:00:00 2001 From: Luigi Date: Mon, 12 Dec 2022 14:00:20 +0100 Subject: [PATCH 2/2] Update label --- assets/js/blocks/product-query/inspector-controls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/blocks/product-query/inspector-controls.tsx b/assets/js/blocks/product-query/inspector-controls.tsx index e99ec60bf5a..692bd60fb64 100644 --- a/assets/js/blocks/product-query/inspector-controls.tsx +++ b/assets/js/blocks/product-query/inspector-controls.tsx @@ -158,7 +158,7 @@ export const TOOLS_PANEL_CONTROLS = { 'woo-gutenberg-products-block' ) } help={ __( - 'Toggle to use the global query context that is set with the current template, such as an archive or search. Disable to customize the settings independently.', + 'Toggle to use the global query context that is set with the current template, such as variations of the product catalog or search. Disable to customize the filtering independently.', 'woo-gutenberg-products-block' ) } checked={