Commit 4d8bfde8f6f for woocommerce
commit 4d8bfde8f6f58ed9ad1fb4872dc1cf82766d0fd5
Author: Lucio Giannotta <lucio.giannotta@a8c.com>
Date: Wed Sep 9 20:49:35 2026 +0200
Fix variation save persisting the inherited parent image (#68389)
diff --git a/plugins/woocommerce/changelog/fix-wooplug-7653-variation-inherited-image b/plugins/woocommerce/changelog/fix-wooplug-7653-variation-inherited-image
new file mode 100644
index 00000000000..acc571df393
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7653-variation-inherited-image
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Variation gallery: stop persisting the inherited parent image on variation save, prefer variation-owned galleries on the storefront, and clean up previously frozen variation thumbnails.
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index a27be7d4b16..c621ba870c0 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -354,6 +354,7 @@ class WC_Install {
'wc_update_1120_remove_abandoned_cart_recovery',
'wc_update_1120_migrate_stock_notifications_alpha_constant',
'wc_update_1120_delete_surface_cart_checkout_note',
+ 'wc_update_1120_cleanup_inherited_variation_images',
'wc_update_11201_migrate_tax_lookup_order_items',
'wc_update_11201_invalidate_analytics_reports_cache',
'wc_update_11202_reset_refund_returning_customer_markers',
diff --git a/plugins/woocommerce/includes/class-wc-product-variable.php b/plugins/woocommerce/includes/class-wc-product-variable.php
index f03445fecc5..2f5361dfc48 100644
--- a/plugins/woocommerce/includes/class-wc-product-variable.php
+++ b/plugins/woocommerce/includes/class-wc-product-variable.php
@@ -598,10 +598,11 @@ class WC_Product_Variable extends WC_Product {
return false;
}
- $variation_featured_id = (int) $variation->get_image_id();
- $variation_featured_valid = $variation_featured_id && wp_attachment_is_image( $variation_featured_id );
- $parent_featured_id = (int) $this->get_image_id();
- $parent_featured_valid = $parent_featured_id && wp_attachment_is_image( $parent_featured_id );
+ $variation_featured_id = (int) $variation->get_image_id();
+ $variation_featured_is_inherited = ! $variation->get_image_id( 'edit' ) && (int) ( $variation->get_parent_data()['image_id'] ?? 0 ) === $variation_featured_id;
+ $variation_featured_valid = ! $variation_featured_is_inherited && $variation_featured_id && wp_attachment_is_image( $variation_featured_id );
+ $parent_featured_id = (int) $this->get_image_id();
+ $parent_featured_valid = $parent_featured_id && wp_attachment_is_image( $parent_featured_id );
$variation_gallery_image_ids = array_values(
array_filter(
@@ -611,7 +612,6 @@ class WC_Product_Variable extends WC_Product {
);
$variation_gallery_html = '';
- // Prefer variation-owned images over the parent fallback.
if ( $variation_featured_valid ) {
$selected_image_id = $variation_featured_id;
} elseif ( ! empty( $variation_gallery_image_ids ) ) {
diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index 37811b0b6de..bc2886f0e5b 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -40,6 +40,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Registe
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync;
use Automattic\WooCommerce\Internal\StockNotifications\StockNotifications;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
+use Automattic\WooCommerce\Internal\VariationGallery\Telemetry as VariationGalleryTelemetry;
use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil;
use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;
@@ -3717,6 +3718,121 @@ function wc_update_1120_delete_surface_cart_checkout_note(): void {
InboxNotifications::delete_surface_cart_checkout_blocks_notification();
}
+/**
+ * Remove variation featured images that duplicate the parent product's featured image.
+ *
+ * The classic editor used to persist the inherited parent image onto variations on save,
+ * freezing dynamic inheritance. Removing values that still equal the parent's canonical
+ * thumbnail is display-neutral; diverged values may be deliberate and are kept. Skipped
+ * for stores upgrading from before 10.9.0, which predates the variation gallery.
+ * A database error stops the cleanup and is logged.
+ *
+ * @since 11.2.0
+ *
+ * @return bool True to run again for the next batch, false when completed.
+ */
+function wc_update_1120_cleanup_inherited_variation_images() {
+ global $wpdb;
+
+ $state_option = 'woocommerce_update_1120_cleanup_state';
+ $completed_option = 'woocommerce_update_1120_completed_at';
+ $batch_size = 250;
+
+ // A manual db-version rollback replays all update callbacks; this one deletes data, so it must not run twice.
+ if ( get_option( $completed_option ) ) {
+ return false;
+ }
+
+ // Still the pre-update version here: the option is only bumped by the final update callback.
+ if ( version_compare( (string) get_option( 'woocommerce_db_version' ), '10.9.0', '<' ) ) {
+ return false;
+ }
+
+ $state = get_option( $state_option, array() );
+ $state = is_array( $state ) ? $state : array();
+
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names cannot be prepared.
+ $matching_variations = (array) $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT variation.ID AS variation_id,
+ variation.post_parent AS parent_id,
+ GROUP_CONCAT(DISTINCT variation_thumb.meta_value) AS inherited_image_ids
+ FROM {$wpdb->posts} AS variation
+ INNER JOIN {$wpdb->postmeta} AS variation_thumb
+ ON variation_thumb.post_id = variation.ID
+ AND variation_thumb.meta_key = '_thumbnail_id'
+ INNER JOIN {$wpdb->postmeta} AS parent_thumb
+ ON parent_thumb.post_id = variation.post_parent
+ AND parent_thumb.meta_key = '_thumbnail_id'
+ WHERE variation.ID > %d
+ AND variation.post_type = 'product_variation'
+ AND variation_thumb.meta_value <> ''
+ AND variation_thumb.meta_value = parent_thumb.meta_value
+ GROUP BY variation.ID
+ ORDER BY variation.ID ASC
+ LIMIT %d",
+ (int) ( $state['last_processed_id'] ?? 0 ),
+ $batch_size + 1
+ ),
+ ARRAY_A
+ );
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+
+ if ( '' !== $wpdb->last_error ) {
+ wc_get_logger()->error(
+ sprintf( 'Stopped cleaning up inherited variation images: %s', $wpdb->last_error ),
+ array( 'source' => 'wc_update_1120_cleanup_inherited_variation_images' )
+ );
+ delete_option( $state_option );
+
+ return false;
+ }
+
+ $has_more = count( $matching_variations ) > $batch_size;
+ $matching_variations = array_slice( $matching_variations, 0, $batch_size );
+
+ $cleaned_count = (int) ( $state['cleaned_count'] ?? 0 );
+
+ foreach ( $matching_variations as $matching_variation ) {
+ // The join matches any parent thumbnail row; only the canonical value is safe to delete.
+ $canonical_parent_thumbnail = (int) get_post_meta( (int) $matching_variation['parent_id'], '_thumbnail_id', true );
+ $deleted_any = false;
+
+ foreach ( wp_parse_id_list( $matching_variation['inherited_image_ids'] ) as $inherited_image_id ) {
+ if ( $inherited_image_id === $canonical_parent_thumbnail && delete_post_meta( (int) $matching_variation['variation_id'], '_thumbnail_id', $inherited_image_id ) ) {
+ $deleted_any = true;
+ }
+ }
+
+ if ( $deleted_any ) {
+ ++$cleaned_count;
+ }
+ }
+
+ if ( $has_more ) {
+ $last_processed = end( $matching_variations );
+ update_option(
+ $state_option,
+ array(
+ 'last_processed_id' => (int) $last_processed['variation_id'],
+ 'cleaned_count' => $cleaned_count,
+ ),
+ false
+ );
+
+ return true;
+ }
+
+ delete_option( $state_option );
+ update_option( $completed_option, time(), false );
+ VariationGalleryTelemetry::record_event(
+ VariationGalleryTelemetry::EVENT_INHERITED_IMAGE_CLEANUP_COMPLETED,
+ array( 'cleaned_count' => $cleaned_count )
+ );
+
+ return false;
+}
+
/**
* Invalidate the Analytics report cache.
*
diff --git a/plugins/woocommerce/src/Internal/VariationGallery/ClassicVariationGalleryAdmin.php b/plugins/woocommerce/src/Internal/VariationGallery/ClassicVariationGalleryAdmin.php
index 68f0ac81361..a56af0656f7 100644
--- a/plugins/woocommerce/src/Internal/VariationGallery/ClassicVariationGalleryAdmin.php
+++ b/plugins/woocommerce/src/Internal/VariationGallery/ClassicVariationGalleryAdmin.php
@@ -235,8 +235,9 @@ class ClassicVariationGalleryAdmin implements RegisterHooksInterface {
* @return array<int>
*/
private function get_display_image_ids( WC_Product_Variation $variation ): array {
- $image_ids = array_values( array_map( 'intval', $variation->get_gallery_image_ids() ) );
- $featured_id = (int) $variation->get_image_id();
+ $image_ids = array_values( array_map( 'intval', $variation->get_gallery_image_ids() ) );
+ // Read the stored value so an inherited parent image is not saved as the variation's own image.
+ $featured_id = (int) $variation->get_image_id( 'edit' );
if ( $featured_id > 0 && ! in_array( $featured_id, $image_ids, true ) ) {
array_unshift( $image_ids, $featured_id );
diff --git a/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php b/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
index 7d5523c1af4..57c2b48e072 100644
--- a/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
+++ b/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
@@ -16,9 +16,10 @@ defined( 'ABSPATH' ) || exit;
*/
class Telemetry implements RegisterHooksInterface {
- public const EVENT_SAVE_SUCCEEDED = 'variation_gallery_save_succeeded';
- public const EVENT_SAVE_FAILED = 'variation_gallery_save_failed';
- public const EVENT_MIGRATION_COMPLETED = 'variation_gallery_migration_completed';
+ public const EVENT_SAVE_SUCCEEDED = 'variation_gallery_save_succeeded';
+ public const EVENT_SAVE_FAILED = 'variation_gallery_save_failed';
+ public const EVENT_MIGRATION_COMPLETED = 'variation_gallery_migration_completed';
+ public const EVENT_INHERITED_IMAGE_CLEANUP_COMPLETED = 'variation_gallery_inherited_image_cleanup_completed';
private const LEGACY_PLUGIN_FILE = 'woocommerce-additional-variation-images/woocommerce-additional-variation-images.php';
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
index f95ac444f89..f7f051c7f68 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
@@ -287,6 +287,53 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
$this->assertSame( '', $available_variation['gallery_images_html'] );
}
+ /**
+ * @testdox 'get_available_variation' prefers the variation's own gallery over the inherited parent featured image.
+ */
+ public function test_get_available_variation_prefers_own_gallery_over_inherited_parent_image(): void {
+ list( $product, $variation, $variation_gallery_id ) = $this->create_variation_gallery_fixture();
+
+ $available_variation = $product->get_available_variation( $variation );
+
+ $this->assertSame(
+ $variation_gallery_id,
+ $available_variation['image_id'],
+ 'A variation that owns a gallery but no featured image should open on gallery[0], not on the inherited parent image.'
+ );
+ }
+
+ /**
+ * @testdox 'get_available_variation' treats a filtered image equal to the parent featured image as inheritance, so the variation gallery still wins.
+ */
+ public function test_get_available_variation_treats_filtered_parent_image_as_inherited(): void {
+ list( $product, $variation, $variation_gallery_id ) = $this->create_variation_gallery_fixture();
+ $parent_featured_id = $product->get_image_id();
+
+ // A filter returning exactly the parent's featured image is indistinguishable from
+ // plain inheritance, so the variation-owned gallery takes priority by design.
+ $filter = static fn() => $parent_featured_id;
+ add_filter( 'woocommerce_product_variation_get_image_id', $filter );
+ $available_variation = $product->get_available_variation( $variation );
+ remove_filter( 'woocommerce_product_variation_get_image_id', $filter );
+
+ $this->assertSame( $variation_gallery_id, $available_variation['image_id'] );
+ }
+
+ /**
+ * @testdox 'get_available_variation' preserves a filtered variation image when no featured image is stored.
+ */
+ public function test_get_available_variation_preserves_filtered_image(): void {
+ list( $product, $variation ) = $this->create_variation_gallery_fixture();
+ $filtered_image_id = $this->create_image_attachment( 'Filtered Variation Image', 'filtered-variation.jpg' );
+
+ $filter = static fn() => $filtered_image_id;
+ add_filter( 'woocommerce_product_variation_get_image_id', $filter );
+ $available_variation = $product->get_available_variation( $variation );
+ remove_filter( 'woocommerce_product_variation_get_image_id', $filter );
+
+ $this->assertSame( $filtered_image_id, $available_variation['image_id'] );
+ }
+
/**
* @testdox get_variation_prices sorts on first call, skips re-sorting on repeat calls, and treats float and string prices as equal via loose comparison.
*/
@@ -389,6 +436,26 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
);
}
+ /**
+ * Create a product with a parent image and an image-less variation with a gallery.
+ *
+ * @return array{0: WC_Product_Variable, 1: WC_Product_Variation, 2: int}
+ */
+ private function create_variation_gallery_fixture(): array {
+ $product = WC_Helper_Product::create_variation_product();
+ $parent_featured_id = $this->create_image_attachment( 'Parent Featured Image', 'parent-featured.jpg' );
+ $variation_gallery_id = $this->create_image_attachment( 'Variation Gallery Image', 'variation-gallery.jpg' );
+ $product->set_image_id( $parent_featured_id );
+ $product->save();
+
+ wc_get_container()->get( Automattic\WooCommerce\Internal\Caches\ProductCache::class )->flush();
+ $variation = wc_get_product( $product->get_children()[0] );
+ $variation->set_gallery_image_ids( array( $variation_gallery_id ) );
+ $variation->save();
+
+ return array( $product, $variation, $variation_gallery_id );
+ }
+
/**
* Create a real image attachment that passes `wp_attachment_is_image()`.
*
diff --git a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
index 8d24d758f87..4fc24f442df 100644
--- a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
@@ -545,4 +545,99 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
$this->assertNull( $get_marker( $refund->get_id() ), 'The refund row marker should be reset to NULL.' );
$this->assertSame( '0', $get_marker( $order->get_id() ), 'The order row marker should be left unchanged.' );
}
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images removes a variation thumbnail that duplicates the parent's featured image.
+ */
+ public function test_wc_update_1120_removes_variation_thumbnail_duplicating_parent() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '11.1.0' );
+ $variation_id = $this->create_variation_with_thumbnails( '77', '77' );
+
+ $this->assertFalse( wc_update_1120_cleanup_inherited_variation_images(), 'A batch smaller than the limit should complete in one run.' );
+ $this->assertSame( '', get_post_meta( $variation_id, '_thumbnail_id', true ), 'The duplicated thumbnail should be removed so the variation inherits again.' );
+ }
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images keeps a variation thumbnail that diverged from the parent's featured image.
+ */
+ public function test_wc_update_1120_keeps_diverged_variation_thumbnail() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '11.1.0' );
+ $variation_id = $this->create_variation_with_thumbnails( '77', '88' );
+
+ $this->assertFalse( wc_update_1120_cleanup_inherited_variation_images() );
+ $this->assertSame( '88', get_post_meta( $variation_id, '_thumbnail_id', true ), 'A diverged value may be a deliberate merchant choice and must survive the cleanup.' );
+ }
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images removes only metadata values that duplicate the parent image.
+ */
+ public function test_wc_update_1120_keeps_other_thumbnail_metadata_values() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '11.1.0' );
+ $variation_id = $this->create_variation_with_thumbnails( '77', '77' );
+ add_post_meta( $variation_id, '_thumbnail_id', '88' );
+
+ wc_update_1120_cleanup_inherited_variation_images();
+
+ $this->assertSame( array( '88' ), get_post_meta( $variation_id, '_thumbnail_id', false ), 'Only the value duplicating the parent image should be removed.' );
+ }
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images never runs again once completed, even if update callbacks are replayed.
+ */
+ public function test_wc_update_1120_does_not_run_again_after_completion() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '11.1.0' );
+ update_option( 'woocommerce_update_1120_completed_at', time() );
+ $variation_id = $this->create_variation_with_thumbnails( '77', '77' );
+
+ $this->assertFalse( wc_update_1120_cleanup_inherited_variation_images() );
+ $this->assertSame( '77', get_post_meta( $variation_id, '_thumbnail_id', true ), 'A value matching the parent after the one-time cleanup may be deliberate and must survive a replay.' );
+ }
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images matches only the parent's canonical thumbnail, not stale duplicate rows.
+ */
+ public function test_wc_update_1120_ignores_stale_duplicate_parent_thumbnail_rows() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '11.1.0' );
+ $variation_id = $this->create_variation_with_thumbnails( '88', '77' );
+ $parent_id = wp_get_post_parent_id( $variation_id );
+ add_post_meta( $parent_id, '_thumbnail_id', '77' );
+
+ wc_update_1120_cleanup_inherited_variation_images();
+
+ $this->assertSame( '77', get_post_meta( $variation_id, '_thumbnail_id', true ), 'A value matching only a stale duplicate parent row may be deliberate and must survive.' );
+ }
+
+ /**
+ * @testdox wc_update_1120_cleanup_inherited_variation_images skips stores upgrading from before the variation gallery existed.
+ */
+ public function test_wc_update_1120_skips_stores_upgrading_from_before_10_9() {
+ include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+ update_option( 'woocommerce_db_version', '10.8.0' );
+ $variation_id = $this->create_variation_with_thumbnails( '77', '77' );
+
+ $this->assertFalse( wc_update_1120_cleanup_inherited_variation_images() );
+ $this->assertSame( '77', get_post_meta( $variation_id, '_thumbnail_id', true ), 'Stores never exposed to the bug must not be touched.' );
+ }
+
+ /**
+ * Create a variable product and return its first variation's ID, with the given thumbnail meta on parent and variation.
+ *
+ * @param string $parent_thumbnail_id Meta value for the parent's _thumbnail_id.
+ * @param string $variation_thumbnail_id Meta value for the variation's _thumbnail_id.
+ * @return int
+ */
+ private function create_variation_with_thumbnails( string $parent_thumbnail_id, string $variation_thumbnail_id ): int {
+ $product = WC_Helper_Product::create_variation_product();
+ $variation_id = $product->get_children()[0];
+
+ update_post_meta( $product->get_id(), '_thumbnail_id', $parent_thumbnail_id );
+ update_post_meta( $variation_id, '_thumbnail_id', $variation_thumbnail_id );
+
+ return $variation_id;
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/VariationGallery/ClassicVariationGalleryAdminTest.php b/plugins/woocommerce/tests/php/src/Internal/VariationGallery/ClassicVariationGalleryAdminTest.php
index d13f130b9e1..009875601ef 100644
--- a/plugins/woocommerce/tests/php/src/Internal/VariationGallery/ClassicVariationGalleryAdminTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/VariationGallery/ClassicVariationGalleryAdminTest.php
@@ -64,9 +64,7 @@ class ClassicVariationGalleryAdminTest extends \WC_Unit_Test_Case {
implode( ',', $image_ids )
);
- ob_start();
- $this->sut->render_variation_gallery_field( 0, array(), get_post( $variation->get_id() ) );
- $output = (string) ob_get_clean();
+ $output = $this->render_variation_gallery_field( $variation );
$this->assertStringContainsString(
'value="' . implode( ',', $image_ids ) . '"',
@@ -86,9 +84,7 @@ class ClassicVariationGalleryAdminTest extends \WC_Unit_Test_Case {
$variation->set_gallery_image_ids( array( $gallery ) );
$variation->save();
- ob_start();
- $this->sut->render_variation_gallery_field( 0, array(), get_post( $variation->get_id() ) );
- $output = (string) ob_get_clean();
+ $output = $this->render_variation_gallery_field( $variation );
// Featured image must be prepended to the list (value) AND get the active class.
$this->assertStringContainsString( 'value="' . $featured . ',' . $gallery . '"', $output );
@@ -109,9 +105,7 @@ class ClassicVariationGalleryAdminTest extends \WC_Unit_Test_Case {
$variation->set_gallery_image_ids( array( $missing, $good ) );
$variation->save();
- ob_start();
- $this->sut->render_variation_gallery_field( 0, array(), get_post( $variation->get_id() ) );
- $output = (string) ob_get_clean();
+ $output = $this->render_variation_gallery_field( $variation );
$this->assertStringContainsString(
'data-attachment_id="' . $missing . '"',
@@ -119,20 +113,6 @@ class ClassicVariationGalleryAdminTest extends \WC_Unit_Test_Case {
);
}
- /**
- * @testdox Empty variation rows render the hidden gallery input needed for the first save.
- */
- public function test_render_variation_gallery_field_renders_hidden_input_for_empty_gallery() {
- $variation = $this->create_variation();
-
- ob_start();
- $this->sut->render_variation_gallery_field( 0, array(), get_post( $variation->get_id() ) );
- $output = (string) ob_get_clean();
-
- $this->assertStringContainsString( 'name="variable_gallery_image_ids[0]"', $output );
- $this->assertStringContainsString( 'value=""', $output );
- }
-
/**
* @testdox Saving an empty variation gallery clears featured + gallery and disables the legacy fallback.
*/
@@ -198,6 +178,44 @@ class ClassicVariationGalleryAdminTest extends \WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox An image-less variation does not render its inherited parent image as variation data.
+ */
+ public function test_render_variation_gallery_field_excludes_inherited_parent_image(): void {
+ $variation = $this->create_variation_inheriting_parent_image();
+ $output = $this->render_variation_gallery_field( $variation );
+
+ $this->assertStringContainsString( 'value=""', $output, 'The inherited parent image must not be submitted as variation data.' );
+ }
+
+ /**
+ * Render the gallery field for a variation.
+ *
+ * @param WC_Product_Variation $variation Variation object.
+ * @return string
+ */
+ private function render_variation_gallery_field( WC_Product_Variation $variation ): string {
+ ob_start();
+ $this->sut->render_variation_gallery_field( 0, array(), get_post( $variation->get_id() ) );
+
+ return (string) ob_get_clean();
+ }
+
+ /**
+ * Create a variation that inherits its parent's image.
+ *
+ * @return WC_Product_Variation
+ */
+ private function create_variation_inheriting_parent_image(): WC_Product_Variation {
+ $variation = $this->create_variation();
+ $parent = wc_get_product( $variation->get_parent_id() );
+ $parent->set_image_id( $this->create_attachment( 'Parent featured image' ) );
+ $parent->save();
+ wc_get_container()->get( \Automattic\WooCommerce\Internal\Caches\ProductCache::class )->flush();
+
+ return $variation;
+ }
+
/**
* Create a variation for testing.
*