diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/best-sellers.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/best-sellers.tsx index 08398bd4f2e..1cadf8977cd 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/best-sellers.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/best-sellers.tsx @@ -32,7 +32,7 @@ const attributes = { perPage: 5, pages: 1, }, - hideControls: [ CoreFilterNames.ORDER ], + hideControls: [ CoreFilterNames.ORDER, CoreFilterNames.FILTERABLE ], }; const heading: InnerBlockTemplate = [ diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/featured.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/featured.tsx index 7c5176108d3..bc4f73d9dd0 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/featured.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/featured.tsx @@ -31,7 +31,7 @@ const attributes = { perPage: 5, pages: 1, }, - hideControls: [ CoreFilterNames.FEATURED ], + hideControls: [ CoreFilterNames.FEATURED, CoreFilterNames.FILTERABLE ], }; const heading: InnerBlockTemplate = [ diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/new-arrivals.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/new-arrivals.tsx index 9f3a3310a30..6171e87fb94 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/new-arrivals.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/new-arrivals.tsx @@ -40,7 +40,7 @@ const attributes = { value: '-7 days', }, }, - hideControls: [ CoreFilterNames.ORDER ], + hideControls: [ CoreFilterNames.ORDER, CoreFilterNames.FILTERABLE ], }; const heading: InnerBlockTemplate = [ diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/on-sale.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/on-sale.tsx index 9f8c0b502a6..214ec729fb5 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/on-sale.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/on-sale.tsx @@ -34,7 +34,7 @@ const attributes = { perPage: 5, pages: 1, }, - hideControls: [ CoreFilterNames.ON_SALE ], + hideControls: [ CoreFilterNames.ON_SALE, CoreFilterNames.FILTERABLE ], }; const heading: InnerBlockTemplate = [ diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/top-rated.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/top-rated.tsx index 79b1e21c11d..4bbeecca87a 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/top-rated.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections/top-rated.tsx @@ -35,7 +35,7 @@ const attributes = { perPage: 5, pages: 1, }, - hideControls: [ CoreFilterNames.ORDER ], + hideControls: [ CoreFilterNames.ORDER, CoreFilterNames.FILTERABLE ], }; const heading: InnerBlockTemplate = [ diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts index 2dcc28b0b5d..98a1d9be2f6 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts @@ -62,6 +62,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = { woocommerceHandPickedProducts: [], timeFrame: undefined, priceRange: undefined, + filterable: false, }; export const DEFAULT_ATTRIBUTES: Pick< diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx index 8df7f261fab..aea5e9447e1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx @@ -36,7 +36,10 @@ import { import { setQueryAttribute, getDefaultSettings } from '../../utils'; import UpgradeNotice from './upgrade-notice'; import ColumnsControl from './columns-control'; -import InheritQueryControl from './inherit-query-control'; +import { + InheritQueryControl, + FilterableControl, +} from './use-page-context-control'; import OrderByControl from './order-by-control'; import OnSaleControl from './on-sale-control'; import StockStatusControl from './stock-status-control'; @@ -79,6 +82,8 @@ const ProductCollectionInspectorControls = ( const showQueryControls = inherit === false; const showInheritQueryControl = isArchiveTemplate && shouldShowFilter( CoreFilterNames.INHERIT ); + const showFilterableControl = + ! isArchiveTemplate && shouldShowFilter( CoreFilterNames.FILTERABLE ); const showOrderControl = showQueryControls && shouldShowFilter( CoreFilterNames.ORDER ); const showOnSaleControl = shouldShowFilter( CoreFilterNames.ON_SALE ); @@ -129,6 +134,9 @@ const ProductCollectionInspectorControls = ( { showInheritQueryControl && ( ) } + { showFilterableControl && ( + + ) } { showOrderControl && ( diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/inherit-query-control.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-page-context-control.tsx similarity index 51% rename from plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/inherit-query-control.tsx rename to plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-page-context-control.tsx index 3fbf3cc24a1..63813b92e62 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/inherit-query-control.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-page-context-control.tsx @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { usePrevious } from '@woocommerce/base-hooks'; -import { select } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; import { ToggleControl, @@ -17,62 +16,28 @@ import { */ import { CoreFilterNames, - ProductCollectionQuery, - QueryControlProps, + type ProductCollectionQuery, + type QueryControlProps, } from '../../types'; import { DEFAULT_QUERY } from '../../constants'; -import { getDefaultValueOfInheritQueryFromTemplate } from '../../utils'; +import { + getDefaultValueOfInherit, + getDefaultValueOfFilterable, +} from '../../utils'; -const label = __( 'Sync with current query', 'woocommerce' ); +const label = __( 'Use page context', 'woocommerce' ); -const productArchiveHelpText = __( - 'Enable to adjust the displayed products based on the current template and any applied filters.', +const helpText = __( + 'Adjust the displayed products depending on the current template and any applied query filters.', 'woocommerce' ); -const productsByCategoryHelpText = __( - 'Enable to adjust the displayed products based on the current category and any applied filters.', - 'woocommerce' -); - -const productsByTagHelpText = __( - 'Enable to adjust the displayed products based on the current tag and any applied filters.', - 'woocommerce' -); - -const productsByAttributeHelpText = __( - 'Enable to adjust the displayed products based on the current attribute and any applied filters.', - 'woocommerce' -); - -const searchResultsHelpText = __( - 'Enable to adjust the displayed products based on the current search and any applied filters.', - 'woocommerce' -); - -const getHelpTextForTemplate = ( templateId: string ): string => { - if ( templateId.includes( '//taxonomy-product_cat' ) ) { - return productsByCategoryHelpText; - } - if ( templateId.includes( '//taxonomy-product_tag' ) ) { - return productsByTagHelpText; - } - if ( templateId.includes( '//taxonomy-product_attribute' ) ) { - return productsByAttributeHelpText; - } - if ( templateId.includes( '//product-search-results' ) ) { - return searchResultsHelpText; - } - return productArchiveHelpText; -}; - const InheritQueryControl = ( { setQueryAttribute, trackInteraction, query, }: QueryControlProps ) => { const inherit = query?.inherit; - const editSiteStore = select( 'core/edit-site' ); const queryObjectBeforeInheritEnabled = usePrevious( query, @@ -81,13 +46,7 @@ const InheritQueryControl = ( { } ); - const defaultValue = useMemo( - () => getDefaultValueOfInheritQueryFromTemplate(), - [] - ); - - const currentTemplateId = editSiteStore.getEditedPostId() as string; - const helpText = getHelpTextForTemplate( currentTemplateId ); + const defaultValue = useMemo( () => getDefaultValueOfInherit(), [] ); return ( { + const filterable = query?.filterable; + + const defaultValue = useMemo( () => getDefaultValueOfFilterable(), [] ); + + return ( + filterable !== defaultValue } + isShownByDefault + onDeselect={ () => { + setQueryAttribute( { + filterable: defaultValue, + } ); + trackInteraction( CoreFilterNames.FILTERABLE ); + } } + > + { + setQueryAttribute( { + filterable: value, + } ); + trackInteraction( CoreFilterNames.FILTERABLE ); + } } + /> + + ); +}; + +export { FilterableControl, InheritQueryControl }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/product-collection-content.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/product-collection-content.tsx index b7a87bcb3e8..a973de10df1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/product-collection-content.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/product-collection-content.tsx @@ -23,7 +23,8 @@ import type { } from '../types'; import { DEFAULT_ATTRIBUTES, INNER_BLOCKS_TEMPLATE } from '../constants'; import { - getDefaultValueOfInheritQueryFromTemplate, + getDefaultValueOfInherit, + getDefaultValueOfFilterable, useSetPreviewState, } from '../utils'; import InspectorControls from './inspector-controls'; @@ -95,7 +96,8 @@ const ProductCollectionContent = ( { ...DEFAULT_ATTRIBUTES, query: { ...( DEFAULT_ATTRIBUTES.query as ProductCollectionQuery ), - inherit: getDefaultValueOfInheritQueryFromTemplate(), + inherit: getDefaultValueOfInherit(), + filterable: getDefaultValueOfFilterable(), }, ...( attributes as Partial< ProductCollectionAttributes > ), queryId, diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts index 1c884c513d1..c772de36dac 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts @@ -28,6 +28,7 @@ export interface ProductCollectionAttributes { */ queryContextIncludes: string[]; forcePageReload: boolean; + filterable: boolean; // eslint-disable-next-line @typescript-eslint/naming-convention __privatePreviewState?: PreviewState; } @@ -93,6 +94,7 @@ export interface ProductCollectionQuery { isProductCollectionBlock: boolean; woocommerceHandPickedProducts: string[]; priceRange: undefined | PriceRange; + filterable: boolean; } export type ProductCollectionEditComponentProps = @@ -118,13 +120,15 @@ export type ProductCollectionSetAttributes = ( attrs: Partial< ProductCollectionAttributes > ) => void; +export type TrackInteraction = ( filter: CoreFilterNames | string ) => void; + export type DisplayLayoutControlProps = { displayLayout: ProductCollectionDisplayLayout; setAttributes: ProductCollectionSetAttributes; }; export type QueryControlProps = { query: ProductCollectionQuery; - trackInteraction: ( filter: CoreFilterNames | string ) => void; + trackInteraction: TrackInteraction; setQueryAttribute: ( attrs: Partial< ProductCollectionQuery > ) => void; }; @@ -150,6 +154,7 @@ export enum CoreFilterNames { STOCK_STATUS = 'stock-status', TAXONOMY = 'taxonomy', PRICE_RANGE = 'price-range', + FILTERABLE = 'filterable', } export type CollectionName = CoreCollectionNames | string; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/utils.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/utils.tsx index 7e0a795d454..08205b3cb60 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/utils.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/utils.tsx @@ -80,7 +80,9 @@ const isInProductArchive = () => { : false; }; -const isFirstBlockThatSyncsWithQuery = () => { +const isFirstBlockThatUsesPageContext = ( + property: 'inherit' | 'filterable' +) => { // We use experimental selector because it's been graduated as stable (`getBlocksByName`) // in Gutenberg 17.6 (https://github.com/WordPress/gutenberg/pull/58156) and will be // available in WordPress 6.5. @@ -97,15 +99,23 @@ const isFirstBlockThatSyncsWithQuery = () => { ( clientId ) => { const block = getBlock( clientId ); - return block.attributes?.query?.inherit; + return block.attributes?.query?.[ property ]; } ); return ! blockAlreadySyncedWithQuery; }; -export function getDefaultValueOfInheritQueryFromTemplate() { - return isInProductArchive() ? isFirstBlockThatSyncsWithQuery() : false; +export function getDefaultValueOfInherit() { + return isInProductArchive() + ? isFirstBlockThatUsesPageContext( 'inherit' ) + : false; +} + +export function getDefaultValueOfFilterable() { + return ! isInProductArchive() + ? isFirstBlockThatUsesPageContext( 'filterable' ) + : false; } /** @@ -226,7 +236,8 @@ export const getDefaultQuery = ( ...currentQuery, orderBy: DEFAULT_QUERY.orderBy as TProductCollectionOrderBy, order: DEFAULT_QUERY.order as TProductCollectionOrder, - inherit: getDefaultValueOfInheritQueryFromTemplate(), + inherit: getDefaultValueOfInherit(), + filterable: getDefaultValueOfFilterable(), } ); export const getDefaultDisplayLayout = () => @@ -246,7 +257,8 @@ export const getDefaultProductCollection = () => ...DEFAULT_ATTRIBUTES, query: { ...DEFAULT_ATTRIBUTES.query, - inherit: getDefaultValueOfInheritQueryFromTemplate(), + inherit: getDefaultValueOfInherit(), + filterable: getDefaultValueOfFilterable(), }, }, createBlocksFromInnerBlocksTemplate( INNER_BLOCKS_TEMPLATE ) diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index ebf89ca2b86..b6c6a6d5a6c 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -126,14 +126,12 @@ test.describe( 'Product Collection', () => { } ); } ); - test.describe( 'Product Collection Sidebar Settings', () => { - test.beforeEach( async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock(); - } ); - + test.describe( 'Inspector Controls', () => { test( 'Reflects the correct number of columns according to sidebar settings', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await pageObject.setNumberOfColumns( 2 ); await expect( pageObject.productTemplate ).toHaveClass( /columns-2/ @@ -154,6 +152,8 @@ test.describe( 'Product Collection', () => { test( 'Order By - sort products by title in descending order correctly', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + const sortedTitles = [ 'WordPress Pennant', 'V-Neck T-Shirt', @@ -177,6 +177,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on "on sale" status', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + const allProducts = pageObject.products; const salePoducts = pageObject.products.filter( { hasText: 'Product on sale', @@ -201,6 +203,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on selection in handpicked products option', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await pageObject.addFilter( 'Show Hand-picked Products' ); const filterName = 'Hand-picked Products'; @@ -221,6 +225,7 @@ test.describe( 'Product Collection', () => { pageObject, } ) => { await pageObject.createNewPostAndInsertBlock(); + await pageObject.addFilter( 'Keyword' ); await pageObject.setKeyword( 'Album' ); @@ -236,6 +241,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on category.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + const filterName = 'Product categories'; await pageObject.addFilter( 'Show product categories' ); await pageObject.setFilterComboboxValue( filterName, [ @@ -276,6 +283,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on tags.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + const filterName = 'Product tags'; await pageObject.addFilter( 'Show product tags' ); await pageObject.setFilterComboboxValue( filterName, [ @@ -296,6 +305,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on product attributes like color, size etc.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await pageObject.addFilter( 'Show Product Attributes' ); await pageObject.setProductAttribute( 'Color', 'Green' ); @@ -313,6 +324,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on stock status (in stock, out of stock, or backorder).', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await pageObject.setFilterComboboxValue( 'Stock status', [ 'Out of stock', ] ); @@ -331,6 +344,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on featured status.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 9 ); await pageObject.addFilter( 'Featured' ); @@ -349,6 +364,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on created date.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 9 ); await pageObject.addFilter( 'Created' ); @@ -376,6 +393,8 @@ test.describe( 'Product Collection', () => { test( 'Products can be filtered based on price range.', async ( { pageObject, } ) => { + await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 9 ); await pageObject.addFilter( 'Price Range' ); @@ -403,74 +422,62 @@ test.describe( 'Product Collection', () => { await expect( pageObject.products ).toHaveCount( 4 ); } ); - test.describe( 'Sync with current template', () => { - test( 'should not be visible on posts', async ( { - pageObject, - } ) => { + test.describe( '"Use page context" control', () => { + test( 'should be visible on posts', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock(); - const sidebarSettings = - await pageObject.locateSidebarSettings(); await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) - ).toBeHidden(); + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); } ); - const archiveTemplates = [ + [ 'woocommerce/woocommerce//archive-product', 'woocommerce/woocommerce//taxonomy-product_cat', 'woocommerce/woocommerce//taxonomy-product_tag', 'woocommerce/woocommerce//taxonomy-product_attribute', 'woocommerce/woocommerce//product-search-results', - ]; - - const nonArchiveTemplates = [ - 'woocommerce/woocommerce//single-product', - 'twentytwentyfour//home', - 'twentytwentyfour//index', - ]; - - archiveTemplates.map( async ( template ) => { - test( `should be visible in archive template: ${ template }`, async ( { + ].forEach( ( slug ) => { + test( `should be visible in archive template: ${ slug }`, async ( { pageObject, editor, } ) => { - await pageObject.goToEditorTemplate( template ); + await pageObject.goToEditorTemplate( slug ); await pageObject.insertProductCollection(); await pageObject.chooseCollectionInTemplate(); await pageObject.focusProductCollection(); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = - await pageObject.locateSidebarSettings(); await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) ).toBeVisible(); } ); } ); - nonArchiveTemplates.map( async ( template ) => { - test( `should not be visible in non-archive template: ${ template }`, async ( { + [ + 'woocommerce/woocommerce//single-product', + 'twentytwentyfour//home', + 'twentytwentyfour//index', + ].forEach( ( slug ) => { + test( `should be visible in non-archive template: ${ slug }`, async ( { pageObject, editor, } ) => { - await pageObject.goToEditorTemplate( template ); + await pageObject.goToEditorTemplate( slug ); await pageObject.insertProductCollection(); await pageObject.chooseCollectionInTemplate(); await pageObject.focusProductCollection(); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = - await pageObject.locateSidebarSettings(); await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) - ).toBeHidden(); + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); } ); } ); @@ -482,18 +489,15 @@ test.describe( 'Product Collection', () => { await pageObject.focusProductCollection(); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = - await pageObject.locateSidebarSettings(); + const sidebarSettings = pageObject.locateSidebarSettings(); // Inherit query from template should be visible & enabled by default await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) + sidebarSettings.locator( SELECTORS.usePageContextControl ) ).toBeVisible(); await expect( sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` + `${ SELECTORS.usePageContextControl } input` ) ).toBeChecked(); @@ -529,45 +533,136 @@ test.describe( 'Product Collection', () => { ).toBeChecked(); } ); - test( 'is enabled by default in 1st Product Collection and disabled in 2nd+', async ( { + test( 'is enabled by default unless already enabled elsewhere', async ( { pageObject, editor, } ) => { + const productCollection = editor.canvas.getByLabel( + 'Block: Product Collection', + { exact: true } + ); + const usePageContextToggle = pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + .locator( 'input' ); + // First Product Catalog // Option should be visible & ENABLED by default await pageObject.goToEditorTemplate(); - await pageObject.focusProductCollection(); + await editor.selectBlocks( productCollection.first() ); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = - await pageObject.locateSidebarSettings(); - - await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) - ).toBeVisible(); - await expect( - sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` - ) - ).toBeChecked(); + await expect( usePageContextToggle ).toBeChecked(); // Second Product Catalog // Option should be visible & DISABLED by default await pageObject.insertProductCollection(); await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); + + await expect( usePageContextToggle ).not.toBeChecked(); + + // Disable the option in the first Product Catalog + await editor.selectBlocks( productCollection.first() ); + await usePageContextToggle.click(); + + // Third Product Catalog + // Option should be visible & ENABLED by default + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); + + await expect( usePageContextToggle ).toBeChecked(); + } ); + + test( 'allows filtering in non-archive context', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( 'productCatalog' ); + + await expect( pageObject.products ).toHaveCount( 18 ); + + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page + .getByPlaceholder( 'Search' ) + .fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + + const productCollection = page.locator( + '.wp-block-woocommerce-product-collection' + ); await expect( - sidebarSettings.locator( - SELECTORS.inheritQueryFromTemplateControl - ) - ).toBeVisible(); + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 9 ); await expect( - sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` - ) - ).not.toBeChecked(); + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '10' ); + await page.keyboard.press( 'Tab' ); + + await expect( + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 1 ); + await expect( + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + } ); + + test( 'correctly combines editor and front-end filters', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Show product categories' ); + await pageObject.setFilterComboboxValue( 'Product categories', [ + 'Music', + ] ); + + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page + .getByPlaceholder( 'Search' ) + .fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); + + await expect( pageObject.products ).toHaveCount( 2 ); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + + await expect( pageObject.products ).toHaveCount( 2 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '5' ); + await page.keyboard.press( 'Tab' ); + + await expect( pageObject.products ).toHaveCount( 1 ); } ); } ); } ); @@ -787,17 +882,16 @@ test.describe( 'Product Collection', () => { await expect( pageObject.products ).toHaveCount( 4 ); } ); - test( "Product Catalog Collection can be added in post and doesn't sync query with template", async ( { + test( 'Product Catalog Collection can be added in post and syncs query with template', async ( { pageObject, } ) => { await pageObject.createNewPostAndInsertBlock( 'productCatalog' ); - const sidebarSettings = await pageObject.locateSidebarSettings(); - const input = sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` - ); + const usePageContextToggle = pageObject + .locateSidebarSettings() + .locator( `${ SELECTORS.usePageContextControl } input` ); - await expect( input ).toBeHidden(); + await expect( usePageContextToggle ).toBeVisible(); await expect( pageObject.products ).toHaveCount( 9 ); await pageObject.publishAndGoToFrontend(); @@ -822,9 +916,9 @@ test.describe( 'Product Collection', () => { await pageObject.chooseCollectionInTemplate(); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = await pageObject.locateSidebarSettings(); + const sidebarSettings = pageObject.locateSidebarSettings(); const input = sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` + `${ SELECTORS.usePageContextControl } input` ); await expect( input ).toBeChecked(); @@ -854,8 +948,7 @@ test.describe( 'Product Collection', () => { test( 'On Sale', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'onSale' ); - const sidebarSettings = - await pageObject.locateSidebarSettings(); + const sidebarSettings = pageObject.locateSidebarSettings(); const input = sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ); @@ -865,8 +958,7 @@ test.describe( 'Product Collection', () => { test( 'Featured', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'featured' ); - const sidebarSettings = - await pageObject.locateSidebarSettings(); + const sidebarSettings = pageObject.locateSidebarSettings(); const input = sidebarSettings.getByLabel( SELECTORS.featuredControlLabel ); @@ -1477,7 +1569,7 @@ test.describe( 'Testing registerProductCollection', () => { 'myCustomCollection' ); - const sidebarSettings = await pageObject.locateSidebarSettings(); + const sidebarSettings = pageObject.locateSidebarSettings(); const onsaleControl = sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts index db0737ceb5d..b154c1eb4c0 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts @@ -38,7 +38,7 @@ export const SELECTORS = { }, onSaleControlLabel: 'Show only products on sale', featuredControlLabel: 'Show only featured products', - inheritQueryFromTemplateControl: + usePageContextControl: '.wc-block-product-collection__inherit-query-control', shrinkColumnsToFit: 'Responsive', productSearchLabel: 'Search', @@ -374,7 +374,7 @@ class ProductCollectionPage { } async setNumberOfColumns( numberOfColumns: number ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const inputField = sidebarSettings.getByRole( 'spinbutton', { name: 'Columns', } ); @@ -390,7 +390,7 @@ class ProductCollectionPage { | 'popularity/desc' | 'rating/desc' ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const orderByComboBox = sidebarSettings.getByRole( 'combobox', { name: 'Order by', } ); @@ -400,7 +400,7 @@ class ProductCollectionPage { } async getOrderByElement() { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); return sidebarSettings.getByRole( 'combobox', { name: 'Order by', } ); @@ -423,7 +423,7 @@ class ProductCollectionPage { onSale: true, } ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ); @@ -448,7 +448,7 @@ class ProductCollectionPage { isLocatorsRefreshNeeded: true, } ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.getByLabel( SELECTORS.featuredControlLabel ); @@ -475,7 +475,7 @@ class ProductCollectionPage { const operatorSelector = SELECTORS.createdFilter.operator[ operator ]; const rangeSelector = SELECTORS.createdFilter.range[ range ]; - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const operatorButton = sidebarSettings.getByLabel( operatorSelector ); const rangeButton = sidebarSettings.getByLabel( rangeSelector ); @@ -487,7 +487,7 @@ class ProductCollectionPage { const minInputSelector = SELECTORS.priceRangeFilter.min; const maxInputSelector = SELECTORS.priceRangeFilter.max; - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const minInput = sidebarSettings.getByLabel( minInputSelector ); const maxInput = sidebarSettings.getByLabel( maxInputSelector ); @@ -498,7 +498,7 @@ class ProductCollectionPage { } async setFilterComboboxValue( filterName: string, filterValue: string[] ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.getByLabel( filterName ); await input.click(); @@ -527,7 +527,7 @@ class ProductCollectionPage { } async setKeyword( keyword: string ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.getByLabel( 'Keyword' ); await input.clear(); await input.fill( keyword ); @@ -590,7 +590,7 @@ class ProductCollectionPage { } async setShrinkColumnsToFit( value = true ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.getByLabel( SELECTORS.shrinkColumnsToFit ); @@ -602,7 +602,7 @@ class ProductCollectionPage { } async setProductAttribute( attribute: 'Color' | 'Size', value: string ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const productAttributesContainer = sidebarSettings.locator( '.woocommerce-product-attributes' @@ -631,9 +631,9 @@ class ProductCollectionPage { } async setInheritQueryFromTemplate( inheritQueryFromTemplate: boolean ) { - const sidebarSettings = await this.locateSidebarSettings(); + const sidebarSettings = this.locateSidebarSettings(); const input = sidebarSettings.locator( - `${ SELECTORS.inheritQueryFromTemplateControl } input` + `${ SELECTORS.usePageContextControl } input` ); if ( inheritQueryFromTemplate ) { await input.check(); @@ -687,7 +687,7 @@ class ProductCollectionPage { /** * Locators */ - async locateSidebarSettings() { + locateSidebarSettings() { return this.page.getByRole( 'region', { name: 'Editor settings', } ); diff --git a/plugins/woocommerce/changelog/48749-product-collection-add-control-to-toggle-whether-block-is-filterable-in-non-archive-context b/plugins/woocommerce/changelog/48749-product-collection-add-control-to-toggle-whether-block-is-filterable-in-non-archive-context new file mode 100644 index 00000000000..4b113e9a86a --- /dev/null +++ b/plugins/woocommerce/changelog/48749-product-collection-add-control-to-toggle-whether-block-is-filterable-in-non-archive-context @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Product Collection: Rename "Sync with current query" option to "Use page context" and make it working in non-archive context as well diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php index 9d828541d02..e11038ba6ff 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php @@ -524,7 +524,10 @@ class ProductCollection extends AbstractBlock { // phpcs:ignore WordPress.DB.SlowDBQuery $block_context_query['tax_query'] = ! empty( $query['tax_query'] ) ? $query['tax_query'] : array(); - $is_exclude_applied_filters = ! ( $block->context['query']['inherit'] ?? false ); + $inherit = $block->context['query']['inherit'] ?? false; + $filterable = $block->context['query']['filterable'] ?? false; + + $is_exclude_applied_filters = ! ( $inherit || $filterable ); return $this->get_final_frontend_query( $block_context_query, $page, $is_exclude_applied_filters ); }