Expose `__experimentalRegisterProductCollection` in @woocommerce/blocks-registry Package (#48141)
* Expose registerProductCollection in @woocommerce/blocks-registry Package This commit exposes the `registerProductCollection` function as part of the `@woocommerce/blocks-registry` package. This enhancement facilitates the registration of new product collections by 3PDs, promoting better modularity and extensibility within the WooCommerce Blocks ecosystem. Changes include: - Migration of `register-product-collection.tsx` to `packages/checkout/blocks-registry`. - Export `registerProductCollection` from `@woocommerce/blocks-registry/index.ts`. - Updated related imports and references to the new path. This update enables 3PDs to register product collections more seamlessly, enhancing the extensibility of Product Collection block. * Replace @woocommerce/blocks-checkout with @woocommerce/blocks-registry * Add __experimental prefix * Add changefile(s) from automation for the following project(s): woocommerce-blocks * Improve registerproductcollection for 3pds * Set isDefault value to false * Don't export all the types * Update changelog * Add changefile(s) from automation for the following project(s): woocommerce-blocks * Add plugin to test __experimentalRegisterProductCollection * Add E2E tests * Fix Lint errors * Improve E2E tests for __experimentalRegisterProductCollection - Reduced preview timeout from 2000ms to 1000ms. - Expanded E2E tests to cover new attributes and preview functionalities. * Refactor code to improve readability and maintainability - Added a warning comment to indicate that `__experimentalRegisterProductCollection` is an experimental API. - Refactored variable names and imports in `register-product-collection.tsx` and `index.tsx` for clarity. - Simplified and reorganized type definitions and imports in `types.ts` and `utils.tsx`. - Renamed function in `register-product-collection-tester.php` for consistency. * E2E: Also test the Frontend * Use alias for import statement * Don't pass isActive to registerProductCollection Now it's handle by registerProductCollection itself. * Update registerproductcollection API structure Refactored the product collection block to enhance attribute management and ensure consistency in query defaults. This change includes: - Importing `DEFAULT_QUERY` from constants and using it to set default query attributes. - Removing `DEFAULT_ATTRIBUTES` from specific collections and directly defining required attributes. - Ensuring `postType` and `isProductCollectionBlock` are set to default values in the query object. - Setting `inherit` attribute to `false` by default in all collections. * Hide inherit control in collections Ensure the "inherit" control is always hidden, as collections should not be able to change this attribute. This includes: - Adding `CoreFilterNames.INHERIT` to the `hideControls` set in `register-product-collection.tsx`. - Adjusting the `hideControls` attribute in individual collection files to remove redundant hiding of the `INHERIT` control. * Fix: Filters not showing in inspector controls * Set inherit to false for all collections * Add changefile(s) from automation for the following project(s): woocommerce-blocks * Product Collection: Add validation for `__experimentalRegisterProductCollection` arguments (#48513) * Add validation for `__experimentalRegisterProductCollection` arguments Introduced comprehensive validation for the `ProductCollectionConfig` object in `__experimentalRegisterProductCollection` to ensure correct data types and values, enhancing error handling and robustness. - Added a new function `isValidProductCollectionConfig` to perform various checks on the `ProductCollectionConfig` object. - Validates properties such as `name`, `title`, `description`, `category`, `keywords`, `icon`, `isDefault`, `innerBlocks`, `example`, `scope`, `isActive`, `attributes`, and `preview`. - Ensures correct data types and provides detailed console error messages for invalid configurations. - Updated `__experimentalRegisterProductCollection` to use the validation function before proceeding with the registration process. **Impact** - Improves stability and prevents invalid configurations from causing runtime errors. - Provides clearer error messages for developers, aiding in quicker debugging and development. * Fix typo * Refactor: Replace console.error with console.warn Updated the error logging in the isValidProductCollectionConfig function to use console.warn instead of console.error for invalid configuration properties. This address the feedback from the PR review. - Replaced console.error with console.warn for various validation checks in isValidProductCollectionConfig. - Removed redundant return statements after console.warn calls. - Improved logging messages to better inform about invalid configuration properties without treating them as critical errors. - Simplified the logic in __experimentalRegisterProductCollection by combining query and attribute properties and ensuring defaults are set properly. * Refactor: Rename isValidProductCollectionConfig to isValidCollectionConfig Updated the function name from isValidProductCollectionConfig to isValidCollectionConfig for better clarity and consistency. Also, renamed related variables for improved readability. * Add validation for name property * Add changefile(s) from automation for the following project(s): woocommerce-blocks * Title is required for new collection * Update comments * Fix E2E tests * Address PR feedback --------- Co-authored-by: github-actions <github-actions@github.com> * Add README file for __experimentalRegisterProductCollection * Add screenshots in README file * Try to fix lint issue * Docs: add example for collection with inner blocks Enhanced the documentation for `__experimentalRegisterProductCollection` to include an example demonstrating how to define a collection with inner blocks. This example shows how to create a custom collection with nested blocks, including a heading and product elements, providing a clear guide for developers. New content added: - Example 4: Collection with inner blocks - Sample code for defining a collection with inner blocks - Tips and links to further resources on inner blocks and core collection definitions * Fix Lint errors * Address PR feedback * Reduce number of JS files on /shop page **Problem:** There was increase in number of JS files on /shop page after exposing `registerProductCollection` function in `@woocommerce/blocks-registry` package. This package is loaded on the frontend. For example, previously 45 JS files were loaded on /shop page but now 55 JS files are loaded on /shop page. **Solution:** 1. After a bit of debugging I found out that constant file which we are importing i.e. `plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts` contain some heavy dependencies & it's not pure. Therefore, I decided to split this file into two files. I moved all the constants that are used in `registerProductCollection` function to a new file i.e. `plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants-register-product-collection.ts`. This way, we don't need to load all the constants on the frontend i.e. /shop page. - This reduced 4 JS files i.e. 51 JS files are loaded on /shop page. 2. After some more investigation, I found out that importing `registerBlockVariation` function is increasing number of JS files on Frontend. Therefore, I decided to use global `wp` object to call `registerBlockVariation` function. This way, we don't need to import it. This reduced last 6 files i.e. 45 JS files are loaded on /shop page. This way, I was able to reduce number of JS files on /shop page from 55 to 45, which is same as before this PR. * Refactor: product collection constants - Moved constants from `constants-register-product-collection.ts` to `constants.ts` - Deleted `constants-register-product-collection.ts` - Updated import paths in relevant files to reflect the changes - Moved utility functions to `utils.ts` --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
d3df046f97
commit
fa11141726
|
@ -1,2 +1,3 @@
|
|||
export * from './payment-methods';
|
||||
export * from './block-components';
|
||||
export * from './product-collection/register-product-collection';
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
/* eslint-disable no-console */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockVariation } from '@wordpress/blocks';
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { EditorBlock } from '@woocommerce/types';
|
||||
import type { ElementType } from '@wordpress/element';
|
||||
import type { BlockEditProps, BlockAttributes } from '@wordpress/blocks';
|
||||
import {
|
||||
SetPreviewState,
|
||||
PreviewState,
|
||||
ProductCollectionAttributes,
|
||||
CoreFilterNames,
|
||||
} from '@woocommerce/blocks/product-collection/types';
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_TEMPLATE,
|
||||
PRODUCT_COLLECTION_BLOCK_NAME as BLOCK_NAME,
|
||||
DEFAULT_QUERY,
|
||||
} from '@woocommerce/blocks/product-collection/constants';
|
||||
|
||||
export interface ProductCollectionConfig extends BlockVariation {
|
||||
preview?: {
|
||||
setPreviewState?: SetPreviewState;
|
||||
initialPreviewState?: PreviewState;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration object of new collection. This function checks
|
||||
* whether the provided config object adheres to the required schema and conditions necessary
|
||||
* for a valid collection.
|
||||
*
|
||||
* Each validation step may log errors or warnings to the console if the corresponding property
|
||||
* does not meet the expected criteria. It will bail early and return false, if any of the
|
||||
* required properties are missing or invalid.
|
||||
*/
|
||||
const isValidCollectionConfig = ( config: ProductCollectionConfig ) => {
|
||||
// Basic checks for the top-level argument
|
||||
if ( typeof config !== 'object' || config === null ) {
|
||||
console.error(
|
||||
'Invalid arguments: You must pass an object to __experimentalRegisterProductCollection.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* BlockVariation properties validation
|
||||
*/
|
||||
// name
|
||||
if ( typeof config.name !== 'string' || config.name.length === 0 ) {
|
||||
console.error( 'Invalid name: name must be a non-empty string.' );
|
||||
return false;
|
||||
} else if (
|
||||
! config.name.match(
|
||||
/^[a-zA-Z0-9-]+\/product-collection\/[a-zA-Z0-9-]+$/
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`To prevent conflicts with other collections, please use a unique name following the pattern: "<plugin-name>/product-collection/<collection-name>". Ensure "<plugin-name>" is your plugin name and "<collection-name>" is your collection name. Both should consist only of alphanumeric characters and hyphens (e.g., "my-plugin/product-collection/my-collection").`
|
||||
);
|
||||
}
|
||||
// title
|
||||
if ( typeof config.title !== 'string' || config.title.length === 0 ) {
|
||||
console.error( 'Invalid title: title must be a non-empty string.' );
|
||||
return false;
|
||||
}
|
||||
// description
|
||||
if (
|
||||
config.description !== undefined &&
|
||||
typeof config.description !== 'string'
|
||||
) {
|
||||
console.warn( 'Invalid description: description must be a string.' );
|
||||
}
|
||||
// category
|
||||
if (
|
||||
config.category !== undefined &&
|
||||
typeof config.category !== 'string'
|
||||
) {
|
||||
console.warn( 'Invalid category: category must be a string.' );
|
||||
}
|
||||
// keywords
|
||||
if ( config.keywords !== undefined && ! Array.isArray( config.keywords ) ) {
|
||||
console.warn(
|
||||
'Invalid keywords: keywords must be an array of strings.'
|
||||
);
|
||||
}
|
||||
// icon
|
||||
if (
|
||||
config.icon !== undefined &&
|
||||
typeof config.icon !== 'string' &&
|
||||
typeof config.icon !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid icon: icon must be a string or an object.' );
|
||||
}
|
||||
// example
|
||||
if ( config.example !== undefined && typeof config.example !== 'object' ) {
|
||||
console.warn( 'Invalid example: example must be an object.' );
|
||||
}
|
||||
// scope
|
||||
if ( config.scope !== undefined && ! Array.isArray( config.scope ) ) {
|
||||
console.warn(
|
||||
'Invalid scope: scope must be an array of type WPBlockVariationScope.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes validation
|
||||
*/
|
||||
// attributes
|
||||
if (
|
||||
config.attributes !== undefined &&
|
||||
typeof config.attributes !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid attributes: attributes must be an object.' );
|
||||
}
|
||||
// attributes.query
|
||||
if (
|
||||
config.attributes?.query !== undefined &&
|
||||
typeof config.attributes.query !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid query: query must be an object.' );
|
||||
}
|
||||
// attributes.query.offset
|
||||
if (
|
||||
config.attributes?.query?.offset !== undefined &&
|
||||
typeof config.attributes.query.offset !== 'number'
|
||||
) {
|
||||
console.warn( 'Invalid offset: offset must be a number.' );
|
||||
}
|
||||
// attributes.query.order
|
||||
if (
|
||||
config.attributes?.query?.order !== undefined &&
|
||||
typeof config.attributes.query.order !== 'string'
|
||||
) {
|
||||
console.warn( 'Invalid order: order must be a string.' );
|
||||
}
|
||||
// attributes.query.orderBy
|
||||
if (
|
||||
config.attributes?.query?.orderBy !== undefined &&
|
||||
typeof config.attributes.query.orderBy !== 'string'
|
||||
) {
|
||||
console.warn( 'Invalid orderBy: orderBy must be a string.' );
|
||||
}
|
||||
// attributes.query.pages
|
||||
if (
|
||||
config.attributes?.query?.pages !== undefined &&
|
||||
typeof config.attributes.query.pages !== 'number'
|
||||
) {
|
||||
console.warn( 'Invalid pages: pages must be a number.' );
|
||||
}
|
||||
// attributes.query.perPage
|
||||
if (
|
||||
config.attributes?.query?.perPage !== undefined &&
|
||||
typeof config.attributes.query.perPage !== 'number'
|
||||
) {
|
||||
console.warn( 'Invalid perPage: perPage must be a number.' );
|
||||
}
|
||||
// attributes.query.search
|
||||
if (
|
||||
config.attributes?.query?.search !== undefined &&
|
||||
typeof config.attributes.query.search !== 'string'
|
||||
) {
|
||||
console.warn( 'Invalid search: search must be a string.' );
|
||||
}
|
||||
// attributes.query.taxQuery
|
||||
if (
|
||||
config.attributes?.query?.taxQuery !== undefined &&
|
||||
typeof config.attributes.query.taxQuery !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid taxQuery: taxQuery must be an object.' );
|
||||
}
|
||||
// attributes.query.featured
|
||||
if (
|
||||
config.attributes?.query?.featured !== undefined &&
|
||||
typeof config.attributes.query.featured !== 'boolean'
|
||||
) {
|
||||
console.warn( 'Invalid featured: featured must be a boolean.' );
|
||||
}
|
||||
// attributes.query.timeFrame
|
||||
if (
|
||||
config.attributes?.query?.timeFrame !== undefined &&
|
||||
typeof config.attributes.query.timeFrame !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid timeFrame: timeFrame must be an object.' );
|
||||
}
|
||||
// attributes.query.woocommerceOnSale
|
||||
if (
|
||||
config.attributes?.query?.woocommerceOnSale !== undefined &&
|
||||
typeof config.attributes.query.woocommerceOnSale !== 'boolean'
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid woocommerceOnSale: woocommerceOnSale must be a boolean.'
|
||||
);
|
||||
}
|
||||
// attributes.query.woocommerceStockStatus
|
||||
if (
|
||||
config.attributes?.query?.woocommerceStockStatus !== undefined &&
|
||||
! Array.isArray( config.attributes.query.woocommerceStockStatus )
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid woocommerceStockStatus: woocommerceStockStatus must be an array.'
|
||||
);
|
||||
}
|
||||
// attributes.query.woocommerceAttributes
|
||||
if (
|
||||
config.attributes?.query?.woocommerceAttributes !== undefined &&
|
||||
! Array.isArray( config.attributes.query.woocommerceAttributes )
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid woocommerceAttributes: woocommerceAttributes must be an array.'
|
||||
);
|
||||
}
|
||||
// attributes.query.woocommerceHandPickedProducts
|
||||
if (
|
||||
config.attributes?.query?.woocommerceHandPickedProducts !== undefined &&
|
||||
! Array.isArray( config.attributes.query.woocommerceHandPickedProducts )
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid woocommerceHandPickedProducts: woocommerceHandPickedProducts must be an array.'
|
||||
);
|
||||
}
|
||||
// attributes.query.priceRange
|
||||
if (
|
||||
config.attributes?.query?.priceRange !== undefined &&
|
||||
typeof config.attributes.query.priceRange !== 'object'
|
||||
) {
|
||||
console.warn( 'Invalid priceRange: priceRange must be an object.' );
|
||||
}
|
||||
// attributes.displayLayout
|
||||
if (
|
||||
config.attributes?.displayLayout !== undefined &&
|
||||
typeof config.attributes.displayLayout !== 'object'
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid displayLayout: displayLayout must be an object.'
|
||||
);
|
||||
}
|
||||
// attributes.hideControls
|
||||
if (
|
||||
config.attributes?.hideControls !== undefined &&
|
||||
! Array.isArray( config.attributes.hideControls )
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid hideControls: hideControls must be an array of strings.'
|
||||
);
|
||||
}
|
||||
// attributes.queryContextIncludes
|
||||
if (
|
||||
config.attributes?.queryContextIncludes !== undefined &&
|
||||
! Array.isArray( config.attributes.queryContextIncludes )
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid queryContextIncludes: queryContextIncludes must be an array of strings.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview validation
|
||||
*/
|
||||
if ( config.preview !== undefined ) {
|
||||
// preview
|
||||
if ( typeof config.preview !== 'object' || config.preview === null ) {
|
||||
console.warn( 'Invalid preview: preview must be an object.' );
|
||||
}
|
||||
// preview.setPreviewState
|
||||
if (
|
||||
config.preview.setPreviewState !== undefined &&
|
||||
typeof config.preview.setPreviewState !== 'function'
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid preview: setPreviewState must be a function.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( config.preview.initialPreviewState !== undefined ) {
|
||||
// preview.initialPreviewState
|
||||
if ( typeof config.preview.initialPreviewState !== 'object' ) {
|
||||
console.warn(
|
||||
'Invalid preview: initialPreviewState must be an object.'
|
||||
);
|
||||
}
|
||||
// preview.initialPreviewState.isPreview
|
||||
if (
|
||||
typeof config.preview.initialPreviewState.isPreview !==
|
||||
'boolean'
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid preview: preview.isPreview must be a boolean.'
|
||||
);
|
||||
}
|
||||
// preview.initialPreviewState.previewMessage
|
||||
if (
|
||||
typeof config.preview.initialPreviewState.previewMessage !==
|
||||
'string'
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid preview: preview.previewMessage must be a string.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new collection for the Product Collection block.
|
||||
*
|
||||
* 🚨🚨🚨 WARNING: This is an experimental API and is subject to change without notice.
|
||||
*
|
||||
* @param {ProductCollectionConfig} config The configuration of new collection.
|
||||
*/
|
||||
export const __experimentalRegisterProductCollection = (
|
||||
config: ProductCollectionConfig
|
||||
) => {
|
||||
// If the config is invalid, return early.
|
||||
if ( ! isValidCollectionConfig( config ) ) {
|
||||
console.error(
|
||||
'Collection could not be registered due to invalid configuration.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { preview: { setPreviewState, initialPreviewState } = {} } = config;
|
||||
|
||||
const isActive = (
|
||||
blockAttrs: BlockAttributes,
|
||||
variationAttributes: BlockAttributes
|
||||
) => {
|
||||
return blockAttrs.collection === variationAttributes.collection;
|
||||
};
|
||||
|
||||
const query = config.attributes?.query || {};
|
||||
/**
|
||||
* As we don't allow collections to change "inherit" attribute,
|
||||
* We always need to hide the inherit control.
|
||||
*/
|
||||
const hideControls = [
|
||||
...new Set( [
|
||||
CoreFilterNames.INHERIT,
|
||||
...( config.attributes?.hideControls || [] ),
|
||||
] ),
|
||||
];
|
||||
const collectionConfigWithoutExtraArgs = {
|
||||
name: config.name,
|
||||
title: config.title,
|
||||
description: config.description,
|
||||
category: config.category,
|
||||
keywords: config.keywords,
|
||||
icon: config.icon,
|
||||
example: config.example,
|
||||
scope: config.scope,
|
||||
attributes: {
|
||||
query: {
|
||||
...DEFAULT_QUERY,
|
||||
...( query.offset !== undefined && { offset: query.offset } ),
|
||||
...( query.order !== undefined && { order: query.order } ),
|
||||
...( query.orderBy !== undefined && {
|
||||
orderBy: query.orderBy,
|
||||
} ),
|
||||
...( query.pages !== undefined && { pages: query.pages } ),
|
||||
...( query.perPage !== undefined && {
|
||||
perPage: query.perPage,
|
||||
} ),
|
||||
...( query.search !== undefined && { search: query.search } ),
|
||||
...( query.taxQuery !== undefined && {
|
||||
taxQuery: query.taxQuery,
|
||||
} ),
|
||||
...( query.featured !== undefined && {
|
||||
featured: query.featured,
|
||||
} ),
|
||||
...( query.timeFrame !== undefined && {
|
||||
timeFrame: query.timeFrame,
|
||||
} ),
|
||||
...( query.woocommerceOnSale !== undefined && {
|
||||
woocommerceOnSale: query.woocommerceOnSale,
|
||||
} ),
|
||||
...( query.woocommerceStockStatus !== undefined && {
|
||||
woocommerceStockStatus: query.woocommerceStockStatus,
|
||||
} ),
|
||||
...( query.woocommerceAttributes !== undefined && {
|
||||
woocommerceAttributes: query.woocommerceAttributes,
|
||||
} ),
|
||||
...( query.woocommerceHandPickedProducts !== undefined && {
|
||||
woocommerceHandPickedProducts:
|
||||
query.woocommerceHandPickedProducts,
|
||||
} ),
|
||||
...( query.priceRange !== undefined && {
|
||||
priceRange: query.priceRange,
|
||||
} ),
|
||||
},
|
||||
displayLayout: config.attributes?.displayLayout,
|
||||
hideControls,
|
||||
queryContextIncludes: config.attributes?.queryContextIncludes,
|
||||
// collection should be set to the name of the collection i.e. config.name
|
||||
collection: config.name,
|
||||
// Collections should always have inherit set to false.
|
||||
inherit: false,
|
||||
},
|
||||
/**
|
||||
* We always want following properties to be set to the default values.
|
||||
*/
|
||||
innerBlocks: config.innerBlocks || INNER_BLOCKS_TEMPLATE,
|
||||
isActive,
|
||||
isDefault: false,
|
||||
} as BlockVariation;
|
||||
|
||||
/**
|
||||
* If setPreviewState or initialPreviewState is provided, inject the setPreviewState & initialPreviewState props.
|
||||
* This is useful for handling preview mode in the editor.
|
||||
*/
|
||||
if ( setPreviewState || initialPreviewState ) {
|
||||
const withSetPreviewState =
|
||||
< T extends EditorBlock< T > >( BlockEdit: ElementType ) =>
|
||||
( props: BlockEditProps< ProductCollectionAttributes > ) => {
|
||||
// If collection name does not match, return the original BlockEdit component.
|
||||
if (
|
||||
props.attributes.collection !==
|
||||
collectionConfigWithoutExtraArgs.name
|
||||
) {
|
||||
return <BlockEdit { ...props } />;
|
||||
}
|
||||
|
||||
// Otherwise, inject the setPreviewState & initialPreviewState props.
|
||||
return (
|
||||
<BlockEdit
|
||||
{ ...props }
|
||||
preview={ {
|
||||
setPreviewState,
|
||||
initialPreviewState,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
addFilter(
|
||||
'editor.BlockEdit',
|
||||
collectionConfigWithoutExtraArgs.name,
|
||||
withSetPreviewState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily utilizing `wp.blocks.registerBlockVariation` directly instead of importing
|
||||
* from `@wordpress/blocks` to mitigate the increase in the number of JavaScript files
|
||||
* loaded on the frontend, specifically on the /shop page.
|
||||
*
|
||||
* TODO - Future Improvement:
|
||||
* It is recommended to encapsulate the `registerProductCollection` function within a new
|
||||
* package that is exclusively loaded in the editor. This strategy will eliminate
|
||||
* the need to directly use `wp.blocks.registerBlockVariation`.
|
||||
*/
|
||||
if ( wp?.blocks?.registerBlockVariation ) {
|
||||
wp.blocks.registerBlockVariation( BLOCK_NAME, {
|
||||
...collectionConfigWithoutExtraArgs,
|
||||
attributes: {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
...collectionConfigWithoutExtraArgs.attributes,
|
||||
query: {
|
||||
...DEFAULT_QUERY,
|
||||
...collectionConfigWithoutExtraArgs.attributes?.query,
|
||||
},
|
||||
displayLayout: {
|
||||
...DEFAULT_ATTRIBUTES.displayLayout,
|
||||
...collectionConfigWithoutExtraArgs.attributes
|
||||
?.displayLayout,
|
||||
},
|
||||
},
|
||||
} );
|
||||
}
|
||||
};
|
|
@ -8,10 +8,7 @@ import { Icon, chartBar } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_PRODUCT_TEMPLATE,
|
||||
} from '../constants';
|
||||
import { INNER_BLOCKS_PRODUCT_TEMPLATE } from '../constants';
|
||||
import { CoreCollectionNames, CoreFilterNames } from '../types';
|
||||
|
||||
const collection = {
|
||||
|
@ -24,22 +21,18 @@ const collection = {
|
|||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 5,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: false,
|
||||
orderBy: 'popularity',
|
||||
order: 'desc',
|
||||
perPage: 5,
|
||||
pages: 1,
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [ CoreFilterNames.INHERIT, CoreFilterNames.ORDER ],
|
||||
hideControls: [ CoreFilterNames.ORDER ],
|
||||
};
|
||||
|
||||
const heading: InnerBlockTemplate = [
|
||||
|
|
|
@ -8,10 +8,7 @@ import { Icon, starFilled } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_PRODUCT_TEMPLATE,
|
||||
} from '../constants';
|
||||
import { INNER_BLOCKS_PRODUCT_TEMPLATE } from '../constants';
|
||||
import { CoreCollectionNames, CoreFilterNames } from '../types';
|
||||
|
||||
const collection = {
|
||||
|
@ -24,21 +21,17 @@ const collection = {
|
|||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 5,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: false,
|
||||
featured: true,
|
||||
perPage: 5,
|
||||
pages: 1,
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [ CoreFilterNames.INHERIT, CoreFilterNames.FEATURED ],
|
||||
hideControls: [ CoreFilterNames.FEATURED ],
|
||||
};
|
||||
|
||||
const heading: InnerBlockTemplate = [
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { select } from '@wordpress/data';
|
||||
import { __experimentalRegisterProductCollection as registerProductCollection } from '@woocommerce/blocks-registry';
|
||||
import {
|
||||
// @ts-expect-error Type definition is missing
|
||||
store as blocksStore,
|
||||
type BlockVariation,
|
||||
BlockAttributes,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,6 @@ import topRated from './top-rated';
|
|||
import bestSellers from './best-sellers';
|
||||
import onSale from './on-sale';
|
||||
import featured from './featured';
|
||||
import registerProductCollection from './register-product-collection';
|
||||
|
||||
const collections: BlockVariation[] = [
|
||||
productCollection,
|
||||
|
@ -32,19 +31,9 @@ const collections: BlockVariation[] = [
|
|||
];
|
||||
|
||||
export const registerCollections = () => {
|
||||
collections.forEach( ( collection ) => {
|
||||
const isActive = (
|
||||
blockAttrs: BlockAttributes,
|
||||
variationAttributes: BlockAttributes
|
||||
) => {
|
||||
return blockAttrs.collection === variationAttributes.collection;
|
||||
};
|
||||
|
||||
registerProductCollection( {
|
||||
isActive,
|
||||
...collection,
|
||||
} );
|
||||
} );
|
||||
collections.forEach( ( collection ) =>
|
||||
registerProductCollection( collection )
|
||||
);
|
||||
};
|
||||
|
||||
export const getCollectionByName = ( collectionName?: CollectionName ) => {
|
||||
|
|
|
@ -8,10 +8,7 @@ import { Icon, calendar } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_PRODUCT_TEMPLATE,
|
||||
} from '../constants';
|
||||
import { INNER_BLOCKS_PRODUCT_TEMPLATE } from '../constants';
|
||||
import {
|
||||
CoreCollectionNames,
|
||||
CoreFilterNames,
|
||||
|
@ -28,15 +25,12 @@ const collection = {
|
|||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 5,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: false,
|
||||
orderBy: 'date',
|
||||
order: 'desc',
|
||||
perPage: 5,
|
||||
|
@ -46,8 +40,7 @@ const attributes = {
|
|||
value: '-7 days',
|
||||
},
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [ CoreFilterNames.INHERIT, CoreFilterNames.ORDER ],
|
||||
hideControls: [ CoreFilterNames.ORDER ],
|
||||
};
|
||||
|
||||
const heading: InnerBlockTemplate = [
|
||||
|
|
|
@ -8,10 +8,7 @@ import { Icon, percent } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_PRODUCT_TEMPLATE,
|
||||
} from '../constants';
|
||||
import { INNER_BLOCKS_PRODUCT_TEMPLATE } from '../constants';
|
||||
import { CoreCollectionNames, CoreFilterNames } from '../types';
|
||||
|
||||
const collection = {
|
||||
|
@ -27,21 +24,17 @@ const collection = {
|
|||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 5,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: false,
|
||||
woocommerceOnSale: true,
|
||||
perPage: 5,
|
||||
pages: 1,
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [ CoreFilterNames.INHERIT, CoreFilterNames.ON_SALE ],
|
||||
hideControls: [ CoreFilterNames.ON_SALE ],
|
||||
};
|
||||
|
||||
const heading: InnerBlockTemplate = [
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Icon, loop } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_ATTRIBUTES, INNER_BLOCKS_TEMPLATE } from '../constants';
|
||||
import { INNER_BLOCKS_TEMPLATE } from '../constants';
|
||||
import { CoreCollectionNames } from '../types';
|
||||
|
||||
const collection = {
|
||||
|
@ -21,19 +21,9 @@ const collection = {
|
|||
scope: [],
|
||||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [],
|
||||
};
|
||||
|
||||
const innerBlocks: InnerBlockTemplate[] = INNER_BLOCKS_TEMPLATE;
|
||||
|
||||
export default {
|
||||
...collection,
|
||||
attributes,
|
||||
innerBlocks,
|
||||
};
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockVariation, registerBlockVariation } from '@wordpress/blocks';
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { EditorBlock } from '@woocommerce/types';
|
||||
import type { ElementType } from '@wordpress/element';
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import blockJson from '../block.json';
|
||||
import {
|
||||
SetPreviewState,
|
||||
PreviewState,
|
||||
ProductCollectionAttributes,
|
||||
} from '../types';
|
||||
|
||||
export interface ProductCollectionConfig extends BlockVariation {
|
||||
preview?: {
|
||||
setPreviewState?: SetPreviewState;
|
||||
initialPreviewState?: PreviewState;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new collection for the Product Collection block.
|
||||
*
|
||||
* @param {ProductCollectionConfig} blockVariationArgs The configuration of new collection.
|
||||
*/
|
||||
const registerProductCollection = ( {
|
||||
preview: { setPreviewState, initialPreviewState } = {},
|
||||
...blockVariationArgs
|
||||
}: ProductCollectionConfig ) => {
|
||||
/**
|
||||
* If setPreviewState or initialPreviewState is provided, inject the setPreviewState & initialPreviewState props.
|
||||
* This is useful for handling preview mode in the editor.
|
||||
*/
|
||||
if ( setPreviewState || initialPreviewState ) {
|
||||
const withSetPreviewState =
|
||||
< T extends EditorBlock< T > >( BlockEdit: ElementType ) =>
|
||||
( props: BlockEditProps< ProductCollectionAttributes > ) => {
|
||||
// If collection name does not match, return the original BlockEdit component.
|
||||
if ( props.attributes.collection !== blockVariationArgs.name ) {
|
||||
return <BlockEdit { ...props } />;
|
||||
}
|
||||
|
||||
// Otherwise, inject the setPreviewState & initialPreviewState props.
|
||||
return (
|
||||
<BlockEdit
|
||||
{ ...props }
|
||||
preview={ {
|
||||
setPreviewState,
|
||||
initialPreviewState,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
addFilter(
|
||||
'editor.BlockEdit',
|
||||
blockVariationArgs.name,
|
||||
withSetPreviewState
|
||||
);
|
||||
}
|
||||
|
||||
registerBlockVariation( blockJson.name, {
|
||||
...blockVariationArgs,
|
||||
} );
|
||||
};
|
||||
|
||||
export default registerProductCollection;
|
|
@ -8,10 +8,7 @@ import { Icon, starEmpty } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_PRODUCT_TEMPLATE,
|
||||
} from '../constants';
|
||||
import { INNER_BLOCKS_PRODUCT_TEMPLATE } from '../constants';
|
||||
import { CoreCollectionNames, CoreFilterNames } from '../types';
|
||||
|
||||
const collection = {
|
||||
|
@ -27,22 +24,18 @@ const collection = {
|
|||
};
|
||||
|
||||
const attributes = {
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 5,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: false,
|
||||
orderBy: 'rating',
|
||||
order: 'desc',
|
||||
perPage: 5,
|
||||
pages: 1,
|
||||
},
|
||||
collection: collection.name,
|
||||
hideControls: [ CoreFilterNames.INHERIT, CoreFilterNames.ORDER ],
|
||||
hideControls: [ CoreFilterNames.ORDER ],
|
||||
};
|
||||
|
||||
const heading: InnerBlockTemplate = [
|
||||
|
|
|
@ -1,30 +1,31 @@
|
|||
/**
|
||||
* Purpose of this file:
|
||||
* This file defines constants for use in `plugins/woocommerce-blocks/assets/js/blocks-registry/product-collection/register-product-collection.tsx`.
|
||||
* By isolating constants here, we avoid loading unnecessary JS file on the frontend (e.g., the /shop page), enhancing site performance.
|
||||
*
|
||||
* Context: https://github.com/woocommerce/woocommerce/pull/48141#issuecomment-2208770592.
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { objectOmit } from '@woocommerce/utils';
|
||||
import {
|
||||
type InnerBlockTemplate,
|
||||
createBlock,
|
||||
// @ts-expect-error Type definitions for this function are missing in Guteberg
|
||||
createBlocksFromInnerBlocksTemplate,
|
||||
} from '@wordpress/blocks';
|
||||
import type { InnerBlockTemplate } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import blockJson from './block.json';
|
||||
import {
|
||||
ProductCollectionAttributes,
|
||||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
ProductCollectionQuery,
|
||||
ProductCollectionDisplayLayout,
|
||||
LayoutOptions,
|
||||
} from './types';
|
||||
import { ImageSizing } from '../../atomic/blocks/product-elements/image/types';
|
||||
import { VARIATION_NAME as PRODUCT_TITLE_ID } from './variations/elements/product-title';
|
||||
import { getDefaultValueOfInheritQueryFromTemplate } from './utils';
|
||||
import blockJson from './block.json';
|
||||
|
||||
export const PRODUCT_COLLECTION_BLOCK_NAME = blockJson.name;
|
||||
const PRODUCT_TITLE_NAME = `${ PRODUCT_COLLECTION_BLOCK_NAME }/product-title`;
|
||||
|
||||
export const STOCK_STATUS_OPTIONS = getSetting< Record< string, string > >(
|
||||
'stockStatusOptions',
|
||||
|
@ -51,7 +52,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = {
|
|||
orderBy: 'title',
|
||||
search: '',
|
||||
exclude: [],
|
||||
inherit: null,
|
||||
inherit: false,
|
||||
taxQuery: {},
|
||||
isProductCollectionBlock: true,
|
||||
featured: false,
|
||||
|
@ -82,25 +83,6 @@ export const DEFAULT_ATTRIBUTES: Pick<
|
|||
forcePageReload: false,
|
||||
};
|
||||
|
||||
export const getDefaultQuery = (
|
||||
currentQuery: ProductCollectionQuery
|
||||
): ProductCollectionQuery => ( {
|
||||
...currentQuery,
|
||||
orderBy: DEFAULT_QUERY.orderBy as TProductCollectionOrderBy,
|
||||
order: DEFAULT_QUERY.order as TProductCollectionOrder,
|
||||
inherit: getDefaultValueOfInheritQueryFromTemplate(),
|
||||
} );
|
||||
|
||||
export const getDefaultDisplayLayout = () =>
|
||||
DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout;
|
||||
|
||||
export const getDefaultSettings = (
|
||||
currentAttributes: ProductCollectionAttributes
|
||||
): Partial< ProductCollectionAttributes > => ( {
|
||||
displayLayout: getDefaultDisplayLayout(),
|
||||
query: getDefaultQuery( currentAttributes.query ),
|
||||
} );
|
||||
|
||||
export const DEFAULT_FILTERS: Pick<
|
||||
ProductCollectionQuery,
|
||||
| 'woocommerceOnSale'
|
||||
|
@ -151,7 +133,7 @@ export const INNER_BLOCKS_PRODUCT_TEMPLATE: InnerBlockTemplate = [
|
|||
},
|
||||
},
|
||||
isLink: true,
|
||||
__woocommerceNamespace: PRODUCT_TITLE_ID,
|
||||
__woocommerceNamespace: PRODUCT_TITLE_NAME,
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -191,16 +173,3 @@ export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
|
|||
INNER_BLOCKS_PAGINATION_TEMPLATE,
|
||||
INNER_BLOCKS_NO_RESULTS_TEMPLATE,
|
||||
];
|
||||
|
||||
export const getDefaultProductCollection = () =>
|
||||
createBlock(
|
||||
blockJson.name,
|
||||
{
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: getDefaultValueOfInheritQueryFromTemplate(),
|
||||
},
|
||||
},
|
||||
createBlocksFromInnerBlocksTemplate( INNER_BLOCKS_TEMPLATE )
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { type CollectionName, CoreCollectionNames } from '../types';
|
||||
import blockJson from '../block.json';
|
||||
import { getCollectionByName } from '../collections';
|
||||
import { getDefaultProductCollection } from '../constants';
|
||||
import { getDefaultProductCollection } from '../utils';
|
||||
|
||||
type CollectionButtonProps = {
|
||||
active?: boolean;
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { DisplayLayoutControlProps } from '../../types';
|
||||
import { getDefaultDisplayLayout } from '../../constants';
|
||||
import { getDefaultDisplayLayout } from '../../utils';
|
||||
|
||||
const columnsLabel = __( 'Columns', 'woocommerce' );
|
||||
const toggleLabel = __( 'Responsive', 'woocommerce' );
|
||||
|
|
|
@ -33,8 +33,7 @@ import {
|
|||
CoreFilterNames,
|
||||
FilterName,
|
||||
} from '../../types';
|
||||
import { setQueryAttribute } from '../../utils';
|
||||
import { getDefaultSettings } from '../../constants';
|
||||
import { setQueryAttribute, getDefaultSettings } from '../../utils';
|
||||
import UpgradeNotice from './upgrade-notice';
|
||||
import ColumnsControl from './columns-control';
|
||||
import InheritQueryControl from './inherit-query-control';
|
||||
|
@ -69,13 +68,15 @@ const ProductCollectionInspectorControls = (
|
|||
filter,
|
||||
} );
|
||||
|
||||
const inherit = query?.inherit || false;
|
||||
|
||||
const shouldShowFilter = prepareShouldShowFilter( hideControls );
|
||||
|
||||
const isArchiveTemplate =
|
||||
tracksLocation === 'product-catalog' ||
|
||||
tracksLocation === 'product-archive';
|
||||
|
||||
const showQueryControls = query?.inherit === false;
|
||||
const showQueryControls = inherit === false;
|
||||
const showInheritQueryControl =
|
||||
isArchiveTemplate && shouldShowFilter( CoreFilterNames.INHERIT );
|
||||
const showOrderControl =
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
QueryControlProps,
|
||||
CoreFilterNames,
|
||||
} from '../../types';
|
||||
import { getDefaultQuery } from '../../constants';
|
||||
import { getDefaultQuery } from '../../utils';
|
||||
|
||||
const orderOptions = [
|
||||
{
|
||||
|
|
|
@ -120,15 +120,6 @@ const ProductCollectionContent = ( {
|
|||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* If inherit is not a boolean, then we haven't set default attributes yet.
|
||||
* We don't wanna render anything until default attributes are set.
|
||||
* Default attributes are set in the useEffect above.
|
||||
*/
|
||||
if ( typeof attributes?.query?.inherit !== 'boolean' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If default attributes are not set, we don't wanna render anything.
|
||||
* Default attributes are set in the useEffect above.
|
||||
|
@ -152,7 +143,7 @@ const ProductCollectionContent = ( {
|
|||
attributes.__privatePreviewState?.previewMessage
|
||||
}
|
||||
className="wc-block-product-collection__preview-button"
|
||||
data-test-id="product-collection-preview-button"
|
||||
data-testid="product-collection-preview-button"
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
|
|
|
@ -60,7 +60,7 @@ export interface PriceRange {
|
|||
|
||||
export interface ProductCollectionQuery {
|
||||
exclude: string[];
|
||||
inherit: boolean | null;
|
||||
inherit: boolean;
|
||||
offset: number;
|
||||
order: TProductCollectionOrder;
|
||||
orderBy: TProductCollectionOrderBy;
|
||||
|
|
|
@ -8,17 +8,30 @@ import { isWpVersion } from '@woocommerce/settings';
|
|||
import type { BlockEditProps, Block } from '@wordpress/blocks';
|
||||
import { useLayoutEffect } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
createBlock,
|
||||
// @ts-expect-error Type definitions for this function are missing in Guteberg
|
||||
createBlocksFromInnerBlocksTemplate,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
PreviewState,
|
||||
ProductCollectionAttributes,
|
||||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
ProductCollectionQuery,
|
||||
ProductCollectionDisplayLayout,
|
||||
PreviewState,
|
||||
SetPreviewState,
|
||||
} from './types';
|
||||
import { coreQueryPaginationBlockName } from './constants';
|
||||
import {
|
||||
coreQueryPaginationBlockName,
|
||||
DEFAULT_QUERY,
|
||||
DEFAULT_ATTRIBUTES,
|
||||
INNER_BLOCKS_TEMPLATE,
|
||||
} from './constants';
|
||||
import blockJson from './block.json';
|
||||
import {
|
||||
LocationType,
|
||||
|
@ -206,3 +219,35 @@ export const useSetPreviewState = ( {
|
|||
setPreviewState,
|
||||
] );
|
||||
};
|
||||
|
||||
export const getDefaultQuery = (
|
||||
currentQuery: ProductCollectionQuery
|
||||
): ProductCollectionQuery => ( {
|
||||
...currentQuery,
|
||||
orderBy: DEFAULT_QUERY.orderBy as TProductCollectionOrderBy,
|
||||
order: DEFAULT_QUERY.order as TProductCollectionOrder,
|
||||
inherit: getDefaultValueOfInheritQueryFromTemplate(),
|
||||
} );
|
||||
|
||||
export const getDefaultDisplayLayout = () =>
|
||||
DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout;
|
||||
|
||||
export const getDefaultSettings = (
|
||||
currentAttributes: ProductCollectionAttributes
|
||||
): Partial< ProductCollectionAttributes > => ( {
|
||||
displayLayout: getDefaultDisplayLayout(),
|
||||
query: getDefaultQuery( currentAttributes.query ),
|
||||
} );
|
||||
|
||||
export const getDefaultProductCollection = () =>
|
||||
createBlock(
|
||||
blockJson.name,
|
||||
{
|
||||
...DEFAULT_ATTRIBUTES,
|
||||
query: {
|
||||
...DEFAULT_ATTRIBUTES.query,
|
||||
inherit: getDefaultValueOfInheritQueryFromTemplate(),
|
||||
},
|
||||
},
|
||||
createBlocksFromInnerBlocksTemplate( INNER_BLOCKS_TEMPLATE )
|
||||
);
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
# Register Product Collection
|
||||
|
||||
The `__experimentalRegisterProductCollection` function is part of the `@woocommerce/blocks-registry` package. This function allows 3PDs to register a new collection. This function accepts most of the arguments that are accepted by [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation).
|
||||
|
||||
> [!WARNING]
|
||||
> It's experimental and may change in the future. Please use it with caution.
|
||||
|
||||
**There are two ways to use this function:**
|
||||
|
||||
1. Using `@woocommerce/dependency-extraction-webpack-plugin` in a Webpack configuration: This will allow you to import the function from the package & use it in your code. For example:
|
||||
|
||||
```tsx
|
||||
import { __experimentalRegisterProductCollection } from "@woocommerce/blocks-registry";
|
||||
```
|
||||
|
||||
2. Using the global `wc` object: This will allow you to use the function using the global JS object without importing it. For example:
|
||||
|
||||
```tsx
|
||||
wc.wcBlocksRegistry.__experimentalRegisterProductCollection({
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> The first method is recommended if you are using Webpack.
|
||||
|
||||
## Defining a Collection
|
||||
|
||||
We will explain important arguments that can be passed to `__experimentalRegisterProductCollection`. For other arguments, you can refer to the [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation) documentation.
|
||||
|
||||
A Collection is defined by an object that can contain the following fields:
|
||||
|
||||
- `name` (type `string`): A unique and machine-readable collection name. We recommend using the format `<plugin-name>/product-collection/<collection-name>`. Both `<plugin-name>` and `<collection-name>` should consist only of alphanumeric characters and hyphens (e.g., `my-plugin/product-collection/my-collection`).
|
||||
- `title` (type `string`): The title of the collection, which will be displayed in various places including the block inserter and collection chooser.
|
||||
- `description` (optional, type `string`): A human-readable description of the collection.
|
||||
- `innerBlocks` (optional, type `Array[]`): An array of inner blocks that will be added to the collection. If not provided, the default inner blocks will be used.
|
||||
- `isDefault`: ⚠️ It's set to `false` for all collections. 3PDs doesn't need to pass this argument.
|
||||
- `isActive`: ⚠️ It will be managed by us. 3PDs doesn't need to pass this argument.
|
||||
|
||||
### Attributes
|
||||
|
||||
Attributes are the properties that define the behavior of the collection. All the attributes are *optional*. Here are some of the important attributes that can be passed to `__experimentalRegisterProductCollection`:
|
||||
|
||||
- `query` (type `object`): The query object that defines the query for the collection. It can contain the following fields:
|
||||
- `offset` (type `number`): The number of items to offset the query by.
|
||||
- `order` (type `string`): The order of the query. Accepted values are `asc` and `desc`.
|
||||
- `orderBy` (type `string`): The field to order the query by.
|
||||
- `pages` (type `number`): The number of pages to query.
|
||||
- `perPage` (type `number`): The number of products per page.
|
||||
- `search` (type `string`): The search term to query by.
|
||||
- `taxQuery` (type `object`): The tax query to filter the query by. For example, if you wanna fetch products with category `Clothing` & `Accessories` and tag `Summer` then you can pass `taxQuery` as `{"product_cat":[20,17],"product_tag":[36]}`. Where array values are the term IDs.
|
||||
- `featured` (type `boolean`): Whether to query for featured items.
|
||||
- `timeFrame` (type `object`): The time frame to query by.
|
||||
- `operator` (type `string`): The operator to use for the time frame query. Accepted values are `in` and `not-in`.
|
||||
- `value` (type `string`): The value to query by. It should be a valid date string that PHP's `strtotime` function can parse.
|
||||
- `woocommerceOnSale` (type `boolean`): Whether to query for items on sale.
|
||||
- `woocommerceStockStatus` (type `array`): The stock status to query by. Some of the accepted values are `instock`, `outofstock`, `onbackorder`.
|
||||
- `woocommerceAttributes` (type `array`): The attributes to query by.
|
||||
- For example, if you wanna fetch products with color `blue` & `gray` and size `Large` then you can pass `woocommerceAttributes` as `[{"termId":23,"taxonomy":"pa_color"},{"termId":26,"taxonomy":"pa_size"},{"termId":29,"taxonomy":"pa_color"}]`.
|
||||
- `woocommerceHandPickedProducts` (type `array`): The hand-picked products to query by. It should contain the product IDs.
|
||||
- `priceRange` (type `object`): The price range to query by.
|
||||
- `min` (type `number`): The minimum price.
|
||||
- `max` (type `number`): The maximum price.
|
||||
|
||||
- `displayLayout` (type `object`): The display layout object that defines the layout of the collection. It can contain the following fields:
|
||||
- `type` (type `string`): The type of layout. Accepted values are `grid` and `stack`.
|
||||
- `columns` (type `number`): The number of columns to display.
|
||||
- `shrinkColumns` (type `boolean`): Whether the layout should be responsive.
|
||||
- `hideControls` (type `array`): The controls to hide.
|
||||
|
||||
#### Preview Attribute
|
||||
|
||||
The `preview` attribute is optional, and it is used to set the preview state of the collection. It can contain the following fields:
|
||||
|
||||
- `initialPreviewState` (type `object`): The initial preview state of the collection. It can contain the following fields:
|
||||
- `isPreview` (type `boolean`): Whether the collection is in preview mode.
|
||||
- `previewMessage` (type `string`): The message to be displayed in the Tooltip when the user hovers over the preview label.
|
||||
- `setPreviewState` (optional, type `function`): The function to set the preview state of the collection. It receives the following arguments:
|
||||
- `setState` (type `function`): The function to set the preview state. You can pass a new preview state to this function containing `isPreview` and `previewMessage`.
|
||||
- `attributes` (type `object`): The current attributes of the collection.
|
||||
- `location` (type `object`): The location of the collection. Accepted values are `product`, `archive`, `cart`, `order`, `site`.
|
||||
|
||||
For more info, you may check PR #46369, in which the Preview feature was added
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Registering a new collection
|
||||
|
||||
```tsx
|
||||
__experimentalRegisterProductCollection({
|
||||
name: "your-plugin-name/product-collection/my-custom-collection",
|
||||
title: "My Custom Collection",
|
||||
icon: "games",
|
||||
description: "This is a custom collection.",
|
||||
keywords: ["custom collection", "product collection"],
|
||||
});
|
||||
```
|
||||
|
||||
As you can see in the example above, we are registering a new collection with the name `woocommerce/product-collection/my-custom-collection` & title `My Custom Collection`. Here is screenshot of how it will look like:
|
||||
![image](https://github.com/woocommerce/woocommerce/assets/16707866/7fddbc02-a6cd-494e-b2f4-13dd5ef9cf96)
|
||||
|
||||
### Example 2: Register a new collection with a preview
|
||||
|
||||
As you can see below, setting the initial preview state is possible. In the example below, we are setting `isPreview` and `previewMessage`.
|
||||
|
||||
```tsx
|
||||
__experimentalRegisterProductCollection({
|
||||
name: "your-plugin-name/product-collection/my-custom-collection-with-preview",
|
||||
title: "My Custom Collection with Preview",
|
||||
icon: "games",
|
||||
description: "This is a custom collection with preview.",
|
||||
keywords: ["My Custom Collection with Preview", "product collection"],
|
||||
preview: {
|
||||
initialPreviewState: {
|
||||
isPreview: true,
|
||||
previewMessage:
|
||||
"This is a preview message for my custom collection with preview.",
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
query: {
|
||||
perPage: 5,
|
||||
featured: true,
|
||||
},
|
||||
displayLayout: {
|
||||
type: "grid",
|
||||
columns: 3,
|
||||
shrinkColumns: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Here is how it will look like:
|
||||
![image](https://github.com/woocommerce/woocommerce/assets/16707866/5fc1aa20-552a-4e09-b811-08babab46665)
|
||||
|
||||
### Example 3: Advanced usage of preview
|
||||
|
||||
As you can see below, it's also possible to use `setPreviewState` to set the preview state. In the example below, we are setting `initialPreviewState` and using `setPreviewState` to change the preview state after 5 seconds.
|
||||
|
||||
**This example shows:**
|
||||
|
||||
- How to access current attributes and location in the preview state
|
||||
- How to use async operations
|
||||
- We are using `setTimeout` to change the preview state after 5 seconds. You can use any async operation, like fetching data from an API, to decide the preview state.
|
||||
- How to use the cleanup function as a return value
|
||||
- We are returning a cleanup function that will clear the timeout after the component is unmounted. You can use this to clean up any resources you have used in `setPreviewState`.
|
||||
|
||||
```tsx
|
||||
__experimentalRegisterProductCollection({
|
||||
name: "your-plugin-name/product-collection/my-custom-collection-with-advanced-preview",
|
||||
title: "My Custom Collection with Advanced Preview",
|
||||
icon: "games",
|
||||
description: "This is a custom collection with advanced preview.",
|
||||
keywords: [
|
||||
"My Custom Collection with Advanced Preview",
|
||||
"product collection",
|
||||
],
|
||||
preview: {
|
||||
setPreviewState: ({
|
||||
setState,
|
||||
attributes: currentAttributes,
|
||||
location,
|
||||
}) => {
|
||||
// setPreviewState has access to the current attributes and location.
|
||||
// console.log( currentAttributes, location );
|
||||
|
||||
const timeoutID = setTimeout(() => {
|
||||
setState({
|
||||
isPreview: false,
|
||||
previewMessage: "",
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
return () => clearTimeout(timeoutID);
|
||||
},
|
||||
initialPreviewState: {
|
||||
isPreview: true,
|
||||
previewMessage:
|
||||
"This is a preview message for my custom collection with advanced preview.",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Example 4: Collection with inner blocks
|
||||
|
||||
As you can see below, it's also possible to define inner blocks for the collection. In the example below, we are defining inner blocks for the collection.
|
||||
|
||||
```tsx
|
||||
__experimentalRegisterProductCollection({
|
||||
name: "your-plugin-name/product-collection/my-custom-collection-with-inner-blocks",
|
||||
title: "My Custom Collection with Inner Blocks",
|
||||
icon: "games",
|
||||
description: "This is a custom collection with inner blocks.",
|
||||
keywords: ["My Custom Collection with Inner Blocks", "product collection"],
|
||||
innerBlocks: [
|
||||
[
|
||||
"core/heading",
|
||||
{
|
||||
textAlign: "center",
|
||||
level: 2,
|
||||
content: "Title of the collection",
|
||||
},
|
||||
],
|
||||
[
|
||||
"woocommerce/product-template",
|
||||
{},
|
||||
[
|
||||
["woocommerce/product-image"],
|
||||
[
|
||||
"woocommerce/product-price",
|
||||
{
|
||||
textAlign: "center",
|
||||
fontSize: "small",
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
This will create a collection with a heading, product image, and product price. Here is how it will look like:
|
||||
|
||||
![image](https://github.com/woocommerce/woocommerce/assets/16707866/3d92c084-91e9-4872-a898-080b4b93afca)
|
||||
|
||||
> ![TIP]
|
||||
> You can learn more about inner blocks template in the [Inner Blocks](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/#template) documentation.
|
||||
|
||||
> ![TIP]
|
||||
> You can also take a look at how we are defining our core collections at `plugins/woocommerce-blocks/assets/js/blocks/product-collection/collections` directory. Our core collections will also evolve over time.
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: Register Product Collection Tester
|
||||
* Description: A plugin to test the registerProductCollection function from WooCommerce Blocks.
|
||||
* Plugin URI: https://github.com/woocommerce/woocommerce
|
||||
* Author: WooCommerce
|
||||
*
|
||||
*
|
||||
* @package register-product-collection-tester
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Enqueue the JavaScript file.
|
||||
function register_product_collections_script()
|
||||
{
|
||||
wp_enqueue_script(
|
||||
'rpc_register_product_collections',
|
||||
plugins_url('register-product-collection-tester/index.js', __FILE__),
|
||||
array('wp-element', 'wp-blocks', 'wp-i18n', 'wp-components', 'wp-editor', 'wc-blocks'),
|
||||
filemtime(plugin_dir_path(__FILE__) . 'register-product-collection-tester/index.js'),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
add_action('enqueue_block_editor_assets', 'register_product_collections_script');
|
|
@ -0,0 +1,63 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars, no-undef */
|
||||
const { __experimentalRegisterProductCollection } = wc.wcBlocksRegistry;
|
||||
|
||||
// Example 1: Register a new collection.
|
||||
__experimentalRegisterProductCollection( {
|
||||
name: 'woocommerce/product-collection/my-custom-collection',
|
||||
title: 'My Custom Collection',
|
||||
description: 'This is a custom collection.',
|
||||
keywords: [ 'custom collection', 'product collection' ],
|
||||
attributes: {
|
||||
query: {
|
||||
perPage: 5,
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
||||
// Example 2: Register a new collection with a preview.
|
||||
__experimentalRegisterProductCollection( {
|
||||
name: 'woocommerce/product-collection/my-custom-collection-with-preview',
|
||||
title: 'My Custom Collection with Preview',
|
||||
description: 'This is a custom collection with preview.',
|
||||
keywords: [ 'custom collection', 'product collection' ],
|
||||
preview: {
|
||||
initialPreviewState: {
|
||||
isPreview: true,
|
||||
previewMessage:
|
||||
'This is a preview message for my custom collection with preview.',
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
||||
// Example 3: Advanced usage of preview.
|
||||
__experimentalRegisterProductCollection( {
|
||||
name: 'woocommerce/product-collection/my-custom-collection-with-advanced-preview',
|
||||
title: 'My Custom Collection with Advanced Preview',
|
||||
description: 'This is a custom collection with advanced preview.',
|
||||
keywords: [ 'custom collection', 'product collection' ],
|
||||
preview: {
|
||||
setPreviewState: ( {
|
||||
setState,
|
||||
attributes: currentAttributes,
|
||||
location,
|
||||
} ) => {
|
||||
// You can access the current attributes and location.
|
||||
// console.log( 'Current attributes:', currentAttributes );
|
||||
// console.log( 'Location:', location );
|
||||
|
||||
const timeoutID = setTimeout( () => {
|
||||
setState( {
|
||||
isPreview: false,
|
||||
previewMessage: '',
|
||||
} );
|
||||
}, 1000 );
|
||||
|
||||
return () => clearTimeout( timeoutID );
|
||||
},
|
||||
initialPreviewState: {
|
||||
isPreview: true,
|
||||
previewMessage:
|
||||
'This is a preview message for my custom collection with advanced preview.',
|
||||
},
|
||||
},
|
||||
} );
|
|
@ -1204,8 +1204,8 @@ test.describe( 'Product Collection', () => {
|
|||
await pageObject.goToEditorTemplate( path );
|
||||
await pageObject.focusProductCollection();
|
||||
|
||||
const previewButtonLocator = editor.canvas.locator(
|
||||
'button[data-test-id="product-collection-preview-button"]'
|
||||
const previewButtonLocator = editor.canvas.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
// The preview button should be visible
|
||||
|
@ -1387,3 +1387,235 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* These E2E tests are for `registerProductCollection` which we are exposing
|
||||
* for 3PDs to register new product collections.
|
||||
*/
|
||||
test.describe( 'Testing registerProductCollection', () => {
|
||||
const MY_REGISTERED_COLLECTIONS = {
|
||||
myCustomCollection: {
|
||||
name: 'My Custom Collection',
|
||||
label: 'Block: My Custom Collection',
|
||||
},
|
||||
myCustomCollectionWithPreview: {
|
||||
name: 'My Custom Collection with Preview',
|
||||
label: 'Block: My Custom Collection with Preview',
|
||||
},
|
||||
myCustomCollectionWithAdvancedPreview: {
|
||||
name: 'My Custom Collection with Advanced Preview',
|
||||
label: 'Block: My Custom Collection with Advanced Preview',
|
||||
},
|
||||
};
|
||||
|
||||
// Activate plugin which registers custom product collections
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'register-product-collection-tester'
|
||||
);
|
||||
} );
|
||||
|
||||
test( `Registered collections should be available in Collection chooser`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
admin,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlockUsingGlobalInserter( pageObject.BLOCK_NAME );
|
||||
|
||||
// Get text of all buttons in the collection chooser
|
||||
const collectionChooserButtonsTexts = await editor.page
|
||||
.locator( '.wc-blocks-product-collection__collection-button-title' )
|
||||
.allTextContents();
|
||||
|
||||
// Check if all registered collections are available in the collection chooser
|
||||
expect(
|
||||
collectionChooserButtonsTexts.includes(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollection.name
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
collectionChooserButtonsTexts.includes(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollectionWithPreview.name
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
collectionChooserButtonsTexts.includes(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollectionWithAdvancedPreview
|
||||
.name
|
||||
)
|
||||
).toBeTruthy();
|
||||
} );
|
||||
|
||||
test.describe( 'My Custom Collection', () => {
|
||||
test( 'Clicking "My Custom Collection" should insert block and show 5 products', async ( {
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock(
|
||||
'myCustomCollection'
|
||||
);
|
||||
expect( pageObject.productTemplate ).not.toBeNull();
|
||||
await expect( pageObject.products ).toHaveCount( 5 );
|
||||
await expect( pageObject.productImages ).toHaveCount( 5 );
|
||||
await expect( pageObject.productTitles ).toHaveCount( 5 );
|
||||
await expect( pageObject.productPrices ).toHaveCount( 5 );
|
||||
await expect( pageObject.addToCartButtons ).toHaveCount( 5 );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
await expect( pageObject.products ).toHaveCount( 5 );
|
||||
} );
|
||||
|
||||
test( 'Should display properly in Product Catalog template', async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToProductCatalogAndInsertCollection(
|
||||
'myCustomCollection'
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollection.label
|
||||
);
|
||||
|
||||
const products = block
|
||||
.getByLabel( BLOCK_LABELS.productImage )
|
||||
.locator( 'visible=true' );
|
||||
await expect( products ).toHaveCount( 5 );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'My Custom Collection with Preview', () => {
|
||||
test( 'Clicking "My Custom Collection with Preview" should insert block and show 9 products', async ( {
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock(
|
||||
'myCustomCollectionWithPreview'
|
||||
);
|
||||
expect( pageObject.productTemplate ).not.toBeNull();
|
||||
await expect( pageObject.products ).toHaveCount( 9 );
|
||||
await expect( pageObject.productImages ).toHaveCount( 9 );
|
||||
await expect( pageObject.productTitles ).toHaveCount( 9 );
|
||||
await expect( pageObject.productPrices ).toHaveCount( 9 );
|
||||
await expect( pageObject.addToCartButtons ).toHaveCount( 9 );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
await expect( pageObject.products ).toHaveCount( 9 );
|
||||
} );
|
||||
|
||||
test( 'Clicking "My Custom Collection with Preview" should show preview', async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock(
|
||||
'myCustomCollectionWithPreview'
|
||||
);
|
||||
const previewButtonLocator = editor.page.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
// The preview button should be visible
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'Should display properly in Product Catalog template', async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToProductCatalogAndInsertCollection(
|
||||
'myCustomCollectionWithPreview'
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollectionWithPreview.label
|
||||
);
|
||||
|
||||
// Check if products are visible
|
||||
const products = block
|
||||
.getByLabel( BLOCK_LABELS.productImage )
|
||||
.locator( 'visible=true' );
|
||||
await expect( products ).toHaveCount( 9 );
|
||||
|
||||
// Check if the preview button is visible
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'My Custom Collection with Advanced Preview', () => {
|
||||
test( 'Clicking "My Custom Collection with Advanced Preview" should insert block and show 9 products', async ( {
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock(
|
||||
'myCustomCollectionWithAdvancedPreview'
|
||||
);
|
||||
expect( pageObject.productTemplate ).not.toBeNull();
|
||||
await expect( pageObject.products ).toHaveCount( 9 );
|
||||
await expect( pageObject.productImages ).toHaveCount( 9 );
|
||||
await expect( pageObject.productTitles ).toHaveCount( 9 );
|
||||
await expect( pageObject.productPrices ).toHaveCount( 9 );
|
||||
await expect( pageObject.addToCartButtons ).toHaveCount( 9 );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
await expect( pageObject.products ).toHaveCount( 9 );
|
||||
} );
|
||||
|
||||
test( 'Clicking "My Custom Collection with Advanced Preview" should show preview for 1 second', async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock(
|
||||
'myCustomCollectionWithAdvancedPreview'
|
||||
);
|
||||
const previewButtonLocator = editor.page.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
// The preview button should be visible
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
|
||||
// Disabling eslint rule because we need to wait for the preview to disappear
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout, no-restricted-syntax
|
||||
await page.waitForTimeout( 1000 );
|
||||
|
||||
// The preview button should be hidden
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'Should display properly in Product Catalog template', async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await pageObject.goToProductCatalogAndInsertCollection(
|
||||
'myCustomCollectionWithAdvancedPreview'
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel(
|
||||
MY_REGISTERED_COLLECTIONS.myCustomCollectionWithAdvancedPreview
|
||||
.label
|
||||
);
|
||||
|
||||
// Check if the preview button is visible
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
|
||||
// Check if products are visible
|
||||
const products = block
|
||||
.getByLabel( BLOCK_LABELS.productImage )
|
||||
.locator( 'visible=true' );
|
||||
await expect( products ).toHaveCount( 9 );
|
||||
|
||||
// Disabling eslint rule because we need to wait for the preview to disappear
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout, no-restricted-syntax
|
||||
await page.waitForTimeout( 1000 );
|
||||
|
||||
// The preview button should be hidden after 1 second
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -59,15 +59,19 @@ export const SELECTORS = {
|
|||
min: 'MIN',
|
||||
max: 'MAX',
|
||||
},
|
||||
previewButtonTestID: 'product-collection-preview-button',
|
||||
};
|
||||
|
||||
type Collections =
|
||||
export type Collections =
|
||||
| 'newArrivals'
|
||||
| 'topRated'
|
||||
| 'bestSellers'
|
||||
| 'onSale'
|
||||
| 'featured'
|
||||
| 'productCatalog';
|
||||
| 'productCatalog'
|
||||
| 'myCustomCollection'
|
||||
| 'myCustomCollectionWithPreview'
|
||||
| 'myCustomCollectionWithAdvancedPreview';
|
||||
|
||||
const collectionToButtonNameMap = {
|
||||
newArrivals: 'New Arrivals Recommend your newest products.',
|
||||
|
@ -77,6 +81,11 @@ const collectionToButtonNameMap = {
|
|||
featured: 'Featured Showcase your featured products.',
|
||||
productCatalog:
|
||||
'Product Catalog Display all products in your catalog. Results can (change to) match the current template, page, or search term.',
|
||||
myCustomCollection: 'My Custom Collection This is a custom collection.',
|
||||
myCustomCollectionWithPreview:
|
||||
'My Custom Collection with Preview This is a custom collection with preview.',
|
||||
myCustomCollectionWithAdvancedPreview:
|
||||
'My Custom Collection with Advanced Preview This is a custom collection with advanced preview.',
|
||||
};
|
||||
|
||||
class ProductCollectionPage {
|
||||
|
@ -206,6 +215,13 @@ class ProductCollectionPage {
|
|||
await this.refreshLocators( 'editor' );
|
||||
}
|
||||
|
||||
async goToProductCatalogAndInsertCollection( collection: Collections ) {
|
||||
await this.goToTemplateAndInsertCollection(
|
||||
'woocommerce/woocommerce//archive-product',
|
||||
collection
|
||||
);
|
||||
}
|
||||
|
||||
async goToProductCatalogFrontend() {
|
||||
await this.page.goto( '/shop' );
|
||||
await this.refreshLocators( 'frontend' );
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Expose __experimentalRegisterProductCollection in @woocommerce/blocks-registry Package
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add validation for `__experimentalRegisterProductCollection` arguments
|
Loading…
Reference in New Issue