Commit d3620aa3235 for woocommerce
commit d3620aa3235fd5d9671addf002c573621f008d08
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Mon Sep 21 12:53:16 2026 +0300
Fix Analytics commands missing from the command palette when stock management is off (#68753)
* test(admin): Move command palette registration below E2E
The command palette spec ran six browser titles. Four of them
opened the palette, typed a command name, clicked the option and
checked the heading of the page it landed on. What each command
registers — its name, its label, its icon, and the URL its callback
navigates to — is decided in the admin bundle's own JavaScript.
Add Jest tests for that: one file covers the four static WooCommerce
commands and the product loader, the other the analytics commands
registered per report. Together they assert each command's name,
label, icon, tracking event and target URL, and the loader's search
debounce, its query, and the records it maps.
Keep two browser titles: the Add new product command, which proves a
registered command still reaches its destination through the real
palette, and the product search command, which proves the loader's
asynchronous path end to end.
Consolidates the mega-branch slice:
- Slice 073: test(admin): Move command palette below E2E
Refs TESTOPS-288
Refs #68046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(admin): Guard the Analytics report handoff to the command palette
The Jest suite for the Analytics command palette assigns
window.wcCommandPaletteAnalytics itself, so once the E2E title was
removed nothing proved that enqueue_command_palette_assets() enqueues
the analytics bundle and localizes it from get_report_pages(). The
review asked for a PHPUnit case to close that gap.
Feed get_report_pages() two reports through its own
woocommerce_analytics_report_menu_items filter, run the enqueue, and
decode the localized global. Assert that the bundle is enqueued, that
reports reach JS as an array, that titles lose their HTML and that
paths pass through. A fixture keeps the test stable when a report is
added to Analytics.
The CI PHPUnit jobs do not build the admin bundles, so enqueue_script()
throws there for want of an asset registry. When a bundle is not built,
the test writes a stub .asset.php and tearDown removes it. Six source
mutations each fail their own assertion, and the class passes with
assets/client/admin hidden.
Refs TESTOPS-288
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(admin): Keep Analytics command palette reports a JSON list
Since #41773, enqueue_command_palette_assets() drops invalid report
entries with array_filter(), which keeps the original keys. When
stock management is off, Analytics::get_report_pages() returns null
for the Stock report in the middle of the list, so the filtered
array has a gap and wp_localize_script() encodes reports as a JSON
object. The analytics bundle registers commands only when reports is
an array, so no WooCommerce Analytics command reaches the palette.
Reindex the filtered reports with array_values() so they always reach
JavaScript as a list. That also covers an extension adding a malformed
entry through the woocommerce_analytics_report_menu_items filter.
Add a PHPUnit case for a store with stock management off. It fails
at the array assertion without the reindex, and a dev store showed
zero analytics commands in the palette before the fix and 11 after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore(changelog): Add changelog entry for the Analytics command palette fix
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(admin): Skip Analytics command registration for an empty global
The Analytics command palette bundle checked window.hasOwnProperty()
and then called hasOwnProperty() on the global itself. When a script
sets window.wcCommandPaletteAnalytics to null or undefined, that second
call throws a TypeError inside the domReady callback instead of
skipping registration. WooCommerce always localizes an object, so this
guards input no known code produces, as CodeRabbit asked on #68620.
Read reports through optional chaining and register only when the
result is an array. One check now covers a missing, null or undefined
global, missing reports and non-array reports. A "null global" row in
the Jest table failed with the TypeError before the change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(admin): Build the command palette report list in one pass
enqueue_command_palette_assets() mapped every Analytics report to
either a formatted entry or null, dropped the nulls with
array_filter(), then reindexed with array_values(). The reindex
existed only to undo the gap array_filter() leaves behind, so the
JSON-array requirement read as a patch bolted onto the pipeline
rather than a property of it.
Build the list with a foreach that skips invalid entries and appends
the valid ones. Appending always produces sequential keys, so
wp_localize_script() encodes a JSON array by construction and no
reindex step is needed. Suggested in review on #68753.
Behavior is unchanged. A report is still dropped unless it is an
array carrying a non-empty string title and path, titles still lose
their HTML, and paths still pass through untouched. Replacing the
append with a key taken from the source index reintroduces the gap
and fails the stock-off PHPUnit case with a JSON object, so the
existing tests cover the new shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-command-palette-analytics-stock-off b/plugins/woocommerce/changelog/fix-command-palette-analytics-stock-off
new file mode 100644
index 00000000000..8fd67a76613
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-command-palette-analytics-stock-off
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show the WooCommerce Analytics commands in the command palette when stock management is turned off.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/__tests__/index.test.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/__tests__/index.test.js
index 27334f56ca8..c7f3ccb48af 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/__tests__/index.test.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/__tests__/index.test.js
@@ -49,6 +49,7 @@ describe( 'Analytics Command Palette', () => {
it.each( [
{ caseName: 'missing global', analytics: undefined },
+ { caseName: 'null global', analytics: null },
{ caseName: 'missing reports', analytics: {} },
{ caseName: 'non-array reports', analytics: { reports: {} } },
{ caseName: 'empty reports', analytics: { reports: [] } },
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
index 98c1ffc0725..cdf5890b963 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
@@ -30,13 +30,9 @@ const registerWooCommerceAnalyticsCommand = ( { label, path } ) => {
};
domReady( () => {
- if (
- window.hasOwnProperty( 'wcCommandPaletteAnalytics' ) &&
- window.wcCommandPaletteAnalytics.hasOwnProperty( 'reports' ) &&
- Array.isArray( window.wcCommandPaletteAnalytics.reports )
- ) {
- const analyticsReports = window.wcCommandPaletteAnalytics.reports;
+ const analyticsReports = window.wcCommandPaletteAnalytics?.reports;
+ if ( Array.isArray( analyticsReports ) ) {
analyticsReports.forEach( ( analyticsReport ) => {
registerWooCommerceAnalyticsCommand( {
label: analyticsReport.title,
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
index f1c74f6d8ff..aea5fa1b8a2 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
@@ -921,24 +921,24 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
if ( ! $admin_features_disabled ) {
$analytics_reports = Analytics::get_report_pages();
if ( is_array( $analytics_reports ) && count( $analytics_reports ) > 0 ) {
- $formatted_analytics_reports = array_map( function( $report ) {
+ // Append so the reports stay a sequential list; wp_localize_script() encodes a gapped array as a JSON object the command palette ignores.
+ $formatted_analytics_reports = array();
+ foreach ( $analytics_reports as $report ) {
if ( ! is_array( $report ) ) {
- return null;
+ continue;
}
- $title = array_key_exists( 'title', $report ) ? $report['title'] : '';
- $path = array_key_exists( 'path', $report ) ? $report['path'] : '';
- if (
- is_string( $title ) && $title !== "" &&
- is_string( $path ) && $path !== ""
- ) {
- return array(
- 'title' => wp_strip_all_tags( $title ),
- 'path' => $path,
- );
+
+ $title = $report['title'] ?? '';
+ $path = $report['path'] ?? '';
+ if ( ! is_string( $title ) || '' === $title || ! is_string( $path ) || '' === $path ) {
+ continue;
}
- return null;
- }, $analytics_reports );
- $formatted_analytics_reports = array_filter( $formatted_analytics_reports, 'is_array' );
+
+ $formatted_analytics_reports[] = array(
+ 'title' => wp_strip_all_tags( $title ),
+ 'path' => $path,
+ );
+ }
$this->enqueue_script( 'wp-admin-scripts', 'command-palette-analytics' );
wp_localize_script(
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-assets-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-assets-test.php
index 912c6e13ee1..7ea70a2b62d 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-assets-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-assets-test.php
@@ -180,6 +180,21 @@ class WC_Admin_Assets_Test extends WC_Unit_Test_Case {
$this->assertSame( array( '/analytics/revenue', '/customers' ), array_column( $localized->reports, 'path' ), 'Each report path should be passed unchanged' );
}
+ /**
+ * @testdox Should localize the Analytics reports as an array when stock management is off and the Stock report is left out.
+ */
+ public function test_enqueue_command_palette_assets_localizes_reports_as_array_without_stock_report(): void {
+ $this->ensure_wp_admin_script_asset_registry( 'command-palette' );
+ $this->ensure_wp_admin_script_asset_registry( 'command-palette-analytics' );
+ update_option( 'woocommerce_manage_stock', 'no' );
+
+ $this->sut->enqueue_command_palette_assets();
+
+ $localized = $this->get_localized_object( 'wc-admin-command-palette-analytics', 'wcCommandPaletteAnalytics' );
+ $this->assertIsArray( $localized->reports ?? null, 'Reports should stay an array once the missing Stock report is dropped, or the command palette registers none of them' );
+ $this->assertNotContains( '/analytics/stock', array_column( $localized->reports, 'path' ), 'The Stock report should not be offered when stock management is off' );
+ }
+
/**
* Writes a stub asset registry for a wp-admin-scripts bundle that is not built, as in the CI PHPUnit jobs.
* tearDown() removes every directory and file this creates.