Commit 0b6e03b6fac for woocommerce

commit 0b6e03b6face548955ba403bb1afb201606b7ccd
Author: Faisal Ahammad <faisalahammad24@gmail.com>
Date:   Fri Sep 11 10:18:25 2026 +0600

    Add filters for default Analytics order status selections (#66288)

    * enhancement: add filters for default analytics order status selections

    Closes #53323. Plugins can now hook woocommerce_analytics_settings_default_excluded_order_statuses
    and woocommerce_analytics_settings_default_actionable_order_statuses to activate a custom
    order status by default on the Analytics Settings screen.

    * fix(analytics): fallback when all filtered default statuses are unregistered

    - sanitize_default_order_statuses() now returns $fallback when array_intersect yields empty
    - Added @param tags to both new filter docblocks
    - Added test for all-invalid slug fallback case

    Addresses PR feedback.

    Refs #66288

    * fix(analytics): make default status filters the source of truth for report consumers

    - DataStore::get_excluded_report_order_statuses uses filter as option
      fallback instead of hardcoded array, plus is_array guard for safety.
    - CustomerHistory::get_excluded_statuses same approach.
    - OrderAwareControllerTrait::get_order_statuses uses
      woocommerce_analytics_settings_default_actionable_order_statuses
      filter as option fallback.

    This ensures a custom order status added via the filters actually
    affects analytics report computation without requiring the merchant
    to open and save Settings first.

    Addresses PR #66288 review feedback.

    Refs #66288

    * fix(analytics): validate default status filters consistently

    - Add Settings::get_valid_order_statuses_or_default() as the single
      place that decides whether a default-order-statuses filter/option
      value is usable, and use it in Settings, DataStore,
      OrderAwareControllerTrait, and CustomerHistory instead of four
      duplicated is_array() guards.
    - Fix OrderAwareControllerTrait::get_order_statuses() falling back to
      an empty array instead of the built-in actionable-status default.
    - Validate the woocommerce_analytics_excluded_order_statuses filter
      result in DataStore.php the same way CustomerHistory.php already
      does (CodeRabbit finding).
    - Bump @since 11.0.0 to 11.1.0 on all four filter docblocks (unreleased
      hooks, current dev version is 11.1.0).
    - Add PHPUnit coverage for the shared method and the fallback paths in
      all three runtime call sites.

    Addresses PR feedback.

    Refs #66288

    * fix(analytics): harden default order status filter handling

    Address review feedback on PR #66288.

    - Keep an explicitly saved empty selection instead of restoring the
      built-in defaults, so report totals match the settings page
    - Add single accessors for the default excluded and actionable
      statuses, and use them across DataStore, OrderAwareControllerTrait,
      CustomerHistory, and ActivityPanelCounts so the filters are the
      source of truth for runtime consumers too
    - Fall back to the pre-filter value and trigger a doing-it-wrong
      notice when the runtime filter returns a non-array
    - Drop non-string elements before using filtered values
    - Add positive filter tests for every runtime consumer and clean up
      test filters in tearDown
    - Bump new @since tags to 11.2.0 and rewrite the changelog entry to
      state the runtime effect

    * fix(analytics): trim order status slugs and drop blanks in default validation

    - trim each string slug in Settings::get_valid_order_statuses_or_default()
    - drop slugs that are empty after trimming, keeping the empty-array and
      non-array fallback rules unchanged
    - add provider cases for blank, padded and all-blank input
    - add a DataStore test proving blank saved slugs no longer reach runtime

    Addresses PR feedback.

    Refs #66288

diff --git a/plugins/woocommerce/changelog/enhancement-53323-analytics-default-status-filters b/plugins/woocommerce/changelog/enhancement-53323-analytics-default-status-filters
new file mode 100644
index 00000000000..3e0d8984f8a
--- /dev/null
+++ b/plugins/woocommerce/changelog/enhancement-53323-analytics-default-status-filters
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Introduce two new PHP filters, woocommerce_analytics_settings_default_excluded_order_statuses and woocommerce_analytics_settings_default_actionable_order_statuses, that let plugins change the default order statuses used by Analytics. The filters apply everywhere, not only to the boxes pre-checked on the Analytics Settings screen: on a store that has never saved these options they also change report totals, the order status enum returned by the reports REST controllers, the Activity Panel orders-to-fulfill count, and the Customer History box.
diff --git a/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php b/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
index 53dc4f13d00..4ca5631f5a2 100644
--- a/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
+++ b/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
@@ -9,6 +9,8 @@ declare( strict_types=1 );

 namespace Automattic\WooCommerce\Admin\API;

+use Automattic\WooCommerce\Internal\Admin\Settings;
+
 defined( 'ABSPATH' ) || exit;

 /**
@@ -173,11 +175,12 @@ class ActivityPanelCounts extends \WC_REST_Data_Controller {
 	private function get_default_order_statuses() {
 		$actionable = get_option( 'woocommerce_actionable_order_statuses', false );

-		// Any array is respected as-is, including an explicitly empty one: the merchant
+		// Any valid array is respected as-is, including an explicitly empty one: the merchant
 		// intentionally cleared all actionable statuses, so there is nothing to fulfill,
 		// matching the previous client-side behaviour. A missing (never configured) or
-		// malformed option falls back to the built-in defaults.
-		return is_array( $actionable ) ? $actionable : array( 'processing', 'on-hold' );
+		// malformed option falls back to the filtered default, matching the Analytics
+		// Settings screen and the reports.
+		return Settings::get_valid_order_statuses_or_default( $actionable, Settings::get_default_actionable_order_statuses() );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
index 491fb2863d1..2b7aa8d2eab 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
@@ -11,6 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) {

 use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
 use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
+use Automattic\WooCommerce\Internal\Admin\Settings;

 /**
  * Common parent for custom report data stores.
@@ -842,9 +843,27 @@ class DataStore extends SqlQuery implements DataStoreInterface {
 	 * @return array
 	 */
 	protected static function get_excluded_report_order_statuses() {
-		$excluded_statuses = \WC_Admin_Settings::get_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled' ) );
-		$excluded_statuses = array_merge( array( 'auto-draft', 'trash' ), array_map( 'esc_sql', $excluded_statuses ) );
-		return apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses );
+		$default_excluded_statuses = Settings::get_default_excluded_order_statuses();
+		$excluded_statuses         = \WC_Admin_Settings::get_option( 'woocommerce_excluded_report_order_statuses', $default_excluded_statuses );
+		$excluded_statuses         = Settings::get_valid_order_statuses_or_default( $excluded_statuses, $default_excluded_statuses );
+		$excluded_statuses         = array_merge( array( 'auto-draft', 'trash' ), array_map( 'esc_sql', $excluded_statuses ) );
+
+		// Keep the value a broken filter would otherwise replace, so the merchant's saved
+		// selection survives it.
+		$pre_filter_statuses = $excluded_statuses;
+
+		/**
+		 * Filter the list of excluded order statuses for customer history and analytics reports.
+		 *
+		 * @since 4.0.0
+		 * @param array $excluded_statuses Order statuses to exclude.
+		 */
+		$excluded_statuses = apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses );
+		if ( ! is_array( $excluded_statuses ) ) {
+			wc_doing_it_wrong( __METHOD__, 'The woocommerce_analytics_excluded_order_statuses filter must return an array.', '11.2.0' );
+			$excluded_statuses = $pre_filter_statuses;
+		}
+		return $excluded_statuses;
 	}

 	/**
diff --git a/plugins/woocommerce/src/Admin/API/Reports/OrderAwareControllerTrait.php b/plugins/woocommerce/src/Admin/API/Reports/OrderAwareControllerTrait.php
index 980f169e95a..8d0c653eceb 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/OrderAwareControllerTrait.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/OrderAwareControllerTrait.php
@@ -3,6 +3,8 @@ declare( strict_types = 1);

 namespace Automattic\WooCommerce\Admin\API\Reports;

+use Automattic\WooCommerce\Internal\Admin\Settings;
+
 // Exit if accessed directly.
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
@@ -93,12 +95,11 @@ trait OrderAwareControllerTrait {
 	public static function get_order_statuses() {
 		// Allow all statuses selected as "actionable" - this may include unregistered statuses.
 		// See: https://github.com/woocommerce/woocommerce-admin/issues/5592.
-		$actionable_statuses = get_option( 'woocommerce_actionable_order_statuses', array() );
+		$default_actionable_statuses = Settings::get_default_actionable_order_statuses();
+		$actionable_statuses         = get_option( 'woocommerce_actionable_order_statuses', $default_actionable_statuses );

 		// Prevent errors if the database entry is not the expected type (array).
-		if ( ! is_array( $actionable_statuses ) ) {
-			$actionable_statuses = array();
-		}
+		$actionable_statuses = Settings::get_valid_order_statuses_or_default( $actionable_statuses, $default_actionable_statuses );

 		// See WC_REST_Orders_V2_Controller::get_collection_params() re: any/trash statuses.
 		$registered_statuses = array_merge( array( 'any', 'trash' ), array_keys( self::get_order_status_labels() ) );
diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
index 537a2dd1495..165ef1d852a 100644
--- a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
+++ b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
@@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes;

 use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
 use Automattic\WooCommerce\Admin\API\Reports\Customers\Query as CustomersQuery;
+use Automattic\WooCommerce\Admin\Overrides\Order as AdminOrder;
+use Automattic\WooCommerce\Internal\Admin\Settings;
 use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
 use Automattic\WooCommerce\Utilities\OrderUtil;
 use WC_Order;
@@ -243,11 +245,14 @@ class CustomerHistory {
 			return $this->excluded_statuses;
 		}

-		$excluded_statuses = get_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled' ) );
-		if ( ! is_array( $excluded_statuses ) ) {
-			$excluded_statuses = array( 'pending', 'failed', 'cancelled' );
-		}
-		$excluded_statuses = array_merge( array( 'auto-draft', 'trash' ), $excluded_statuses );
+		$default_excluded_statuses = Settings::get_default_excluded_order_statuses();
+		$excluded_statuses         = get_option( 'woocommerce_excluded_report_order_statuses', $default_excluded_statuses );
+		$excluded_statuses         = Settings::get_valid_order_statuses_or_default( $excluded_statuses, $default_excluded_statuses );
+		$excluded_statuses         = array_merge( array( 'auto-draft', 'trash' ), $excluded_statuses );
+
+		// Keep the value a broken filter would otherwise replace, so the merchant's saved
+		// selection survives it. Mirrors Reports\DataStore::get_excluded_report_order_statuses().
+		$pre_filter_statuses = $excluded_statuses;

 		/**
 		 * Filter the list of excluded order statuses for customer history and analytics reports.
@@ -257,7 +262,8 @@ class CustomerHistory {
 		 */
 		$excluded_statuses = apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses );
 		if ( ! is_array( $excluded_statuses ) ) {
-			$excluded_statuses = array( 'auto-draft', 'trash', 'pending', 'failed', 'cancelled' );
+			wc_doing_it_wrong( __METHOD__, 'The woocommerce_analytics_excluded_order_statuses filter must return an array.', '11.2.0' );
+			$excluded_statuses = $pre_filter_statuses;
 		}

 		$this->excluded_statuses = $excluded_statuses;
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php
index f2da74679ed..f17e43bd3c4 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings.php
@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore
 use Automattic\WooCommerce\Admin\Features\Features;
 use Automattic\WooCommerce\Admin\PageController;
 use Automattic\WooCommerce\Admin\PluginsHelper;
