Skip to content

fix: attribute selection from PDPs with tokenized ECE #10306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 13, 2025
4 changes: 4 additions & 0 deletions changelog/fix-tokenized-ece-attribute-selection
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

fix: attribute selection from PDPs with tokenized ECE
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/* eslint-disable jsx-a11y/accessible-emoji */

/**
* External dependencies
*/
import { applyFilters } from '@wordpress/hooks';
import { render } from '@testing-library/react';

/**
* Internal dependencies
*/
import '../wc-product-page';

describe( 'ECE product page compatibility', () => {
it( 'returns the variation data', () => {
render(
<form className="variations_form">
<table className="variations" role="presentation">
<tbody>
<tr>
<th className="label">
<label htmlFor="pa_color">Color</label>
</th>
<td className="value">
<select
id="pa_color"
name="attribute_pa_color"
data-attribute_name="attribute_pa_color"
defaultValue="red"
>
<option value="">Choose an option</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="red">Red</option>
</select>
</td>
</tr>
<tr>
<th className="label">
<label htmlFor="logo">Logo</label>
</th>
<td className="value">
<select
id="logo"
name="attribute_logo"
data-attribute_name="attribute_logo"
defaultValue="Yes"
>
<option value="">Choose an option</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</td>
</tr>
</tbody>
</table>
<div className="single_variation_wrap">
<input type="hidden" name="product_id" value="10" />
</div>
</form>
);
const productData = applyFilters(
'wcpay.express-checkout.cart-add-item',
{
variation: [],
}
);

expect( productData ).toStrictEqual( {
id: 10,
variation: [
{
attribute: 'Color',
value: 'red',
},
{
attribute: 'attribute_pa_color',
value: 'red',
},
{
attribute: 'Logo',
value: 'Yes',
},
{
attribute: 'attribute_logo',
value: 'Yes',
},
],
} );
} );
it( 'ensures compatibility with plugins modifying the DOM with additional markup', () => {
// this markup is simulating the output of the "woo-variation-swatches" plugin.
render(
<form className="variations_form">
<table className="variations">
<tbody>
<tr>
<th className="label">
<label htmlFor="size%f0%9f%98%86">
Size😆
<span className="cfvsw-selected-label">
Medium
</span>
</label>
</th>
<td className="value woo-variation-items-wrapper">
<select
id="size%f0%9f%98%86"
name="attribute_size%f0%9f%98%86"
data-attribute_name="attribute_size%f0%9f%98%86"
defaultValue="Medium"
>
<option value="">Choose an option</option>
<option value="Small">Small</option>
<option value="Medium">Medium</option>
</select>
</td>
</tr>
<tr>
<th className="label">
<label htmlFor="color-%e2%9c%8f%ef%b8%8f">
Color ✏️
</label>
<span>: Blue</span>
</th>
<td className="value woo-variation-items-wrapper">
<select
id="color-%e2%9c%8f%ef%b8%8f"
name="attribute_color-%e2%9c%8f%ef%b8%8f"
data-attribute_name="attribute_color-%e2%9c%8f%ef%b8%8f"
defaultValue="Green"
>
<option value="">Choose an option</option>
<option value="Blue">Blue</option>
<option value="Green">Green</option>
</select>
</td>
</tr>
<tr>
<th className="label">
<label htmlFor="autograph-choice-%e2%9c%8f%ef%b8%8f">
Autograph choice ✏️
</label>
<span>: Yes 👍</span>
</th>
<td className="value woo-variation-items-wrapper">
<select
id="autograph-choice-%e2%9c%8f%ef%b8%8f"
name="attribute_autograph-choice-%e2%9c%8f%ef%b8%8f"
data-attribute_name="attribute_autograph-choice-%e2%9c%8f%ef%b8%8f"
defaultValue="Yes 👍"
>
<option value="">Choose an option</option>
<option value="Yes 👍">Yes 👍</option>
<option value="No 👎">No 👎</option>
</select>
</td>
</tr>
</tbody>
</table>
<div className="single_variation_wrap">
<input type="hidden" name="product_id" value="10" />
</div>
</form>
);
const productData = applyFilters(
'wcpay.express-checkout.cart-add-item',
{
variation: [],
}
);

expect( productData ).toStrictEqual( {
id: 10,
variation: [
{
attribute: 'Size😆',
value: 'Medium',
},
{
attribute: 'attribute_size%f0%9f%98%86',
value: 'Medium',
},
{
attribute: 'Color ✏️',
value: 'Green',
},
{
attribute: 'attribute_color-%e2%9c%8f%ef%b8%8f',
value: 'Green',
},
{
attribute: 'Autograph choice ✏️',
value: 'Yes 👍',
},
{
attribute: 'attribute_autograph-choice-%e2%9c%8f%ef%b8%8f',
value: 'Yes 👍',
},
],
} );
} );
} );
46 changes: 28 additions & 18 deletions client/tokenized-express-checkout/compatibility/wc-product-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ addFilter(
'wcpay.express-checkout.cart-add-item',
'automattic/wcpay/express-checkout',
( productData ) => {
const $variationInformation = jQuery( '.single_variation_wrap' );
if ( ! $variationInformation.length ) {
const variationInformation = document.querySelector(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved away from the jQuery selectors and switched to vanilla JS selectors, so that testing would be more reliable and easier (without having to mock jQuery)

'.single_variation_wrap'
);
if ( ! variationInformation ) {
return productData;
}

const productId = $variationInformation
.find( 'input[name="product_id"]' )
.val();
const productId = variationInformation.querySelector(
'input[name="product_id"]'
).value;
return {
...productData,
id: parseInt( productId, 10 ),
Expand All @@ -55,31 +57,39 @@ addFilter(
'wcpay.express-checkout.cart-add-item',
'automattic/wcpay/express-checkout',
( productData ) => {
const $variationsForm = jQuery( '.variations_form' );
if ( ! $variationsForm.length ) {
const variationsForm = document.querySelector( '.variations_form' );
if ( ! variationsForm ) {
return productData;
}

const attributes = [];
const $variationSelectElements = $variationsForm.find(
const variationSelectElements = variationsForm.querySelectorAll(
'.variations select'
);
$variationSelectElements.each( function () {
const $select = jQuery( this );
Array.from( variationSelectElements ).forEach( function ( select ) {
const attributeName =
$select.data( 'attribute_name' ) || $select.attr( 'name' );
select.dataset.attribute_name || select.dataset.name;

attributes.push( {
// The Store API accepts the variable attribute's label, rather than an internal identifier:
// https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/StoreApi/docs/cart.md#add-item
// It's an unfortunate hack that doesn't work when labels have special characters in them.
attribute: document.querySelector(
`label[for="${ attributeName.replace(
'attribute_',
''
) }"]`
).innerHTML,
value: $select.val() || '',
// fallback until https://github.com/woocommerce/woocommerce/pull/55317 has been consolidated in WC Core.
attribute: Array.from(
document.querySelector(
`label[for="${ attributeName.replace(
'attribute_',
''
) }"]`
).childNodes
)[ 0 ].textContent,
value: select.value || '',
} );

// proper logic for https://github.com/woocommerce/woocommerce/pull/55317 .
attributes.push( {
attribute: attributeName,
value: select.value || '',
} );
} );

Expand Down
Loading