Commit cb31ea3dd06 for woocommerce

commit cb31ea3dd0602ac6bef36401d90ae7dbc049b084
Author: Cem Ünalan <raicem@users.noreply.github.com>
Date:   Wed Sep 2 10:59:45 2026 +0300

    Hide empty star ratings on Discover page product cards (#67882)

    * Hide marketplace rating block when a product has no rating

    The in-app Marketplace product card guarded its rating block with
    `product.averageRating !== null`, which lets `undefined` through:
    `undefined !== null` is true. When it does, the block renders a filled
    star with no number beside it, announced to screen readers as
    "0.0 stars" via the `?? 0` fallback — a zero rating for a product that
    has none.

    `undefined` is reachable because the two paths that build Product
    objects normalize differently. The search path maps the response field
    by field with `averageRating: product.rating ?? null`, so a missing
    rating becomes null. The Discover path casts the featured response to
    ProductGroup[] with no mapping, so whatever keys the API sends land on
    the object verbatim, and an absent key reads as `undefined`. The type
    allows it: `averageRating?: number | null`.

    Guard on `typeof === 'number'` instead, which excludes null and
    undefined alike, and drop the now-unreachable `?? 0` fallback.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    * Hide marketplace rating block when the rating is zero

    * Reject non-numeric ratings and assert the star icon is absent in tests

    * Inline no-rating test assertions to satisfy jest/expect-expect

    ---------

    Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-marketplace-missing-rating-guard b/plugins/woocommerce/changelog/fix-marketplace-missing-rating-guard
new file mode 100644
index 00000000000..b558949705f
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-marketplace-missing-rating-guard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Hide the rating block on in-app Marketplace product cards when a product has no rating
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card-footer.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card-footer.tsx
index da5dbc676e2..3612d3475ca 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card-footer.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/product-card-footer.tsx
@@ -199,6 +199,9 @@ function ProductCardFooter( props: { product: Product } ) {
 		);
 	}

+	// Ratings run 1-5, so 0 -- like a missing value -- means "no rating".
+	const averageRating = product.averageRating ?? 0;
+
 	return (
 		<>
 			<div className="woocommerce-marketplace__product-card__price">
@@ -230,18 +233,18 @@ function ProductCardFooter( props: { product: Product } ) {
 				</span>
 			</div>
 			<div className="woocommerce-marketplace__product-card__rating">
-				{ product.averageRating !== null && (
+				{ Number.isFinite( averageRating ) && averageRating > 0 && (
 					<>
 						<span className="woocommerce-marketplace__product-card__rating-icon">
 							<Icon icon={ 'star-filled' } size={ 16 } />
 						</span>
 						<span className="woocommerce-marketplace__product-card__rating-average">
-							<span aria-hidden>{ product.averageRating }</span>
+							<span aria-hidden>{ averageRating }</span>
 							<span className="screen-reader-text">
 								{ sprintf(
 									// translators: %.1f: average rating
 									__( '%.1f stars', 'woocommerce' ),
-									product.averageRating ?? 0
+									averageRating
 								) }
 							</span>
 						</span>
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card-footer.test.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card-footer.test.tsx
new file mode 100644
index 00000000000..966f719cda1
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/product-card/test/product-card-footer.test.tsx
@@ -0,0 +1,113 @@
+/**
+ * External dependencies
+ */
+import { render } from '@testing-library/react';
+import React from 'react';
+
+jest.mock( '@woocommerce/navigation', () => ( {
+	getNewPath: jest.fn( () => '/new-path' ),
+	navigateTo: jest.fn(),
+} ) );
+
+jest.mock( '@woocommerce/tracks', () => ( {
+	recordEvent: jest.fn(),
+} ) );
+
+jest.mock( '@woocommerce/data', () => ( {
+	useUser: jest.fn( () => ( {
+		user: null,
+		currentUserCan: jest.fn( () => false ),
+	} ) ),
+} ) );
+
+/**
+ * Internal dependencies
+ */
+import ProductCardFooter from '../product-card-footer';
+import { MarketplaceContext } from '../../../contexts/marketplace-context';
+import { MarketplaceContextType } from '../../../contexts/types';
+import { Product, ProductType } from '../../product-list/types';
+
+const context = {
+	selectedTab: 'extensions',
+	isProductInstalled: () => false,
+} as unknown as MarketplaceContextType;
+
+const product: Product = {
+	id: 1,
+	title: 'Test extension',
+	image: '',
+	type: ProductType.extension,
+	description: '',
+	vendorName: '',
+	vendorUrl: '',
+	icon: '',
+	url: '',
+	price: 0,
+	isInstallable: false,
+	currency: 'USD',
+	isOnSale: false,
+	regularPrice: 0,
+	reviewsCount: 10,
+};
+
+function renderFooter( averageRating: number | null | undefined ) {
+	return render(
+		<MarketplaceContext.Provider value={ context }>
+			<ProductCardFooter product={ { ...product, averageRating } } />
+		</MarketplaceContext.Provider>
+	);
+}
+
+describe( 'ProductCardFooter rating', () => {
+	it( 'renders the rating when the product has one', () => {
+		const { getByText } = renderFooter( 4.5 );
+
+		expect( getByText( '4.5' ) ).toBeInTheDocument();
+	} );
+
+	it( 'renders no rating when the product has none', () => {
+		const { container } = renderFooter( null );
+
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating'
+			)?.textContent
+		).toBe( '' );
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating-icon'
+			)
+		).toBeNull();
+	} );
+
+	it( 'renders no rating when the rating is zero', () => {
+		const { container } = renderFooter( 0 );
+
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating'
+			)?.textContent
+		).toBe( '' );
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating-icon'
+			)
+		).toBeNull();
+	} );
+
+	it( 'renders no rating when the API omits the rating', () => {
+		const { container } = renderFooter( undefined );
+
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating'
+			)?.textContent
+		).toBe( '' );
+		expect(
+			container.querySelector(
+				'.woocommerce-marketplace__product-card__rating-icon'
+			)
+		).toBeNull();
+	} );
+} );