Commit 6d4bc4903da for woocommerce
commit 6d4bc4903daae7814588ade049bae972c7bdf920
Author: Michal Iwanow <4765119+mcliwanow@users.noreply.github.com>
Date: Mon Oct 12 08:48:04 2026 +0200
Fix HTML entities showing in in-app Marketplace product titles (#69453)
* Decode HTML entities in in-app Marketplace product titles
* Add changelog entry for Marketplace product title decoding
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/wccom-1854-decode-marketplace-product-titles b/plugins/woocommerce/changelog/wccom-1854-decode-marketplace-product-titles
new file mode 100644
index 00000000000..9eedd6f2b12
--- /dev/null
+++ b/plugins/woocommerce/changelog/wccom-1854-decode-marketplace-product-titles
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show ampersands and other special characters correctly in product names on the in-app Marketplace.
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card.tsx
index 02c8170dca2..f9e89ef5647 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card.tsx
@@ -105,6 +105,7 @@ function ProductCard( props: ProductCardProps ): React.JSX.Element {
const showDescription = ! isTheme && ! isCompact;
const showCardIcon = ! isTheme || isCompact;
const showBigImage = isTheme && ! isCompact;
+ const decodedTitle = decodeEntities( product.title );
const decodedDescription = decodeEntities( product.description );
function isSponsored(): boolean {
@@ -277,7 +278,7 @@ function ProductCard( props: ProductCardProps ): React.JSX.Element {
handleCardClick();
} }
>
- { isLoading ? ' ' : product.title }
+ { isLoading ? ' ' : decodedTitle }
{ screenReaderText }
</a>
);
@@ -343,7 +344,7 @@ function ProductCard( props: ProductCardProps ): React.JSX.Element {
<img
className="woocommerce-marketplace__product-card__image-inner"
src={ product.image }
- alt={ product.title }
+ alt={ decodedTitle }
/>
) }
</div>
@@ -359,7 +360,7 @@ function ProductCard( props: ProductCardProps ): React.JSX.Element {
<img
className="woocommerce-marketplace__product-card__icon"
src={ product.icon || product.image }
- alt={ product.title }
+ alt={ decodedTitle }
/>
) }
</>
@@ -440,7 +441,7 @@ function ProductCard( props: ProductCardProps ): React.JSX.Element {
{ shouldShowPreview && isPreviewModalOpen && product && (
<ProductPreviewModal
- productTitle={ product.title }
+ productTitle={ decodedTitle }
productVendor={ createVendorLink(
'marketplace_product_preview_vendor_clicked'
) }
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card.test.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card.test.tsx
index f10110ba71f..4353ade8838 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card.test.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card.test.tsx
@@ -298,6 +298,26 @@ describe( 'ProductCard click tracking', () => {
} );
} );
+describe( 'ProductCard title', () => {
+ it( 'decodes HTML entities from the API', () => {
+ const { container } = renderCard( ProductCardType.compact, {
+ title: 'Mondial Relay & Chronopost',
+ icon: 'https://example.com/icon.png',
+ } );
+
+ expect(
+ container.querySelector(
+ '.woocommerce-marketplace__product-card__title'
+ )?.textContent
+ ).toContain( 'Mondial Relay & Chronopost' );
+ expect(
+ container
+ .querySelector( '.woocommerce-marketplace__product-card__icon' )
+ ?.getAttribute( 'alt' )
+ ).toBe( 'Mondial Relay & Chronopost' );
+ } );
+} );
+
describe( 'ProductCard product preview experiment', () => {
const productUrl = 'https://woocommerce.com/products/test-extension/';