Product Collection: Trigger `wc-blocks_viewed_product` JS event (#51156)
* Define the event * Add action sending an event in PC store * Add directives and context to Product Template li element * Use on--click directive in ProductImage * Use on--click directive in Product Title * Use on--click directive in Product Button * Add changelog * Add E2E tests * Update docs * Update blocks reference and docs manifest * Update m,anifest * Fix mistake in docs * Regenerate docs manifest * Fix lint * Extractb new tests to a separate file
This commit is contained in:
parent
5a5b1f0478
commit
a7231863c0
|
@ -1109,16 +1109,16 @@ The contents of this block will display when there are no products found.
|
||||||
- **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
|
- **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
|
||||||
- **Attributes:**
|
- **Attributes:**
|
||||||
|
|
||||||
## Product Filter (Experimental) - woocommerce/product-filter
|
## Product Filters (Experimental) - woocommerce/product-filters
|
||||||
|
|
||||||
A block that adds product filters to the product collection.
|
Let shoppers filter products displayed on the page.
|
||||||
|
|
||||||
- **Name:** woocommerce/product-filter
|
- **Name:** woocommerce/product-filters
|
||||||
- **Category:** woocommerce
|
- **Category:** woocommerce
|
||||||
- **Ancestor:** woocommerce/product-filters
|
- **Ancestor:**
|
||||||
- **Parent:**
|
- **Parent:**
|
||||||
- **Supports:** ~~html~~, ~~inserter~~, ~~reusable~~
|
- **Supports:** align, color (background, text), interactivity, layout (allowJustification, allowOrientation, allowVerticalAlignment, default, ~~allowInheriting~~), spacing (blockGap), typography (fontSize, textAlign), ~~inserter~~, ~~multiple~~
|
||||||
- **Attributes:** attributeId, filterType, heading, isPreview
|
- **Attributes:** overlay, overlayButtonStyle, overlayIcon, overlayIconSize
|
||||||
|
|
||||||
## Filter Options - woocommerce/product-filter-active
|
## Filter Options - woocommerce/product-filter-active
|
||||||
|
|
||||||
|
@ -1153,50 +1153,6 @@ Allows shoppers to reset this filter.
|
||||||
- **Supports:** interactivity, ~~inserter~~
|
- **Supports:** interactivity, ~~inserter~~
|
||||||
- **Attributes:**
|
- **Attributes:**
|
||||||
|
|
||||||
## Filter Options - woocommerce/product-filter-price
|
|
||||||
|
|
||||||
Enable customers to filter the product collection by choosing a price range.
|
|
||||||
|
|
||||||
- **Name:** woocommerce/product-filter-price
|
|
||||||
- **Category:** woocommerce
|
|
||||||
- **Ancestor:** woocommerce/product-filter
|
|
||||||
- **Parent:**
|
|
||||||
- **Supports:** interactivity, ~~inserter~~
|
|
||||||
- **Attributes:** inlineInput, showInputFields
|
|
||||||
|
|
||||||
## Filter Options - woocommerce/product-filter-rating
|
|
||||||
|
|
||||||
Enable customers to filter the product collection by rating.
|
|
||||||
|
|
||||||
- **Name:** woocommerce/product-filter-rating
|
|
||||||
- **Category:** woocommerce
|
|
||||||
- **Ancestor:** woocommerce/product-filter
|
|
||||||
- **Parent:**
|
|
||||||
- **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~
|
|
||||||
- **Attributes:** className, displayStyle, isPreview, selectType, showCounts
|
|
||||||
|
|
||||||
## Filter Options - woocommerce/product-filter-stock-status
|
|
||||||
|
|
||||||
Enable customers to filter the product collection by stock status.
|
|
||||||
|
|
||||||
- **Name:** woocommerce/product-filter-stock-status
|
|
||||||
- **Category:** woocommerce
|
|
||||||
- **Ancestor:** woocommerce/product-filter
|
|
||||||
- **Parent:**
|
|
||||||
- **Supports:** color (text, ~~background~~), interactivity, ~~html~~, ~~inserter~~, ~~multiple~~
|
|
||||||
- **Attributes:** className, displayStyle, isPreview, selectType, showCounts
|
|
||||||
|
|
||||||
## Product Filters (Experimental) - woocommerce/product-filters
|
|
||||||
|
|
||||||
Let shoppers filter products displayed on the page.
|
|
||||||
|
|
||||||
- **Name:** woocommerce/product-filters
|
|
||||||
- **Category:** woocommerce
|
|
||||||
- **Ancestor:**
|
|
||||||
- **Parent:**
|
|
||||||
- **Supports:** align, color (background, text), interactivity, layout (allowJustification, allowOrientation, allowVerticalAlignment, default, ~~allowInheriting~~), spacing (blockGap), typography (fontSize, textAlign), ~~inserter~~, ~~multiple~~
|
|
||||||
- **Attributes:** overlay, overlayButtonStyle, overlayIcon, overlayIconSize
|
|
||||||
|
|
||||||
## Product Filters Overlay (Experimental) - woocommerce/product-filters-overlay
|
## Product Filters Overlay (Experimental) - woocommerce/product-filters-overlay
|
||||||
|
|
||||||
Display product filters in an overlay on top of a page.
|
Display product filters in an overlay on top of a page.
|
||||||
|
@ -1219,6 +1175,50 @@ Display overlay navigation controls.
|
||||||
- **Supports:** align (center, left, right), color (background, text), layout (default, ~~allowEditing~~), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~inserter~~
|
- **Supports:** align (center, left, right), color (background, text), layout (default, ~~allowEditing~~), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~inserter~~
|
||||||
- **Attributes:** align, buttonStyle, iconSize, navigationStyle, overlayMode, style, triggerType
|
- **Attributes:** align, buttonStyle, iconSize, navigationStyle, overlayMode, style, triggerType
|
||||||
|
|
||||||
|
## Filter Options - woocommerce/product-filter-price
|
||||||
|
|
||||||
|
Enable customers to filter the product collection by choosing a price range.
|
||||||
|
|
||||||
|
- **Name:** woocommerce/product-filter-price
|
||||||
|
- **Category:** woocommerce
|
||||||
|
- **Ancestor:** woocommerce/product-filter
|
||||||
|
- **Parent:**
|
||||||
|
- **Supports:** interactivity, ~~inserter~~
|
||||||
|
- **Attributes:** inlineInput, showInputFields
|
||||||
|
|
||||||
|
## Product Filter (Experimental) - woocommerce/product-filter
|
||||||
|
|
||||||
|
A block that adds product filters to the product collection.
|
||||||
|
|
||||||
|
- **Name:** woocommerce/product-filter
|
||||||
|
- **Category:** woocommerce
|
||||||
|
- **Ancestor:** woocommerce/product-filters
|
||||||
|
- **Parent:**
|
||||||
|
- **Supports:** inserter, ~~html~~, ~~reusable~~
|
||||||
|
- **Attributes:** attributeId, filterType, heading, isPreview
|
||||||
|
|
||||||
|
## Filter Options - woocommerce/product-filter-rating
|
||||||
|
|
||||||
|
Enable customers to filter the product collection by rating.
|
||||||
|
|
||||||
|
- **Name:** woocommerce/product-filter-rating
|
||||||
|
- **Category:** woocommerce
|
||||||
|
- **Ancestor:** woocommerce/product-filter
|
||||||
|
- **Parent:**
|
||||||
|
- **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~
|
||||||
|
- **Attributes:** className, displayStyle, isPreview, selectType, showCounts
|
||||||
|
|
||||||
|
## Filter Options - woocommerce/product-filter-stock-status
|
||||||
|
|
||||||
|
Enable customers to filter the product collection by stock status.
|
||||||
|
|
||||||
|
- **Name:** woocommerce/product-filter-stock-status
|
||||||
|
- **Category:** woocommerce
|
||||||
|
- **Ancestor:** woocommerce/product-filter
|
||||||
|
- **Parent:**
|
||||||
|
- **Supports:** color (text, ~~background~~), interactivity, ~~html~~, ~~inserter~~
|
||||||
|
- **Attributes:** className, displayStyle, isPreview, selectType, showCounts
|
||||||
|
|
||||||
## Product Gallery (Beta) - woocommerce/product-gallery
|
## Product Gallery (Beta) - woocommerce/product-gallery
|
||||||
|
|
||||||
Showcase your products relevant images and media.
|
Showcase your products relevant images and media.
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
"post_title": "Blocks reference",
|
"post_title": "Blocks reference",
|
||||||
"menu_title": "Blocks Reference",
|
"menu_title": "Blocks Reference",
|
||||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/building-a-woo-store/block-references.md",
|
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/building-a-woo-store/block-references.md",
|
||||||
"hash": "329f17097ce67074a915d7814b2363e8b9e908910c1f7b196c8f4fd8594cc55c",
|
"hash": "9bbd3555641a70a0d7c24c818323a9270e437a6446998de9a6506e0c2ed6ddf5",
|
||||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/block-references.md",
|
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/block-references.md",
|
||||||
"id": "1fbe91d7fa4fafaf35f0297e4cee1e7958756aed"
|
"id": "1fbe91d7fa4fafaf35f0297e4cee1e7958756aed"
|
||||||
},
|
},
|
||||||
|
@ -1059,7 +1059,7 @@
|
||||||
"menu_title": "DOM Events",
|
"menu_title": "DOM Events",
|
||||||
"tags": "how-to",
|
"tags": "how-to",
|
||||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-collection-block/dom-events.md",
|
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-collection-block/dom-events.md",
|
||||||
"hash": "fbad20bc55cc569161e80478c0789db3c34cf35513e669554af36db1de967a26",
|
"hash": "59a4b49eb146774d33229bc60ab7d8f74381493f6e7089ca8f0e2d0eb433a7a4",
|
||||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-collection-block/dom-events.md",
|
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-collection-block/dom-events.md",
|
||||||
"id": "c8d247b91472740075871e6b57a9583d893ac650"
|
"id": "c8d247b91472740075871e6b57a9583d893ac650"
|
||||||
}
|
}
|
||||||
|
@ -1804,5 +1804,5 @@
|
||||||
"categories": []
|
"categories": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hash": "212688b70a2dd0e70819746e6ffc033bc2279cb5b7b6350f377bbc3bc28c080f"
|
"hash": "12e9abfbcdbeae7dd5cc12dc3af3818332f272cb4b3ad12993cc010299009013"
|
||||||
}
|
}
|
|
@ -10,13 +10,13 @@ tags: how-to
|
||||||
|
|
||||||
This event is triggered when Product Collection block was rendered or re-rendered (e.g. due to page change).
|
This event is triggered when Product Collection block was rendered or re-rendered (e.g. due to page change).
|
||||||
|
|
||||||
### `detail` parameters
|
### `wc-blocks_product_list_rendered` - `detail` parameters
|
||||||
|
|
||||||
| Parameter | Type | Default value | Description |
|
| Parameter | Type | Default value | Description |
|
||||||
| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. |
|
| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. |
|
||||||
|
|
||||||
### Example usage
|
### `wc-blocks_product_list_rendered` - Example usage
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
window.document.addEventListener(
|
window.document.addEventListener(
|
||||||
|
@ -27,3 +27,27 @@ window.document.addEventListener(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Event: `wc-blocks_viewed_product`
|
||||||
|
|
||||||
|
This event is triggered when some blocks are clicked in order to view product (redirect to product page).
|
||||||
|
|
||||||
|
### `wc-blocks_viewed_product` - `detail` parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default value | Description |
|
||||||
|
| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. |
|
||||||
|
| `productId` | number | | Product ID |
|
||||||
|
|
||||||
|
### `wc-blocks_viewed_product` Example usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.document.addEventListener(
|
||||||
|
'wc-blocks_viewed_product',
|
||||||
|
( e ) => {
|
||||||
|
const { collection, productId } = e.detail;
|
||||||
|
console.log( collection ) // -> collection name, e.g. "woocommerce/product-collection/featured" or undefined for default one
|
||||||
|
console.log( productId ) // -> product ID, e.g. 34
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
|
@ -70,6 +70,17 @@ export const triggerProductListRenderedEvent = ( payload: {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const triggerViewedProductEvent = ( payload: {
|
||||||
|
collection?: CoreCollectionNames | string;
|
||||||
|
productId: number;
|
||||||
|
} ): void => {
|
||||||
|
dispatchEvent( 'wc-blocks_viewed_product', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
detail: payload,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that listens to a jQuery event and dispatches a native JS event.
|
* Function that listens to a jQuery event and dispatches a native JS event.
|
||||||
* Useful to convert WC Core events into events that can be read by blocks.
|
* Useful to convert WC Core events into events that can be read by blocks.
|
||||||
|
|
|
@ -8,7 +8,10 @@ import {
|
||||||
getElement,
|
getElement,
|
||||||
getContext,
|
getContext,
|
||||||
} from '@woocommerce/interactivity';
|
} from '@woocommerce/interactivity';
|
||||||
import { triggerProductListRenderedEvent } from '@woocommerce/base-utils';
|
import {
|
||||||
|
triggerProductListRenderedEvent,
|
||||||
|
triggerViewedProductEvent,
|
||||||
|
} from '@woocommerce/base-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -17,6 +20,8 @@ import { CoreCollectionNames } from './types';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export type ProductCollectionStoreContext = {
|
export type ProductCollectionStoreContext = {
|
||||||
|
// Available on the <li/> product element and deeper
|
||||||
|
productId?: number;
|
||||||
isPrefetchNextOrPreviousLink: boolean;
|
isPrefetchNextOrPreviousLink: boolean;
|
||||||
animation: 'start' | 'finish';
|
animation: 'start' | 'finish';
|
||||||
accessibilityMessage: string;
|
accessibilityMessage: string;
|
||||||
|
@ -164,6 +169,14 @@ const productCollectionStore = {
|
||||||
yield prefetch( ref.href );
|
yield prefetch( ref.href );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
*viewProduct() {
|
||||||
|
const { collection, productId } =
|
||||||
|
getContext< ProductCollectionStoreContext >();
|
||||||
|
|
||||||
|
if ( productId ) {
|
||||||
|
triggerViewedProductEvent( { collection, productId } );
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { test as base, expect } from '@woocommerce/e2e-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import ProductCollectionPage from './product-collection.page';
|
||||||
|
|
||||||
|
const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
||||||
|
pageObject: async ( { page, admin, editor }, use ) => {
|
||||||
|
const pageObject = new ProductCollectionPage( {
|
||||||
|
page,
|
||||||
|
admin,
|
||||||
|
editor,
|
||||||
|
} );
|
||||||
|
await use( pageObject );
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
test.describe( 'Product Collection - extensibility JS events', () => {
|
||||||
|
test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( {
|
||||||
|
pageObject,
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await pageObject.createNewPostAndInsertBlock();
|
||||||
|
|
||||||
|
await page.addInitScript( () => {
|
||||||
|
let eventFired = 0;
|
||||||
|
window.document.addEventListener(
|
||||||
|
'wc-blocks_product_list_rendered',
|
||||||
|
( e ) => {
|
||||||
|
const { collection } = e.detail;
|
||||||
|
window.eventPayload = collection;
|
||||||
|
window.eventFired = ++eventFired;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll( async () => await page.evaluate( 'window.eventPayload' ) )
|
||||||
|
.toBe( undefined );
|
||||||
|
await expect
|
||||||
|
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
||||||
|
.toBe( 1 );
|
||||||
|
|
||||||
|
await page.getByRole( 'link', { name: 'Next Page' } ).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
||||||
|
.toBe( 2 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'emits one wc-blocks_product_list_rendered event per block', async ( {
|
||||||
|
pageObject,
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
// Adding three blocks in total
|
||||||
|
await pageObject.createNewPostAndInsertBlock();
|
||||||
|
await pageObject.insertProductCollection();
|
||||||
|
await pageObject.chooseCollectionInPost();
|
||||||
|
await pageObject.insertProductCollection();
|
||||||
|
await pageObject.chooseCollectionInPost();
|
||||||
|
|
||||||
|
await page.addInitScript( () => {
|
||||||
|
let eventFired = 0;
|
||||||
|
window.document.addEventListener(
|
||||||
|
'wc-blocks_product_list_rendered',
|
||||||
|
() => {
|
||||||
|
window.eventFired = ++eventFired;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
||||||
|
.toBe( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test.describe( 'wc-blocks_viewed_product is emitted', () => {
|
||||||
|
let promise: Promise< { productId?: number; collection?: string } >;
|
||||||
|
|
||||||
|
test.beforeEach( async ( { page, pageObject } ) => {
|
||||||
|
await pageObject.createNewPostAndInsertBlock( 'featured' );
|
||||||
|
|
||||||
|
promise = new Promise( ( resolve ) => {
|
||||||
|
void page.exposeFunction( 'resolvePayload', resolve );
|
||||||
|
void page.addInitScript( () => {
|
||||||
|
window.document.addEventListener(
|
||||||
|
'wc-blocks_viewed_product',
|
||||||
|
( e ) => {
|
||||||
|
window.resolvePayload( e.detail );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'when Product Image is clicked', async ( { page } ) => {
|
||||||
|
await page
|
||||||
|
.locator( '[data-block-name="woocommerce/product-image"]' )
|
||||||
|
.nth( 0 )
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const { collection, productId } = await promise;
|
||||||
|
expect( collection ).toEqual(
|
||||||
|
'woocommerce/product-collection/featured'
|
||||||
|
);
|
||||||
|
expect( productId ).toEqual( expect.any( Number ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'when Product Title is clicked', async ( { page } ) => {
|
||||||
|
await page.locator( '.wp-block-post-title' ).nth( 0 ).click();
|
||||||
|
|
||||||
|
const { collection, productId } = await promise;
|
||||||
|
expect( collection ).toEqual(
|
||||||
|
'woocommerce/product-collection/featured'
|
||||||
|
);
|
||||||
|
expect( productId ).toEqual( expect.any( Number ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'when Add to Cart Anchor is clicked', async ( { page } ) => {
|
||||||
|
await page.getByLabel( 'Select options for “V-Neck T-' ).click();
|
||||||
|
|
||||||
|
const { collection, productId } = await promise;
|
||||||
|
expect( collection ).toEqual(
|
||||||
|
'woocommerce/product-collection/featured'
|
||||||
|
);
|
||||||
|
expect( productId ).toEqual( expect.any( Number ) );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -901,72 +901,6 @@ test.describe( 'Product Collection', () => {
|
||||||
await expect( products ).toHaveText( expectedProducts );
|
await expect( products ).toHaveText( expectedProducts );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Extensibility - JS events', () => {
|
|
||||||
test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( {
|
|
||||||
pageObject,
|
|
||||||
page,
|
|
||||||
} ) => {
|
|
||||||
await pageObject.createNewPostAndInsertBlock();
|
|
||||||
|
|
||||||
await page.addInitScript( () => {
|
|
||||||
let eventFired = 0;
|
|
||||||
window.document.addEventListener(
|
|
||||||
'wc-blocks_product_list_rendered',
|
|
||||||
( e ) => {
|
|
||||||
const { collection } = e.detail;
|
|
||||||
window.eventPayload = collection;
|
|
||||||
window.eventFired = ++eventFired;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(
|
|
||||||
async () => await page.evaluate( 'window.eventPayload' )
|
|
||||||
)
|
|
||||||
.toBe( undefined );
|
|
||||||
await expect
|
|
||||||
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
|
||||||
.toBe( 1 );
|
|
||||||
|
|
||||||
await page.getByRole( 'link', { name: 'Next Page' } ).click();
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
|
||||||
.toBe( 2 );
|
|
||||||
} );
|
|
||||||
|
|
||||||
test( 'emits one wc-blocks_product_list_rendered event per block', async ( {
|
|
||||||
pageObject,
|
|
||||||
page,
|
|
||||||
} ) => {
|
|
||||||
// Adding three blocks in total
|
|
||||||
await pageObject.createNewPostAndInsertBlock();
|
|
||||||
await pageObject.insertProductCollection();
|
|
||||||
await pageObject.chooseCollectionInPost();
|
|
||||||
await pageObject.insertProductCollection();
|
|
||||||
await pageObject.chooseCollectionInPost();
|
|
||||||
|
|
||||||
await page.addInitScript( () => {
|
|
||||||
let eventFired = 0;
|
|
||||||
window.document.addEventListener(
|
|
||||||
'wc-blocks_product_list_rendered',
|
|
||||||
() => {
|
|
||||||
window.eventFired = ++eventFired;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll( async () => await page.evaluate( 'window.eventFired' ) )
|
|
||||||
.toBe( 3 );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Testing "usesReference" argument in "registerProductCollection"', () => {
|
test.describe( 'Testing "usesReference" argument in "registerProductCollection"', () => {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Product Elements send a JS event when user attempts to view a product
|
|
@ -176,6 +176,10 @@ class ProductButton extends AbstractBlock {
|
||||||
data-wc-class--loading="context.isLoading"
|
data-wc-class--loading="context.isLoading"
|
||||||
';
|
';
|
||||||
|
|
||||||
|
$anchor_directive = '
|
||||||
|
data-wc-on--click="woocommerce/product-collection::actions.viewProduct"
|
||||||
|
';
|
||||||
|
|
||||||
$span_button_directives = '
|
$span_button_directives = '
|
||||||
data-wc-text="state.addToCartText"
|
data-wc-text="state.addToCartText"
|
||||||
data-wc-class--wc-block-slide-in="state.slideInAnimation"
|
data-wc-class--wc-block-slide-in="state.slideInAnimation"
|
||||||
|
@ -219,7 +223,7 @@ class ProductButton extends AbstractBlock {
|
||||||
'{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
|
'{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
|
||||||
'{add_to_cart_text}' => esc_html( $initial_product_text ),
|
'{add_to_cart_text}' => esc_html( $initial_product_text ),
|
||||||
'{div_directives}' => $is_ajax_button ? $div_directives : '',
|
'{div_directives}' => $is_ajax_button ? $div_directives : '',
|
||||||
'{button_directives}' => $is_ajax_button ? $button_directives : '',
|
'{button_directives}' => $is_ajax_button ? $button_directives : $anchor_directive,
|
||||||
'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
|
'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
|
||||||
'{view_cart_html}' => $is_ajax_button ? $this->get_view_cart_html() : '',
|
'{view_cart_html}' => $is_ajax_button ? $this->get_view_cart_html() : '',
|
||||||
)
|
)
|
||||||
|
|
|
@ -117,6 +117,7 @@ class ProductCollection extends AbstractBlock {
|
||||||
// Interactivity API: Add navigation directives to the product collection block.
|
// Interactivity API: Add navigation directives to the product collection block.
|
||||||
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 );
|
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 );
|
||||||
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
|
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
|
||||||
|
add_filter( 'render_block_core/post-title', array( $this, 'add_product_title_click_event_directives' ), 10, 3 );
|
||||||
|
|
||||||
add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
|
add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
|
||||||
|
|
||||||
|
@ -408,6 +409,36 @@ class ProductCollection extends AbstractBlock {
|
||||||
return $block_content;
|
return $block_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add interactivity to the Product Title block within Product Collection.
|
||||||
|
* This enables the triggering of a custom event when the product title is clicked.
|
||||||
|
*
|
||||||
|
* @param string $block_content The block content.
|
||||||
|
* @param array $block The full block, including name and attributes.
|
||||||
|
* @param \WP_Block $instance The block instance.
|
||||||
|
* @return string Modified block content with added interactivity.
|
||||||
|
*/
|
||||||
|
public function add_product_title_click_event_directives( $block_content, $block, $instance ) {
|
||||||
|
$namespace = $instance->attributes['__woocommerceNamespace'] ?? '';
|
||||||
|
$is_product_title_block = 'woocommerce/product-collection/product-title' === $namespace;
|
||||||
|
$is_link = $instance->attributes['isLink'] ?? false;
|
||||||
|
|
||||||
|
// Only proceed if the block is a Product Title (Post Title variation) block.
|
||||||
|
if ( $is_product_title_block && $is_link ) {
|
||||||
|
$p = new \WP_HTML_Tag_Processor( $block_content );
|
||||||
|
$p->next_tag( array( 'class_name' => 'wp-block-post-title' ) );
|
||||||
|
$is_anchor = $p->next_tag( array( 'tag_name' => 'a' ) );
|
||||||
|
|
||||||
|
if ( $is_anchor ) {
|
||||||
|
$p->set_attribute( 'data-wc-on--click', 'woocommerce/product-collection::actions.viewProduct' );
|
||||||
|
|
||||||
|
$block_content = $p->get_updated_html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $block_content;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process pagination links within the block content.
|
* Process pagination links within the block content.
|
||||||
*
|
*
|
||||||
|
|
|
@ -125,12 +125,15 @@ class ProductImage extends AbstractBlock {
|
||||||
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) {
|
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) {
|
||||||
$product_permalink = $product->get_permalink();
|
$product_permalink = $product->get_permalink();
|
||||||
|
|
||||||
$pointer_events = false === $attributes['showProductLink'] ? 'pointer-events: none;' : '';
|
$is_link = true === $attributes['showProductLink'];
|
||||||
|
$pointer_events = $is_link ? '' : 'pointer-events: none;';
|
||||||
|
$directive = $is_link ? 'data-wc-on--click="woocommerce/product-collection::actions.viewProduct"' : '';
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<a href="%1$s" style="%2$s">%3$s %4$s</a>',
|
'<a href="%1$s" style="%2$s" %3$s>%4$s %5$s</a>',
|
||||||
$product_permalink,
|
$product_permalink,
|
||||||
$pointer_events,
|
$pointer_events,
|
||||||
|
$directive,
|
||||||
$on_sale_badge,
|
$on_sale_badge,
|
||||||
$product_image
|
$product_image
|
||||||
);
|
);
|
||||||
|
|
|
@ -85,6 +85,7 @@ class ProductTemplate extends AbstractBlock {
|
||||||
|
|
||||||
// Get an instance of the current Post Template block.
|
// Get an instance of the current Post Template block.
|
||||||
$block_instance = $block->parsed_block;
|
$block_instance = $block->parsed_block;
|
||||||
|
$product_id = get_the_ID();
|
||||||
|
|
||||||
// Set the block name to one that does not correspond to an existing registered block.
|
// Set the block name to one that does not correspond to an existing registered block.
|
||||||
// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
|
// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
|
||||||
|
@ -97,14 +98,39 @@ class ProductTemplate extends AbstractBlock {
|
||||||
$block_instance,
|
$block_instance,
|
||||||
array(
|
array(
|
||||||
'postType' => get_post_type(),
|
'postType' => get_post_type(),
|
||||||
'postId' => get_the_ID(),
|
'postId' => $product_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)->render( array( 'dynamic' => false ) );
|
)->render( array( 'dynamic' => false ) );
|
||||||
|
|
||||||
|
$interactive = array(
|
||||||
|
'namespace' => 'woocommerce/product-collection',
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = array(
|
||||||
|
'productId' => $product_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
$li_directives = '
|
||||||
|
data-wc-interactive=\'' . wp_json_encode( $interactive, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
|
||||||
|
data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
|
||||||
|
data-wc-key="product-item-' . $product_id . '"
|
||||||
|
';
|
||||||
|
|
||||||
// Wrap the render inner blocks in a `li` element with the appropriate post classes.
|
// Wrap the render inner blocks in a `li` element with the appropriate post classes.
|
||||||
$post_classes = implode( ' ', get_post_class( 'wc-block-product' ) );
|
$post_classes = implode( ' ', get_post_class( 'wc-block-product' ) );
|
||||||
$content .= '<li data-wc-key="product-item-' . get_the_ID() . '" class="' . esc_attr( $post_classes ) . '">' . $block_content . '</li>';
|
$content .= strtr(
|
||||||
|
'<li class="{classes}"
|
||||||
|
{li_directives}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</li>',
|
||||||
|
array(
|
||||||
|
'{classes}' => esc_attr( $post_classes ),
|
||||||
|
'{li_directives}' => $li_directives,
|
||||||
|
'{content}' => $block_content,
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue