Commit ad0e0c88375 for woocommerce
commit ad0e0c883750ee9b01b71ee3679d4c189e2b0f37
Author: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>
Date: Tue Aug 25 22:02:12 2026 +0300
Restore editor incompatibility notice regression coverage (#67910)
* test(blocks): Restore incompatible notice regression coverage
* docs: Clarify multisite notice dismissal behavior
* test(blocks): Add storefront site-scoping coverage
The storefront incompatibility notice shares the site-scoped storage helper with the editor notice.
Its integration suite derived all keys through that helper, so losing site scoping left the suite green.
Add a cross-site dismissal test with mutable site settings so the storefront suite catches that regression.
Refs #67108
diff --git a/plugins/woocommerce/changelog/42469-keep-incompatible-notice-dismissed b/plugins/woocommerce/changelog/42469-keep-incompatible-notice-dismissed
index 37f7b9b68b7..eb9fdc0196a 100644
--- a/plugins/woocommerce/changelog/42469-keep-incompatible-notice-dismissed
+++ b/plugins/woocommerce/changelog/42469-keep-incompatible-notice-dismissed
@@ -1,4 +1,4 @@
Significance: patch
Type: fix
-Keep the Cart and Checkout incompatible extensions notices (editor sidebar and storefront banner) dismissed when an incompatible extension or payment gateway is disabled, and warn again only when something the merchant has not yet acknowledged becomes incompatible. Dismissals are now stored per site; existing ones carry over on single-site installs.
+Keep the Cart and Checkout incompatible extensions notices (editor sidebar and storefront banner) dismissed when an incompatible extension or payment gateway is disabled, and warn again only when something the merchant has not yet acknowledged becomes incompatible. Dismissals are now stored per site. Existing dismissals carry over on single-site installs but not on multisite installs, where notices may need to be dismissed again after upgrading.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/test/incompatible-extensions-notice.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/test/incompatible-extensions-notice.tsx
index a57a3db0b70..b117c830ffd 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/test/incompatible-extensions-notice.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/test/incompatible-extensions-notice.tsx
@@ -14,16 +14,25 @@ import {
} from '@woocommerce/editor-components/incompatible-extension-notice/storage';
import { IncompatibleExtensionsFrontendNotice } from '../incompatible-extensions-notice';
+const SITE_A = 1;
+const SITE_B = 2;
+
let mockIsAdmin = true;
+let mockSiteId = SITE_A;
+let mockIsMultisite = false;
jest.mock( '@woocommerce/settings', () => ( {
getSetting: jest.fn(),
- // A getter, not a value: the viewer changes between renders.
+ // Getters, not values: the viewer and site change between renders.
get CURRENT_USER_IS_ADMIN() {
return mockIsAdmin;
},
- CURRENT_SITE_ID: 1,
- IS_MULTISITE: false,
+ get CURRENT_SITE_ID() {
+ return mockSiteId;
+ },
+ get IS_MULTISITE() {
+ return mockIsMultisite;
+ },
} ) );
// Use the real localStorage-backed hook (via its source module) without pulling
@@ -86,6 +95,8 @@ describe( 'IncompatibleExtensionsFrontendNotice', () => {
window.localStorage.clear();
setIncompatibleExtensions( [] );
mockIsAdmin = true;
+ mockSiteId = SITE_A;
+ mockIsMultisite = false;
} );
describe( 'rendering', () => {
@@ -307,6 +318,32 @@ describe( 'IncompatibleExtensionsFrontendNotice', () => {
} );
} );
+ // Sites on a subdirectory multisite share one localStorage origin, so the
+ // storefront banner must use the current site's key rather than another
+ // site's acknowledgement.
+ describe( 'site scoping', () => {
+ it( 'does not carry a dismissal to another site on the same origin', () => {
+ mockIsMultisite = true;
+ setIncompatibleExtensions( [
+ { id: 'test-plugin', title: 'Test Plugin' },
+ ] );
+ const { unmount } = render(
+ <IncompatibleExtensionsFrontendNotice block="woocommerce/checkout" />
+ );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Dismiss' } )
+ );
+ unmount();
+
+ mockSiteId = SITE_B;
+
+ render(
+ <IncompatibleExtensionsFrontendNotice block="woocommerce/checkout" />
+ );
+ expect( screen.getByTestId( 'notice-banner' ) ).toBeInTheDocument();
+ } );
+ } );
+
// Before this split the storefront banner shared the editor's key, and
// neither key named a site, so a merchant's dismissal lives under the
// unscoped key on every site that ran 10.7.0 or later. Without a migration
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/incompatible-extension-notice/test/use-combined-incompatibility-notice.ts b/plugins/woocommerce/client/blocks/assets/js/editor-components/incompatible-extension-notice/test/use-combined-incompatibility-notice.ts
index 311c601d584..6836f77005f 100644
--- a/plugins/woocommerce/client/blocks/assets/js/editor-components/incompatible-extension-notice/test/use-combined-incompatibility-notice.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/incompatible-extension-notice/test/use-combined-incompatibility-notice.ts
@@ -28,10 +28,20 @@ let mockIncompatibleExtensions:
| Array< { id: string; title: string } >
| undefined = [];
+const SITE_A = 1;
+const SITE_B = 2;
+
+let mockSiteId = SITE_A;
+let mockIsMultisite = false;
+
jest.mock( '@woocommerce/settings', () => ( {
...jest.requireActual( '@woocommerce/settings' ),
- CURRENT_SITE_ID: 1,
- IS_MULTISITE: false,
+ get CURRENT_SITE_ID() {
+ return mockSiteId;
+ },
+ get IS_MULTISITE() {
+ return mockIsMultisite;
+ },
getSetting: jest.fn().mockImplementation( ( name: string, ...rest ) => {
if ( name === 'incompatibleExtensions' ) {
return mockIncompatibleExtensions;
@@ -93,6 +103,8 @@ describe( 'useCombinedIncompatibilityNotice', () => {
mockIncompatiblePaymentMethods = {};
mockIncompatibleExtensions = [];
mockPaymentMethodsLoaded = true;
+ mockSiteId = SITE_A;
+ mockIsMultisite = false;
} );
it( 'shows the notice when there is an incompatible gateway', () => {
@@ -395,11 +407,26 @@ describe( 'useCombinedIncompatibilityNotice', () => {
} );
} );
+ // Sites on a subdirectory multisite share one localStorage origin, so the
+ // editor hook must use the current site's key rather than another site's
+ // acknowledgement.
+ describe( 'site scoping', () => {
+ it( 'does not carry a dismissal to another site on the same origin', () => {
+ mockIsMultisite = true;
+ mockIncompatiblePaymentMethods = { gw_a: 'Gateway A' };
+ mountAndDismiss( CHECKOUT );
+ expect( mountVisibility( CHECKOUT ) ).toBe( false );
+
+ mockSiteId = SITE_B;
+
+ expect( mountVisibility( CHECKOUT ) ).toBe( true );
+ } );
+ } );
+
// The dismissals merchants already made live under the unscoped key, so
// without a migration every one of them would see the notice one more time.
- // Site scoping, the multisite refusal, and the corrupt-value rules are the
- // storage contract's and are pinned in test/storage.ts; these two pin this
- // surface's wiring to it.
+ // The storage contract's full matrix is pinned in test/storage.ts; these
+ // cases pin the editor hook's wiring to it.
describe( 'migration from before site scoping', () => {
const seedUnscoped = ( value: unknown ) =>
window.localStorage.setItem(
@@ -429,5 +456,16 @@ describe( 'useCombinedIncompatibilityNotice', () => {
before
);
} );
+
+ // Only an absent scoped key may open migration. Otherwise a corrupt value
+ // would revive a dismissal the merchant has since replaced.
+ it( 'does not migrate over a scoped value it cannot parse', () => {
+ window.localStorage.setItem( storageKey(), '{not valid json' );
+ seedUnscoped( [ { [ CHECKOUT ]: [ 'gw_a' ] } ] );
+ mockIncompatiblePaymentMethods = { gw_a: 'Gateway A' };
+
+ expect( mountVisibility( CHECKOUT ) ).toBe( true );
+ expect( console ).toHaveErrored();
+ } );
} );
} );