From 77264f7632398342d930cf4dba4d1ba16f3b0a53 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 16:03:36 +0100 Subject: [PATCH 1/9] Connectors: Rename AI install slug constant --- routes/connectors-home/ai-plugin-callout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/connectors-home/ai-plugin-callout.tsx b/routes/connectors-home/ai-plugin-callout.tsx index 44757685b9f9f5..4fef98de459de8 100644 --- a/routes/connectors-home/ai-plugin-callout.tsx +++ b/routes/connectors-home/ai-plugin-callout.tsx @@ -22,7 +22,7 @@ import { WpLogoDecoration } from './wp-logo-decoration'; import type { PluginStatus } from './use-connector-plugin'; -const AI_PLUGIN_SLUG = 'ai'; +const AI_PLUGIN_INSTALL_SLUG = 'ai'; const AI_PLUGIN_PAGE_SLUG = 'ai-wp-admin'; const AI_PLUGIN_ID = 'ai/ai'; const AI_PLUGIN_URL = 'https://wordpress.org/plugins/ai/'; @@ -133,7 +133,7 @@ export function AiPluginCallout() { await saveEntityRecord( 'root', 'plugin', - { slug: AI_PLUGIN_SLUG, status: 'active' }, + { slug: AI_PLUGIN_INSTALL_SLUG, status: 'active' }, { throwOnError: true } ); setJustActivated( true ); From 231535a5d0b3622011d9595e3c408a8ca491fae9 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 16:43:57 +0100 Subject: [PATCH 2/9] Connectors: Test AI settings CTA target --- .../ai-plugin-callout.test.tsx | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 routes/connectors-home/ai-plugin-callout.test.tsx diff --git a/routes/connectors-home/ai-plugin-callout.test.tsx b/routes/connectors-home/ai-plugin-callout.test.tsx new file mode 100644 index 00000000000000..d5a2d17a97114d --- /dev/null +++ b/routes/connectors-home/ai-plugin-callout.test.tsx @@ -0,0 +1,157 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * WordPress dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { AiPluginCallout } from './ai-plugin-callout'; + +jest.mock( '@wordpress/a11y', () => ( { + speak: jest.fn(), +} ) ); + +jest.mock( '@wordpress/components', () => { + const { createElement, forwardRef } = jest.requireActual( + '@wordpress/element' + ); + + return { + Button: forwardRef( + ( + { + href, + children, + onClick, + disabled, + }: { + href?: string; + children: unknown; + onClick?: () => void; + disabled?: boolean; + }, + ref + ) => + href + ? createElement( 'a', { ref, href }, children ) + : createElement( + 'button', + { ref, type: 'button', onClick, disabled }, + children + ) + ), + ExternalLink: ( { + href, + children, + }: { + href: string; + children: unknown; + } ) => createElement( 'a', { href }, children ), + }; +} ); + +jest.mock( '@wordpress/core-data', () => ( { + store: 'core', +} ) ); + +jest.mock( '@wordpress/data', () => ( { + useDispatch: jest.fn(), + useSelect: jest.fn(), +} ) ); + +jest.mock( './default-connectors', () => ( { + getConnectorData: jest.fn( () => ( { + openai: { + type: 'ai_provider', + authentication: { + method: 'api_key', + settingName: 'connectors_ai_openai_api_key', + isConnected: false, + }, + }, + } ) ), +} ) ); + +jest.mock( './wp-logo-decoration', () => ( { + WpLogoDecoration: () => null, +} ) ); + +type StoreState = { + canCreate: boolean; + hasFinishedResolution: boolean; + plugin?: { plugin: string; status: string }; + siteSettings?: Record< string, string >; +}; + +describe( 'AiPluginCallout', () => { + let storeState: StoreState; + let selectorStore: { + canUser: jest.Mock; + getEntityRecord: jest.Mock; + hasFinishedResolution: jest.Mock; + }; + + beforeEach( () => { + storeState = { + canCreate: true, + hasFinishedResolution: true, + plugin: { + plugin: 'ai/ai', + status: 'active', + }, + siteSettings: {}, + }; + + selectorStore = { + canUser: jest.fn( () => storeState.canCreate ), + getEntityRecord: jest.fn( + ( kind: string, name: string, id?: string ) => { + if ( kind === 'root' && name === 'site' ) { + return storeState.siteSettings; + } + + if ( + kind === 'root' && + name === 'plugin' && + id === 'ai/ai' + ) { + return storeState.plugin; + } + + return undefined; + } + ), + hasFinishedResolution: jest.fn( + () => storeState.hasFinishedResolution + ), + }; + + ( useSelect as jest.Mock ).mockImplementation( + ( + mapSelect: ( + select: () => typeof selectorStore + ) => unknown + ) => mapSelect( () => selectorStore ) + ); + + ( useDispatch as jest.Mock ).mockReturnValue( { + saveEntityRecord: jest.fn(), + } ); + } ); + + it( 'links to the renamed AI settings page when the plugin is active without a connected provider', () => { + render( ); + + expect( + screen.getByRole( 'link', { + name: 'Control features in the AI plugin', + } ) + ).toHaveAttribute( 'href', 'options-general.php?page=ai-wp-admin' ); + } ); +} ); From 1b0f150a8d6446f83a63176c0ba569812bc3fef5 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 16:58:41 +0100 Subject: [PATCH 3/9] Connectors: Test AI install slug behavior --- .../ai-plugin-callout.test.tsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/routes/connectors-home/ai-plugin-callout.test.tsx b/routes/connectors-home/ai-plugin-callout.test.tsx index d5a2d17a97114d..8acdae27ef2116 100644 --- a/routes/connectors-home/ai-plugin-callout.test.tsx +++ b/routes/connectors-home/ai-plugin-callout.test.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * WordPress dependencies @@ -89,6 +90,8 @@ type StoreState = { siteSettings?: Record< string, string >; }; +const mockSaveEntityRecord = jest.fn(); + describe( 'AiPluginCallout', () => { let storeState: StoreState; let selectorStore: { @@ -140,8 +143,10 @@ describe( 'AiPluginCallout', () => { ) => mapSelect( () => selectorStore ) ); + mockSaveEntityRecord.mockReset(); + mockSaveEntityRecord.mockResolvedValue( undefined ); ( useDispatch as jest.Mock ).mockReturnValue( { - saveEntityRecord: jest.fn(), + saveEntityRecord: mockSaveEntityRecord, } ); } ); @@ -154,4 +159,25 @@ describe( 'AiPluginCallout', () => { } ) ).toHaveAttribute( 'href', 'options-general.php?page=ai-wp-admin' ); } ); + + it( 'installs the AI plugin using the plugin slug', async () => { + const user = userEvent.setup(); + + storeState.plugin = undefined; + + render( ); + + await user.click( + screen.getByRole( 'button', { name: 'Install the AI plugin' } ) + ); + + await waitFor( () => { + expect( mockSaveEntityRecord ).toHaveBeenCalledWith( + 'root', + 'plugin', + { slug: 'ai', status: 'active' }, + { throwOnError: true } + ); + } ); + } ); } ); From 525b54f8224f97b99fc86c99a136645ab4abc010 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 17:25:58 +0100 Subject: [PATCH 4/9] Lint: Treat route spec files as development-only --- eslint.config.mjs | 1 + routes/connectors-home/ai-plugin-callout.test.tsx | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index af1f1ef566231f..9869c854d035a7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -65,6 +65,7 @@ function dedupePlugins( configs ) { const developmentFiles = [ '**/benchmark/**/*.js', '**/@(__mocks__|__tests__|test)/**/*.[tj]s?(x)', + '**/*.@(test|spec).[tj]s?(x)', '**/@(storybook|stories)/**/*.[tj]s?(x)', 'packages/babel-preset-default/bin/**/*.js', 'packages/theme/bin/**/*.[tj]s?(x)', diff --git a/routes/connectors-home/ai-plugin-callout.test.tsx b/routes/connectors-home/ai-plugin-callout.test.tsx index 8acdae27ef2116..7b7cb8cfc1c420 100644 --- a/routes/connectors-home/ai-plugin-callout.test.tsx +++ b/routes/connectors-home/ai-plugin-callout.test.tsx @@ -136,11 +136,8 @@ describe( 'AiPluginCallout', () => { }; ( useSelect as jest.Mock ).mockImplementation( - ( - mapSelect: ( - select: () => typeof selectorStore - ) => unknown - ) => mapSelect( () => selectorStore ) + ( mapSelect: ( select: () => typeof selectorStore ) => unknown ) => + mapSelect( () => selectorStore ) ); mockSaveEntityRecord.mockReset(); From 508374fbefa5bdda577b6d17f561c0a1b7de063f Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 17:31:28 +0100 Subject: [PATCH 5/9] Lint: Move connectors AI route test under test directory --- eslint.config.mjs | 1 - .../ai-plugin-callout.tsx} | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename routes/connectors-home/{ai-plugin-callout.test.tsx => test/ai-plugin-callout.tsx} (96%) diff --git a/eslint.config.mjs b/eslint.config.mjs index 9869c854d035a7..af1f1ef566231f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -65,7 +65,6 @@ function dedupePlugins( configs ) { const developmentFiles = [ '**/benchmark/**/*.js', '**/@(__mocks__|__tests__|test)/**/*.[tj]s?(x)', - '**/*.@(test|spec).[tj]s?(x)', '**/@(storybook|stories)/**/*.[tj]s?(x)', 'packages/babel-preset-default/bin/**/*.js', 'packages/theme/bin/**/*.[tj]s?(x)', diff --git a/routes/connectors-home/ai-plugin-callout.test.tsx b/routes/connectors-home/test/ai-plugin-callout.tsx similarity index 96% rename from routes/connectors-home/ai-plugin-callout.test.tsx rename to routes/connectors-home/test/ai-plugin-callout.tsx index 7b7cb8cfc1c420..16bcfaf01b4cf6 100644 --- a/routes/connectors-home/ai-plugin-callout.test.tsx +++ b/routes/connectors-home/test/ai-plugin-callout.tsx @@ -12,7 +12,7 @@ import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { AiPluginCallout } from './ai-plugin-callout'; +import { AiPluginCallout } from '../ai-plugin-callout'; jest.mock( '@wordpress/a11y', () => ( { speak: jest.fn(), @@ -66,7 +66,7 @@ jest.mock( '@wordpress/data', () => ( { useSelect: jest.fn(), } ) ); -jest.mock( './default-connectors', () => ( { +jest.mock( '../default-connectors', () => ( { getConnectorData: jest.fn( () => ( { openai: { type: 'ai_provider', @@ -79,7 +79,7 @@ jest.mock( './default-connectors', () => ( { } ) ), } ) ); -jest.mock( './wp-logo-decoration', () => ( { +jest.mock( '../wp-logo-decoration', () => ( { WpLogoDecoration: () => null, } ) ); From 544365708166e2e74b8c7d43237a286b67d1abee Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 17:37:24 +0100 Subject: [PATCH 6/9] Tests: Stub wordpress data store helpers in AI callout test --- .../connectors-home/test/ai-plugin-callout.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/routes/connectors-home/test/ai-plugin-callout.tsx b/routes/connectors-home/test/ai-plugin-callout.tsx index 16bcfaf01b4cf6..8831d8b2d4c0ce 100644 --- a/routes/connectors-home/test/ai-plugin-callout.tsx +++ b/routes/connectors-home/test/ai-plugin-callout.tsx @@ -64,6 +64,22 @@ jest.mock( '@wordpress/core-data', () => ( { jest.mock( '@wordpress/data', () => ( { useDispatch: jest.fn(), useSelect: jest.fn(), + createSelector: jest.fn( ( fn ) => fn ), + createRegistrySelector: jest.fn( ( fn ) => fn ), + createReduxStore: jest.fn( () => ( {} ) ), + combineReducers: jest.fn( ( reducers ) => ( state = {}, action ) => { + const newState: Record< string, unknown > = {}; + + Object.keys( reducers ).forEach( ( key ) => { + newState[ key ] = reducers[ key ]( + ( state as Record< string, unknown > )[ key ], + action + ); + } ); + + return newState; + } ), + register: jest.fn(), } ) ); jest.mock( '../default-connectors', () => ( { From 6678cbd30a6a51d9d74a22ad484c2a7217895d8e Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 Apr 2026 18:03:32 +0100 Subject: [PATCH 7/9] Tests: Format AI callout mock --- routes/connectors-home/test/ai-plugin-callout.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/routes/connectors-home/test/ai-plugin-callout.tsx b/routes/connectors-home/test/ai-plugin-callout.tsx index 8831d8b2d4c0ce..f94abd88d824f9 100644 --- a/routes/connectors-home/test/ai-plugin-callout.tsx +++ b/routes/connectors-home/test/ai-plugin-callout.tsx @@ -19,9 +19,8 @@ jest.mock( '@wordpress/a11y', () => ( { } ) ); jest.mock( '@wordpress/components', () => { - const { createElement, forwardRef } = jest.requireActual( - '@wordpress/element' - ); + const { createElement, forwardRef } = + jest.requireActual( '@wordpress/element' ); return { Button: forwardRef( From cf82100ebd04b3fa120fedc23dee90b0d9c5b2fc Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 15 Apr 2026 11:32:01 +0100 Subject: [PATCH 8/9] test fixes --- routes/connectors-home/test/ai-plugin-callout.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/routes/connectors-home/test/ai-plugin-callout.tsx b/routes/connectors-home/test/ai-plugin-callout.tsx index f94abd88d824f9..6a239f43a6250b 100644 --- a/routes/connectors-home/test/ai-plugin-callout.tsx +++ b/routes/connectors-home/test/ai-plugin-callout.tsx @@ -106,6 +106,8 @@ type StoreState = { }; const mockSaveEntityRecord = jest.fn(); +const mockCreateSuccessNotice = jest.fn(); +const mockCreateErrorNotice = jest.fn(); describe( 'AiPluginCallout', () => { let storeState: StoreState; @@ -157,8 +159,12 @@ describe( 'AiPluginCallout', () => { mockSaveEntityRecord.mockReset(); mockSaveEntityRecord.mockResolvedValue( undefined ); + mockCreateSuccessNotice.mockReset(); + mockCreateErrorNotice.mockReset(); ( useDispatch as jest.Mock ).mockReturnValue( { saveEntityRecord: mockSaveEntityRecord, + createSuccessNotice: mockCreateSuccessNotice, + createErrorNotice: mockCreateErrorNotice, } ); } ); From 7bf815d03e1c65a3a43bfa36c8fed7b6999ffa64 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 15 Apr 2026 12:30:00 +0100 Subject: [PATCH 9/9] remove unrequired change --- routes/connectors-home/ai-plugin-callout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/connectors-home/ai-plugin-callout.tsx b/routes/connectors-home/ai-plugin-callout.tsx index 4fef98de459de8..44757685b9f9f5 100644 --- a/routes/connectors-home/ai-plugin-callout.tsx +++ b/routes/connectors-home/ai-plugin-callout.tsx @@ -22,7 +22,7 @@ import { WpLogoDecoration } from './wp-logo-decoration'; import type { PluginStatus } from './use-connector-plugin'; -const AI_PLUGIN_INSTALL_SLUG = 'ai'; +const AI_PLUGIN_SLUG = 'ai'; const AI_PLUGIN_PAGE_SLUG = 'ai-wp-admin'; const AI_PLUGIN_ID = 'ai/ai'; const AI_PLUGIN_URL = 'https://wordpress.org/plugins/ai/'; @@ -133,7 +133,7 @@ export function AiPluginCallout() { await saveEntityRecord( 'root', 'plugin', - { slug: AI_PLUGIN_INSTALL_SLUG, status: 'active' }, + { slug: AI_PLUGIN_SLUG, status: 'active' }, { throwOnError: true } ); setJustActivated( true );