+use Automattic\WooCommerce\Enums\OrderStatus;
 use Automattic\WooCommerce\Internal\Admin\Settings\SettingsUIRequestContext;
 use Automattic\WooCommerce\Internal\Utilities\PriceSeparators;
 use Automattic\WooCommerce\Utilities\FeaturesUtil;
@@ -80,6 +81,127 @@ class Settings {
 		return array_combine( $formatted_statuses, $formatted_statuses );
 	}

+	/**
+	 * Validate a filtered list of default order statuses for display in the Settings UI.
+	 *
+	 * Drops slugs that are not registered or synced (they cannot be shown as checkboxes).
+	 * An explicitly empty list stays empty so the UI agrees with the runtime accessors; a
+	 * non-empty list with nothing displayable left falls back to the built-in default, and
+	 * a non-array falls back too. This cross-check is a display-only concern: runtime
+	 * consumers of the same options accept any string slug via
+	 * get_valid_order_statuses_or_default().
+	 *
+	 * @param mixed $statuses     Value returned by a default-order-statuses filter.
+	 * @param array $all_statuses Known order statuses, keyed by status slug.
+	 * @param array $fallback     Built-in default to use if nothing displayable remains.
+	 * @return array
+	 */
+	private function sanitize_default_order_statuses( $statuses, $all_statuses, $fallback ) {
+		$valid_statuses = self::get_valid_order_statuses_or_default( $statuses, $fallback );
+		if ( empty( $valid_statuses ) ) {
+			return $valid_statuses;
+		}
+		$sanitized = array_values( array_intersect( $valid_statuses, array_keys( $all_statuses ) ) );
+		return empty( $sanitized ) ? $fallback : $sanitized;
+	}
+
+	/**
+	 * The default order statuses excluded from Analytics report totals, before a merchant
+	 * saves the option. Single source of truth for the Settings UI and every runtime
+	 * consumer, so the filtered value applies everywhere without saving the option.
+	 *
+	 * @since 11.2.0
+	 *
+	 * @return array
+	 */
+	public static function get_default_excluded_order_statuses(): array {
+		$builtin_default = array( OrderStatus::PENDING, OrderStatus::CANCELLED, OrderStatus::FAILED );
+
+		/**
+		 * Filters the default set of order statuses excluded from Analytics report totals.
+		 *
+		 * @since 11.2.0
+		 *
+		 * @param array $default_statuses Default excluded order statuses.
+		 */
+		$filtered = apply_filters( 'woocommerce_analytics_settings_default_excluded_order_statuses', $builtin_default );
+
+		return self::get_valid_order_statuses_or_default( $filtered, $builtin_default );
+	}
+
+	/**
+	 * The default order statuses considered actionable in Analytics, before a merchant
+	 * saves the option. Single source of truth for the Settings UI and every runtime
+	 * consumer, so the filtered value applies everywhere without saving the option.
+	 *
+	 * @since 11.2.0
+	 *
+	 * @return array
+	 */
+	public static function get_default_actionable_order_statuses(): array {
+		$builtin_default = array( OrderStatus::PROCESSING, OrderStatus::ON_HOLD );
+
+		/**
+		 * Filters the default set of order statuses considered actionable in Analytics.
+		 *
+		 * @since 11.2.0
+		 *
+		 * @param array $default_statuses Default actionable order statuses.
+		 */
+		$filtered = apply_filters( 'woocommerce_analytics_settings_default_actionable_order_statuses', $builtin_default );
+
+		return self::get_valid_order_statuses_or_default( $filtered, $builtin_default );
+	}
+
+	/**
+	 * Validate a value that should be an array of order status slugs (an option value or a
+	 * default-order-statuses filter's return value), falling back to the passed default if
+	 * it is not an array.
+	 *
+	 * An explicitly saved empty array passes through: the merchant intentionally cleared
+	 * the selection, and replacing it with the defaults would make reports disagree with
+	 * the Settings page. Non-string elements are dropped, and string slugs are trimmed,
+	 * so a broken filter return value cannot raise warnings or leave a blank slug that
+	 * the Settings UI drops but runtime consumers keep.
+	 *
+	 * This is a cheap type check only. Slugs are not cross-checked against registered or
+	 * synced order statuses, because runtime call sites intentionally accept slugs outside
+	 * that set and must not pay for OrdersDataStore::get_all_statuses()'s DB-backed lookup
+	 * on every request. Only add_settings() needs the fuller display cross-check, via
+	 * sanitize_default_order_statuses().
+	 *
+	 * @since 11.2.0
+	 *
+	 * @param mixed $value    Value to validate.
+	 * @param array $fallback Default to use if $value is not an array.
+	 * @return array
+	 */
+	public static function get_valid_order_statuses_or_default( $value, array $fallback ): array {
+		if ( ! is_array( $value ) ) {
+			return $fallback;
+		}
+
+		// An intentionally empty array means "no statuses" and must survive as-is, whether
+		// it came from a saved option or a filter.
+		if ( empty( $value ) ) {
+			return array();
+		}
+
+		$statuses = array_map( 'trim', array_filter( $value, 'is_string' ) );
+		$statuses = array_values(
+			array_filter(
+				$statuses,
+				static function ( $status ) {
+					return '' !== $status;
+				}
+			)
+		);
+
+		// A non-empty array whose elements are all unusable is treated as invalid, so a
+		// broken callback does not silently empty the selection.
+		return empty( $statuses ) ? $fallback : $statuses;
+	}
+
 	/**
 	 * Return an object defining the currency options for the site's current currency
 	 *
@@ -322,12 +444,17 @@ class Settings {
 		$registered_statuses   = self::get_order_statuses( wc_get_order_statuses() );
 		$all_statuses          = array_merge( $unregistered_statuses, $registered_statuses );

+		// Only the Settings UI needs the display cross-check; runtime consumers use the
+		// accessor values as-is.
+		$default_excluded_statuses   = $this->sanitize_default_order_statuses( self::get_default_excluded_order_statuses(), $all_statuses, array( OrderStatus::PENDING, OrderStatus::CANCELLED, OrderStatus::FAILED ) );
+		$default_actionable_statuses = $this->sanitize_default_order_statuses( self::get_default_actionable_order_statuses(), $all_statuses, array( OrderStatus::PROCESSING, OrderStatus::ON_HOLD ) );
+
 		$settings[] = array(
 			'id'          => 'woocommerce_excluded_report_order_statuses',
 			'option_key'  => 'woocommerce_excluded_report_order_statuses',
 			'label'       => __( 'Excluded report order statuses', 'woocommerce' ),
 			'description' => __( 'Statuses that should not be included when calculating report totals.', 'woocommerce' ),
-			'default'     => array( 'pending', 'cancelled', 'failed' ),
+			'default'     => $default_excluded_statuses,
 			'type'        => 'multiselect',
 			'options'     => $all_statuses,
 		);
@@ -336,7 +463,7 @@ class Settings {
 			'option_key'  => 'woocommerce_actionable_order_statuses',
 			'label'       => __( 'Actionable order statuses', 'woocommerce' ),
 			'description' => __( 'Statuses that require extra action on behalf of the store admin.', 'woocommerce' ),
-			'default'     => array( 'processing', 'on-hold' ),
+			'default'     => $default_actionable_statuses,
 			'type'        => 'multiselect',
 			'options'     => $all_statuses,
 		);
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php b/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
index b4d3dce05a1..5660968faa5 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
@@ -243,4 +243,35 @@ class ActivityPanelCountsTest extends WC_REST_Unit_Test_Case {
 		$this->assertEquals( 200, $response->get_status() );
 		$this->assertSame( 2, $data['orders_to_fulfill_count'] );
 	}
+
+	/**
+	 * Test that the filter-added actionable status drives the endpoint's default count when
+	 * the option has never been saved, matching the reports and the Analytics Settings screen.
+	 * Counts are asymmetric (1 processing, 2 completed) so falling back to the built-in
+	 * processing/on-hold defaults would count 1 instead of 3.
+	 */
+	public function test_filtered_default_actionable_statuses_drive_default_count() {
+		wp_set_current_user( $this->user );
+
+		WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::PROCESSING ) );
+		WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::COMPLETED ) );
+		WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::COMPLETED ) );
+
+		delete_option( 'woocommerce_actionable_order_statuses' );
+		$add_completed = function ( $statuses ) {
+			$statuses[] = OrderStatus::COMPLETED;
+			return $statuses;
+		};
+		add_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses', $add_completed );
+		$this->reregister_routes();
+
+		$request  = new WP_REST_Request( 'GET', self::ENDPOINT );
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
+
+		remove_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses', $add_completed );
+
+		$this->assertEquals( 200, $response->get_status() );
+		$this->assertSame( 3, $data['orders_to_fulfill_count'] );
+	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/DataStore/ExcludedOrderStatusesTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/DataStore/ExcludedOrderStatusesTest.php
new file mode 100644
index 00000000000..47a259edf0a
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/DataStore/ExcludedOrderStatusesTest.php
@@ -0,0 +1,141 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\API\Reports\DataStore;
+
+use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the base Reports DataStore excluded order statuses default.
+ */
+class ExcludedOrderStatusesTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Filter hooks added during a test, as [ tag, callback ] pairs, removed again in tearDown().
+	 *
+	 * @var array[]
+	 */
+	private $added_filters = array();
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		foreach ( $this->added_filters as $added_filter ) {
+			remove_filter( $added_filter[0], $added_filter[1] );
+		}
+		$this->added_filters = array();
+		delete_option( 'woocommerce_excluded_report_order_statuses' );
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses defaults to pending, failed, cancelled (plus auto-draft, trash) when not filtered.
+	 */
+	public function test_default_excluded_order_statuses_without_filter(): void {
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		$this->assertContains( 'pending', $statuses );
+		$this->assertContains( 'failed', $statuses );
+		$this->assertContains( 'cancelled', $statuses );
+		$this->assertContains( 'auto-draft', $statuses );
+		$this->assertContains( 'trash', $statuses );
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses falls back to the built-in default when the default-statuses filter returns a non-array.
+	 */
+	public function test_default_excluded_order_statuses_filter_invalid_type_falls_back(): void {
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		remove_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+
+		$this->assertContains( 'pending', $statuses, 'Should still contain the built-in default "pending" status.' );
+		$this->assertContains( 'failed', $statuses, 'Should still contain the built-in default "failed" status.' );
+		$this->assertContains( 'cancelled', $statuses, 'Should still contain the built-in default "cancelled" status.' );
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses reflects a custom status added by the default-statuses filter when the option has never been saved.
+	 */
+	public function test_default_excluded_order_statuses_filter_reaches_runtime(): void {
+		delete_option( 'woocommerce_excluded_report_order_statuses' );
+		$callback = array( $this, 'add_custom_excluded_status' );
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', $callback );
+		$this->added_filters[] = array( 'woocommerce_analytics_settings_default_excluded_order_statuses', $callback );
+
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		$this->assertContains( 'custom-excluded-status', $statuses, 'A status added by the filter should reach the runtime consumer.' );
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses respects an explicitly saved empty selection instead of restoring the defaults.
+	 */
+	public function test_saved_empty_excluded_statuses_are_respected(): void {
+		update_option( 'woocommerce_excluded_report_order_statuses', array() );
+
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		$this->assertNotContains( 'pending', $statuses, 'A cleared selection must not fall back to the built-in defaults.' );
+		$this->assertNotContains( 'failed', $statuses, 'A cleared selection must not fall back to the built-in defaults.' );
+		$this->assertNotContains( 'cancelled', $statuses, 'A cleared selection must not fall back to the built-in defaults.' );
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses trims padded slugs and drops blank ones saved in the option.
+	 */
+	public function test_blank_saved_statuses_do_not_reach_runtime(): void {
+		update_option( 'woocommerce_excluded_report_order_statuses', array( ' pending ', '', ' ' ) );
+
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		$this->assertContains( 'pending', $statuses, 'A padded slug should be trimmed and kept.' );
+		$this->assertNotContains( ' pending ', $statuses, 'A padded slug must not reach runtime consumers untrimmed.' );
+		$this->assertNotContains( '', $statuses, 'A blank slug must not reach runtime consumers.' );
+		$this->assertNotContains( ' ', $statuses, 'A whitespace-only slug must not reach runtime consumers.' );
+	}
+
+	/**
+	 * Filter callback that appends a custom status to the excluded defaults.
+	 *
+	 * @param array $statuses Default statuses.
+	 * @return array
+	 */
+	public function add_custom_excluded_status( $statuses ) {
+		$statuses[] = 'custom-excluded-status';
+		return $statuses;
+	}
+
+	/**
+	 * @testdox get_excluded_report_order_statuses falls back to the pre-filter value and triggers a doing_it_wrong notice when the woocommerce_analytics_excluded_order_statuses filter returns a non-array.
+	 */
+	public function test_excluded_order_statuses_filter_invalid_type_falls_back(): void {
+		$this->setExpectedIncorrectUsage( 'Automattic\WooCommerce\Admin\API\Reports\DataStore::get_excluded_report_order_statuses' );
+		add_filter( 'woocommerce_analytics_excluded_order_statuses', '__return_false' );
+
+		$statuses = $this->invoke_get_excluded_report_order_statuses();
+
+		remove_filter( 'woocommerce_analytics_excluded_order_statuses', '__return_false' );
+
+		$this->assertContains( 'pending', $statuses );
+		$this->assertContains( 'cancelled', $statuses );
+		$this->assertContains( 'failed', $statuses );
+		$this->assertContains( 'auto-draft', $statuses );
+		$this->assertContains( 'trash', $statuses );
+	}
+
+	/**
+	 * Call the protected static get_excluded_report_order_statuses method via reflection.
+	 *
+	 * @return array
+	 */
+	private function invoke_get_excluded_report_order_statuses(): array {
+		$method = new \ReflectionMethod( CustomersDataStore::class, 'get_excluded_report_order_statuses' );
+		$method->setAccessible( true );
+		return $method->invoke( null );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/OrderAwareControllerTraitTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/OrderAwareControllerTraitTest.php
new file mode 100644
index 00000000000..291e034530d
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/OrderAwareControllerTraitTest.php
@@ -0,0 +1,87 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\API\Reports;
+
+use Automattic\WooCommerce\Admin\API\Reports\Controller;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for OrderAwareControllerTrait, via the Reports Controller class that uses it.
+ */
+class OrderAwareControllerTraitTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Filter hooks added during a test, as [ tag, callback ] pairs, removed again in tearDown().
+	 *
+	 * @var array[]
+	 */
+	private $added_filters = array();
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		foreach ( $this->added_filters as $added_filter ) {
+			remove_filter( $added_filter[0], $added_filter[1] );
+		}
+		$this->added_filters = array();
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox get_order_statuses includes the built-in default actionable statuses when not filtered.
+	 */
+	public function test_default_actionable_order_statuses_without_filter(): void {
+		$statuses = Controller::get_order_statuses();
+
+		$this->assertContains( 'processing', $statuses );
+		$this->assertContains( 'on-hold', $statuses );
+	}
+
+	/**
+	 * @testdox get_order_statuses falls back to the built-in default actionable statuses when the default-statuses filter returns a non-array.
+	 */
+	public function test_default_actionable_order_statuses_filter_invalid_type_falls_back(): void {
+		add_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses', '__return_false' );
+
+		$statuses = Controller::get_order_statuses();
+
+		remove_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses', '__return_false' );
+
+		$this->assertContains( 'processing', $statuses, 'Should still contain the built-in default "processing" status.' );
+		$this->assertContains( 'on-hold', $statuses, 'Should still contain the built-in default "on-hold" status.' );
+	}
+
+	/**
+	 * @testdox get_order_statuses reflects a custom status added by the default-statuses filter when the option has never been saved.
+	 *
+	 * get_order_statuses() merges the registered statuses, so a built-in slug would pass even
+	 * if the filter were ignored. Only a custom slug can prove the filter reaches runtime.
+	 */
+	public function test_default_actionable_order_statuses_filter_reaches_runtime(): void {
+		delete_option( 'woocommerce_actionable_order_statuses' );
+		$this->add_filter_returning(
+			'woocommerce_analytics_settings_default_actionable_order_statuses',
+			array( 'processing', 'on-hold', 'custom-status' )
+		);
+
+		$statuses = Controller::get_order_statuses();
+
+		$this->assertContains( 'custom-status', $statuses, 'A status added by the filter should reach the runtime consumer.' );
+	}
+
+	/**
+	 * Register a filter that returns a fixed value, tracked for removal in tearDown().
+	 *
+	 * @param string $tag   Filter tag to hook.
+	 * @param array  $value Value the filter should return.
+	 */
+	private function add_filter_returning( string $tag, array $value ): void {
+		$callback = static function () use ( $value ) {
+			return $value;
+		};
+		add_filter( $tag, $callback );
+		$this->added_filters[] = array( $tag, $callback );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
index 2439bfbe846..05955ee0262 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
@@ -39,6 +39,13 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 	 */
 	private $add_long_status_callback = null;

+	/**
+	 * Filters added during a test, as [ tag, callback ] pairs, removed in tearDown().
+	 *
+	 * @var array[]
+	 */
+	private $added_filters = array();
+
 	/**
 	 * Previous HPOS state.
 	 *
@@ -96,6 +103,10 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 		}

 		delete_option( 'woocommerce_excluded_report_order_statuses' );
+		foreach ( $this->added_filters as $added_filter ) {
+			remove_filter( $added_filter[0], $added_filter[1] );
+		}
+		$this->added_filters = array();
 		if ( null !== $this->add_long_status_callback ) {
 			remove_filter( 'wc_order_statuses', $this->add_long_status_callback );
 			unset( $GLOBALS['wp_post_statuses']['wc-competition-completed'] );
@@ -493,6 +504,61 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 		$this->assertStringContainsString( 'cancelled', $output, 'Tooltip should mention "cancelled"' );
 	}

+	/**
+	 * @testdox Tooltip should fall back to default excluded statuses when the default-statuses filter returns a non-array.
+	 */
+	public function test_tooltip_falls_back_when_default_excluded_statuses_filter_returns_non_array(): void {
+		OrderHelper::toggle_cot_feature_and_usage( true );
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+
+		$customer_id = $this->factory->user->create();
+
+		$order = WC_Helper_Order::create_order( $customer_id );
+		$order->set_status( 'completed' );
+		$order->save();
+
+		ob_start();
+		$this->sut->output( $order );
+		$output = ob_get_clean();
+
+		remove_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+
+		$this->assertStringContainsString( 'pending payment', $output, 'Tooltip should still mention "pending payment"' );
+		$this->assertStringContainsString( 'failed', $output, 'Tooltip should still mention "failed"' );
+		$this->assertStringContainsString( 'cancelled', $output, 'Tooltip should still mention "cancelled"' );
+	}
+
+	/**
+	 * @testdox Tooltip should reflect a custom status added by the default-statuses filter when the option was never saved.
+	 */
+	public function test_tooltip_reflects_default_excluded_statuses_filter(): void {
+		delete_option( 'woocommerce_excluded_report_order_statuses' );
+		$add_label  = function ( $statuses ) {
+			$statuses['wc-custom-excluded'] = 'Custom Excluded';
+			return $statuses;
+		};
+		$add_custom = function ( $statuses ) {
+			$statuses[] = 'custom-excluded';
+			return $statuses;
+		};
+		add_filter( 'wc_order_statuses', $add_label );
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', $add_custom );
+		$this->added_filters[] = array( 'wc_order_statuses', $add_label );
+		$this->added_filters[] = array( 'woocommerce_analytics_settings_default_excluded_order_statuses', $add_custom );
+
+		$customer_id = $this->factory->user->create();
+
+		$order = WC_Helper_Order::create_order( $customer_id );
+		$order->set_status( 'completed' );
+		$order->save();
+
+		ob_start();
+		$this->sut->output( $order );
+		$output = ob_get_clean();
+
+		$this->assertStringContainsString( 'custom excluded', $output, 'Tooltip should mention the status added by the default-statuses filter.' );
+	}
+
 	/**
 	 * @testdox Tooltip should reflect custom excluded statuses option.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/SettingsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/SettingsTest.php
index 4bfe08eb600..497bb389cef 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/SettingsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/SettingsTest.php
@@ -12,13 +12,46 @@ use WC_Unit_Test_Case;
  */
 class SettingsTest extends WC_Unit_Test_Case {

+	/**
+	 * The System Under Test.
+	 *
+	 * @var Settings
+	 */
+	private $sut;
+
+	/**
+	 * Filter hooks added during a test, as [ tag, callback ] pairs, removed again in tearDown().
+	 *
+	 * @var array[]
+	 */
+	private $added_filters = array();
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->sut = Settings::get_instance();
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		foreach ( $this->added_filters as $added_filter ) {
+			remove_filter( $added_filter[0], $added_filter[1] );
+		}
+		$this->added_filters = array();
+		parent::tearDown();
+	}
+
 	/**
 	 * @testdox Should register the date type setting with a 'date_paid' default, matching the reports data stores.
 	 */
 	public function test_date_type_setting_has_date_paid_default(): void {
 		$sut = new Settings();

-		$date_type = $this->get_setting_by_id( $sut->add_settings( array() ), 'woocommerce_date_type' );
+		$date_type = $this->find_setting( $sut->add_settings( array() ), 'woocommerce_date_type' );

 		$this->assertNotNull( $date_type, 'The woocommerce_date_type setting should be registered in the wc_admin group' );
 		$this->assertSame( 'date_paid', $date_type['default'] ?? null, 'The date type default should match the date_paid fallback used by the reports data stores' );
@@ -30,7 +63,7 @@ class SettingsTest extends WC_Unit_Test_Case {
 	public function test_date_type_value_falls_back_to_date_paid_when_option_is_unset(): void {
 		delete_option( 'woocommerce_date_type' );

-		$date_type = $this->get_setting_by_id( $this->get_wc_admin_group_settings(), 'woocommerce_date_type' );
+		$date_type = $this->find_setting( $this->get_wc_admin_group_settings(), 'woocommerce_date_type' );

 		$this->assertNotNull( $date_type, 'The woocommerce_date_type setting should be registered in the wc_admin group' );
 		$this->assertSame( 'date_paid', $date_type['value'], 'An unset date type option should resolve to the effective default, date_paid' );
@@ -42,7 +75,7 @@ class SettingsTest extends WC_Unit_Test_Case {
 	public function test_date_type_value_reflects_saved_option(): void {
 		update_option( 'woocommerce_date_type', 'date_completed' );

-		$date_type = $this->get_setting_by_id( $this->get_wc_admin_group_settings(), 'woocommerce_date_type' );
+		$date_type = $this->find_setting( $this->get_wc_admin_group_settings(), 'woocommerce_date_type' );

 		$this->assertNotNull( $date_type, 'The woocommerce_date_type setting should be registered in the wc_admin group' );
 		$this->assertSame( 'date_completed', $date_type['value'], 'A saved date type option should be reflected as the setting value' );
@@ -61,6 +94,201 @@ class SettingsTest extends WC_Unit_Test_Case {
 		$this->assertSame( ',', $currency_settings['decimalSeparator'], 'A decimal separator stored as an HTML entity should be decoded to the real character' );
 	}

+	/**
+	 * @testdox Excluded report order statuses default to pending, cancelled, and failed when not filtered.
+	 */
+	public function test_default_excluded_order_statuses_without_filter(): void {
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertSame( array( 'pending', 'cancelled', 'failed' ), $setting['default'] );
+	}
+
+	/**
+	 * @testdox Actionable order statuses default to processing and on-hold when not filtered.
+	 */
+	public function test_default_actionable_order_statuses_without_filter(): void {
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_actionable_order_statuses' );
+
+		$this->assertSame( array( 'processing', 'on-hold' ), $setting['default'] );
+	}
+
+	/**
+	 * @testdox woocommerce_analytics_settings_default_excluded_order_statuses lets a plugin activate a status by default.
+	 */
+	public function test_default_excluded_order_statuses_can_be_filtered(): void {
+		$this->add_default_status_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses' );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertContains( 'refunded', $setting['default'] );
+	}
+
+	/**
+	 * @testdox woocommerce_analytics_settings_default_actionable_order_statuses lets a plugin activate a status by default.
+	 */
+	public function test_default_actionable_order_statuses_can_be_filtered(): void {
+		$this->add_default_status_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses' );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_actionable_order_statuses' );
+
+		$this->assertContains( 'refunded', $setting['default'] );
+	}
+
+	/**
+	 * @testdox A default order statuses filter returning an unknown status has that status dropped.
+	 */
+	public function test_default_order_statuses_filter_drops_unknown_status(): void {
+		$this->add_default_status_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses' );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertNotContains( 'not-a-real-status', $setting['default'] );
+	}
+
+	/**
+	 * @testdox A default order statuses filter returning only unknown slugs falls back to the built-in default.
+	 */
+	public function test_default_order_statuses_filter_all_unknown_falls_back(): void {
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', array( $this, 'return_only_unknown_statuses' ) );
+		$this->added_filters[] = array( 'woocommerce_analytics_settings_default_excluded_order_statuses', array( $this, 'return_only_unknown_statuses' ) );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertSame( array( 'pending', 'cancelled', 'failed' ), $setting['default'] );
+	}
+
+	/**
+	 * Filter callback that returns only unregistered status slugs.
+	 *
+	 * @param array $statuses Default statuses.
+	 * @return array
+	 */
+	public function return_only_unknown_statuses( $statuses ) {
+		unset( $statuses );
+		return array( 'not-a-real-status', 'also-fake' );
+	}
+
+	/**
+	 * @testdox A default order statuses filter returning a non-array falls back to the built-in default.
+	 */
+	public function test_default_order_statuses_filter_invalid_type_falls_back(): void {
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+		$this->added_filters[] = array( 'woocommerce_analytics_settings_default_excluded_order_statuses', '__return_false' );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertSame( array( 'pending', 'cancelled', 'failed' ), $setting['default'] );
+	}
+
+	/**
+	 * Hook the shared filter callback and track it for removal in tearDown().
+	 *
+	 * @param string $filter Filter tag to hook.
+	 */
+	private function add_default_status_filter( string $filter ): void {
+		$callback = array( $this, 'add_status_to_defaults' );
+		add_filter( $filter, $callback );
+		$this->added_filters[] = array( $filter, $callback );
+	}
+
+	/**
+	 * Filter callback that appends a known status and an unknown status to a default statuses array.
+	 *
+	 * @param array $statuses Default statuses.
+	 * @return array
+	 */
+	public function add_status_to_defaults( $statuses ) {
+		$statuses[] = 'refunded';
+		$statuses[] = 'not-a-real-status';
+		return $statuses;
+	}
+
+	/**
+	 * @testdox get_valid_order_statuses_or_default() keeps arrays as-is (dropping non-strings) and falls back only for unusable values.
+	 * @dataProvider provider_valid_order_statuses_or_default
+	 * @param mixed $value    Value to validate.
+	 * @param array $expected Expected result.
+	 */
+	public function test_get_valid_order_statuses_or_default( $value, array $expected ): void {
+		$result = Settings::get_valid_order_statuses_or_default( $value, array( 'processing', 'on-hold' ) );
+
+		$this->assertSame( $expected, $result );
+	}
+
+	/**
+	 * Data provider for test_get_valid_order_statuses_or_default.
+	 *
+	 * @return array<string, array<mixed>>
+	 */
+	public function provider_valid_order_statuses_or_default(): array {
+		return array(
+			'non-empty array passes through'              => array( array( 'refunded' ), array( 'refunded' ) ),
+			'non-array value falls back'                  => array( false, array( 'processing', 'on-hold' ) ),
+			'empty array passes through'                  => array( array(), array() ),
+			'nested array elements are dropped'           => array( array( 'refunded', array( 'nested' ) ), array( 'refunded' ) ),
+			'non-string elements are dropped'             => array( array( 'refunded', 42, null ), array( 'refunded' ) ),
+			'blank string elements are dropped'           => array( array( 'refunded', '' ), array( 'refunded' ) ),
+			'slugs are trimmed'                           => array( array( ' refunded ' ), array( 'refunded' ) ),
+			'all-blank array falls back as unusable'      => array( array( ' ', '' ), array( 'processing', 'on-hold' ) ),
+			'all-non-string array falls back as unusable' => array( array( 42, null ), array( 'processing', 'on-hold' ) ),
+		);
+	}
+
+	/**
+	 * @testdox A default order statuses filter is the source of truth for runtime consumers, not just the settings UI.
+	 */
+	public function test_default_excluded_order_statuses_accessor_applies_filter(): void {
+		$this->add_default_status_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses' );
+
+		$statuses = Settings::get_default_excluded_order_statuses();
+
+		$this->assertContains( 'refunded', $statuses, 'The filtered status should reach runtime consumers.' );
+	}
+
+	/**
+	 * @testdox The actionable default accessor applies its filter.
+	 */
+	public function test_default_actionable_order_statuses_accessor_applies_filter(): void {
+		$this->add_default_status_filter( 'woocommerce_analytics_settings_default_actionable_order_statuses' );
+
+		$statuses = Settings::get_default_actionable_order_statuses();
+
+		$this->assertContains( 'refunded', $statuses, 'The filtered status should reach runtime consumers.' );
+	}
+
+	/**
+	 * @testdox A saved empty selection is respected, so unchecking every status does not restore the defaults.
+	 */
+	public function test_saved_empty_selection_is_respected(): void {
+		$result = Settings::get_valid_order_statuses_or_default( array(), array( 'pending', 'cancelled', 'failed' ) );
+
+		$this->assertSame( array(), $result, 'An explicitly saved empty selection must not be replaced by the built-in default.' );
+	}
+
+	/**
+	 * @testdox A filter returning an empty array keeps the settings UI empty, agreeing with the runtime accessors.
+	 */
+	public function test_filter_returning_empty_array_keeps_ui_empty(): void {
+		$empty_filter = function () {
+			return array();
+		};
+		add_filter( 'woocommerce_analytics_settings_default_excluded_order_statuses', $empty_filter );
+		$this->added_filters[] = array( 'woocommerce_analytics_settings_default_excluded_order_statuses', $empty_filter );
+
+		$settings = $this->sut->add_settings( array() );
+		$setting  = $this->find_setting( $settings, 'woocommerce_excluded_report_order_statuses' );
+
+		$this->assertSame( array(), $setting['default'], 'An empty filtered default should stay empty in the UI.' );
+		$this->assertSame( array(), Settings::get_default_excluded_order_statuses(), 'The UI and runtime consumers should agree.' );
+	}
+
 	/**
 	 * Get the resolved wc_admin group settings via the REST settings controller.
 	 *
@@ -81,7 +309,7 @@ class SettingsTest extends WC_Unit_Test_Case {
 	 * @param string $id       Setting id to look for.
 	 * @return array|null The first matching setting, or null if none matches.
 	 */
-	private function get_setting_by_id( array $settings, string $id ): ?array {
+	private function find_setting( array $settings, string $id ): ?array {
 		foreach ( $settings as $setting ) {
 			if ( ( $setting['id'] ?? '' ) === $id ) {
 				return $setting;