Commit 91c4edc3011 for woocommerce
commit 91c4edc3011a4d9b90441d3af940912c8956a613
Author: Ismael Martín Alabarce <info@ismaeld.com>
Date: Tue Aug 11 09:02:40 2026 +0200
Fix stale task routes in WooCommerce Admin (#67362)
* Fix stale task routes in WooCommerce Admin
* Add stale task route changelog entry
---------
Co-authored-by: Sam Najian <dev@najian.info>
diff --git a/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect b/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect
new file mode 100644
index 00000000000..83efe7c387b
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Redirect missing WooCommerce Admin task routes to Home instead of leaving a blank screen.
diff --git a/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx b/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
index 21efb55468e..5f2bda48bc3 100644
--- a/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
+++ b/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
@@ -4,9 +4,10 @@
import { __ } from '@wordpress/i18n';
import { MenuGroup, MenuItem } from '@wordpress/components';
import { check } from '@wordpress/icons';
-import { Fragment } from '@wordpress/element';
+import { Fragment, useEffect } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { onboardingStore, TaskListType, TaskType } from '@woocommerce/data';
+import { getHistory, getNewPath } from '@woocommerce/navigation';
import { recordEvent } from '@woocommerce/tracks';
/**
@@ -79,6 +80,12 @@ export const TaskLists = ( { query }: TaskListsProps ) => {
const currentTask = getCurrentTask();
+ useEffect( () => {
+ if ( task && ! currentTask && ! isResolving ) {
+ getHistory().replace( getNewPath( {}, '/', {} ) );
+ }
+ }, [ currentTask, isResolving, task ] );
+
if ( task && ! currentTask ) {
return null;
}
diff --git a/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx b/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
index f7247e69815..d2dd57d3175 100644
--- a/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
+++ b/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
@@ -4,6 +4,7 @@
import { render, act, cleanup, waitFor } from '@testing-library/react';
import { useDispatch, useSelect } from '@wordpress/data';
import { recordEvent } from '@woocommerce/tracks';
+import { getHistory, getNewPath } from '@woocommerce/navigation';
import userEvent from '@testing-library/user-event';
/**
@@ -28,6 +29,11 @@ jest.mock( '@wordpress/data', () => {
jest.mock( '@woocommerce/explat' );
jest.mock( '@woocommerce/tracks' );
+jest.mock( '@woocommerce/navigation', () => ( {
+ ...jest.requireActual( '@woocommerce/navigation' ),
+ getHistory: jest.fn(),
+ getNewPath: jest.fn().mockReturnValue( 'home-path' ),
+} ) );
jest.mock( '../components/task-list', () => ( {
TaskList: ( { id }: TaskListProps ) => <div>task-list:{ id }</div>,
@@ -56,8 +62,12 @@ jest.mock( '~/activity-panel/display-options', () => ( {
describe( 'Task', () => {
const hideTaskList = jest.fn();
const updateOptions = jest.fn();
+ const historyReplace = jest.fn();
beforeEach( () => {
jest.clearAllMocks();
+ ( getHistory as jest.Mock ).mockReturnValue( {
+ replace: historyReplace,
+ } );
( useDispatch as jest.Mock ).mockImplementation( () => ( {
hideTaskList,
updateOptions,
@@ -102,9 +112,10 @@ describe( 'Task', () => {
</div>
);
expect( queryByText( 'task:main-task-1' ) ).toBeInTheDocument();
+ expect( historyReplace ).not.toHaveBeenCalled();
} );
- it( 'should not render anything if query has task, but task does not exist', () => {
+ it( 'should redirect home if query has task, but task does not exist', async () => {
const { queryByText } = render(
<div>
<TaskLists query={ { task: 'main-task-random' } } />
@@ -114,6 +125,23 @@ describe( 'Task', () => {
queryByText( 'task:main-task-random' )
).not.toBeInTheDocument();
expect( queryByText( 'task-list:main' ) ).not.toBeInTheDocument();
+ await waitFor( () => {
+ expect( historyReplace ).toHaveBeenCalledWith( 'home-path' );
+ } );
+ expect( getNewPath ).toHaveBeenCalledWith( {}, '/', {} );
+ } );
+
+ it( 'should not redirect if task lists are still resolving', () => {
+ ( useSelect as jest.Mock ).mockImplementation( () => ( {
+ isResolving: true,
+ taskLists: [],
+ } ) );
+ render(
+ <div>
+ <TaskLists query={ { task: 'main-task-random' } } />
+ </div>
+ );
+ expect( historyReplace ).not.toHaveBeenCalled();
} );
it( 'should render the placeholder if isResolving is true', () => {