Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ jobs:
event: ['${{ github.event_name }}']
shard: [1, 2, 3, 4]
node: ['22', '24']
wp: ['6.2', 'latest', 'trunk']
wp: ['6.2', '6.3', '6.8', 'latest', 'trunk']
exclude:
# On PRs: only test Node 22 + WP latest and trunk for fast feedback
# On trunk: full matrix with node 24 and minimum WP version
- event: 'pull_request'
node: '24'
- event: 'pull_request'
wp: '6.2'

env:
WP_ENV_CORE: ${{ matrix.wp == 'trunk' && 'WordPress/WordPress' || (matrix.wp != 'latest' && format('WordPress/WordPress#{0}', matrix.wp) || null) }}
Expand Down
83 changes: 23 additions & 60 deletions assets/src/js/commands/admin-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { dispatch } from '@wordpress/data';
import { dispatch, select } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';
import {
layout,
Expand All @@ -31,12 +29,12 @@ import {
* Register admin commands for SCF
*/
const registerAdminCommands = () => {
if ( ! dispatch( 'core/commands' ) || ! window.acf?.data ) {
if ( ! select( 'core/commands') || ! dispatch( 'core/commands' ) ) {
return;
}

const registeredCommands = select( 'core/commands' ).getCommands();
const commandStore = dispatch( 'core/commands' );
const adminUrl = window.acf?.data?.admin_url || '';

const viewCommands = [
{
Expand All @@ -45,10 +43,6 @@ const registerAdminCommands = () => {
url: 'edit.php',
urlArgs: { post_type: 'acf-field-group' },
icon: layout,
description: __(
'SCF: View and manage custom field groups',
'secure-custom-fields'
),
keywords: [
'acf',
'custom fields',
Expand All @@ -62,10 +56,6 @@ const registerAdminCommands = () => {
url: 'edit.php',
urlArgs: { post_type: 'acf-post-type' },
icon: postList,
description: __(
'SCF: Manage custom post types',
'secure-custom-fields'
),
keywords: [ 'cpt', 'content types', 'manage post types' ],
},
{
Expand All @@ -74,10 +64,6 @@ const registerAdminCommands = () => {
url: 'edit.php',
urlArgs: { post_type: 'acf-taxonomy' },
icon: category,
description: __(
'SCF: Manage custom taxonomies for organizing content',
'secure-custom-fields'
),
keywords: [ 'categories', 'tags', 'terms', 'custom taxonomies' ],
},
{
Expand All @@ -86,10 +72,6 @@ const registerAdminCommands = () => {
url: 'edit.php',
urlArgs: { post_type: 'acf-ui-options-page' },
icon: settings,
description: __(
'SCF: Manage custom options pages for global settings',
'secure-custom-fields'
),
keywords: [ 'settings', 'global options', 'site options' ],
},
{
Expand All @@ -98,10 +80,6 @@ const registerAdminCommands = () => {
url: 'admin.php',
urlArgs: { page: 'acf-tools' },
icon: tool,
description: __(
'SCF: Access SCF utility tools',
'secure-custom-fields'
),
keywords: [ 'utilities', 'import export', 'json' ],
},
{
Expand All @@ -110,10 +88,6 @@ const registerAdminCommands = () => {
url: 'admin.php',
urlArgs: { page: 'acf-tools', tool: 'import' },
icon: upload,
description: __(
'SCF: Import field groups, post types, taxonomies, and options pages',
'secure-custom-fields'
),
keywords: [ 'upload', 'json', 'migration', 'transfer' ],
},
{
Expand All @@ -122,10 +96,6 @@ const registerAdminCommands = () => {
url: 'admin.php',
urlArgs: { page: 'acf-tools', tool: 'export' },
icon: download,
description: __(
'SCF: Export field groups, post types, taxonomies, and options pages',
'secure-custom-fields'
),
keywords: [ 'download', 'json', 'backup', 'migration' ],
},
];
Expand All @@ -138,10 +108,6 @@ const registerAdminCommands = () => {
url: 'post-new.php',
urlArgs: { post_type: 'acf-field-group' },
icon: plus,
description: __(
'SCF: Create a new field group to organize custom fields',
'secure-custom-fields'
),
keywords: [
'add',
'new',
Expand All @@ -156,10 +122,6 @@ const registerAdminCommands = () => {
url: 'post-new.php',
urlArgs: { post_type: 'acf-post-type' },
icon: plus,
description: __(
'SCF: Create a new custom post type',
'secure-custom-fields'
),
keywords: [ 'add', 'new', 'create', 'cpt', 'content type' ],
},
{
Expand All @@ -168,10 +130,6 @@ const registerAdminCommands = () => {
url: 'post-new.php',
urlArgs: { post_type: 'acf-taxonomy' },
icon: plus,
description: __(
'SCF: Create a new custom taxonomy',
'secure-custom-fields'
),
keywords: [
'add',
'new',
Expand All @@ -187,10 +145,6 @@ const registerAdminCommands = () => {
url: 'post-new.php',
urlArgs: { post_type: 'acf-ui-options-page' },
icon: plus,
description: __(
'SCF: Create a new custom options page',
'secure-custom-fields'
),
keywords: [ 'add', 'new', 'create', 'options', 'settings page' ],
},
];
Expand All @@ -199,27 +153,36 @@ const registerAdminCommands = () => {
commandStore.registerCommand( {
name: 'scf/' + command.name,
label: command.label,
icon: createElement( Icon, { icon: command.icon } ),
icon: command.icon,
context: 'admin',
description: command.description,
keywords: command.keywords,
callback: ( { close } ) => {
document.location = command.urlArgs
? addQueryArgs( adminUrl + command.url, command.urlArgs )
: adminUrl + command.url;
document.location = addQueryArgs(
command.url,
command.urlArgs
);
close();
},
} );
};

// WordPress 6.9+ adds Command Palette commands for all admin menu items.
const wpVersion = window.acf.data.wp_version;
const isWp69Plus =
wpVersion.localeCompare( '6.9', undefined, { numeric: true } ) >= 0;
// For older versions, we need to register them manually. The most reliable way to
// detect this is to check if the commands are already registered.
viewCommands.forEach( ( command ) => {
const commandUrl = addQueryArgs( command.url, command.urlArgs );
// WordPress stores destination URLs in the command *name*, appended to
// the menu slug (which is also a relative URL), resulting in somewhat
// peculiar naming, e.g.
// edit.php?post_type=acf-field-group-edit.php?post_type=acf-ui-options-page
if ( registeredCommands.some( ( cmd ) => cmd.name.endsWith( commandUrl ) ) ) {
return;
}
registerCommand( command );
} );

if ( ! isWp69Plus ) {
viewCommands.forEach( registerCommand );
}
// "Create New" commands are not automatically registered by WordPress,
// so we always register them.
createCommands.forEach( registerCommand );
};

Expand Down
22 changes: 6 additions & 16 deletions assets/src/js/commands/custom-post-type-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { dispatch } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';
import { page, plus, edit } from '@wordpress/icons';
Expand All @@ -34,7 +32,6 @@ const registerPostTypeCommands = () => {
}

const commandStore = dispatch( 'core/commands' );
const adminUrl = window.acf.data.admin_url || '';
const postTypes = window.acf.data.customPostTypes;

// WordPress 6.9+ adds Command Palette commands for all admin menu items.
Expand All @@ -58,9 +55,8 @@ const registerPostTypeCommands = () => {
commandStore.registerCommand( {
name: `scf/cpt-${ postType.name }`,
label: postType.all_items,
icon: createElement( Icon, { icon: page } ),
icon: page,
context: 'admin',
description: postType.all_items,
keywords: [
'post type',
'content',
Expand All @@ -69,7 +65,7 @@ const registerPostTypeCommands = () => {
postType.label,
].filter( Boolean ),
callback: ( { close } ) => {
document.location = addQueryArgs( adminUrl + 'edit.php', {
document.location = addQueryArgs( 'edit.php', {
post_type: postType.name,
} );
close();
Expand All @@ -80,9 +76,8 @@ const registerPostTypeCommands = () => {
commandStore.registerCommand( {
name: `scf/new-${ postType.name }`,
label: postType.add_new_item,
icon: createElement( Icon, { icon: plus } ),
icon: plus,
context: 'admin',
description: postType.add_new_item,
keywords: [
'add',
'new',
Expand All @@ -93,7 +88,7 @@ const registerPostTypeCommands = () => {
],
callback: ( { close } ) => {
document.location = addQueryArgs(
adminUrl + 'post-new.php',
'post-new.php',
{
post_type: postType.name,
}
Expand All @@ -111,13 +106,8 @@ const registerPostTypeCommands = () => {
__( 'Edit post type: %s', 'secure-custom-fields' ),
postType.label
),
icon: createElement( Icon, { icon: edit } ),
icon: edit,
context: 'admin',
description: sprintf(
/* translators: %s: post type label */
__( 'Edit the %s post type settings', 'secure-custom-fields' ),
postType.label
),
keywords: [
'edit',
'modify',
Expand All @@ -128,7 +118,7 @@ const registerPostTypeCommands = () => {
postType.label,
],
callback: ( { close } ) => {
document.location = addQueryArgs( adminUrl + 'post.php', {
document.location = addQueryArgs( 'post.php', {
post: postType.id,
action: 'edit',
} );
Expand Down
91 changes: 91 additions & 0 deletions tests/e2e/command-palette.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Internal dependencies
*/
const { test, expect, wpVersionAtLeast } = require( './fixtures' );

test.describe( 'Command Palette', () => {
test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.activatePlugin( 'secure-custom-fields' );
} );

test.afterAll( async ( { requestUtils } ) => {
await requestUtils.deactivatePlugin( 'secure-custom-fields' );
} );

test( 'should register SCF create commands', async ( { page, admin } ) => {
await admin.visitAdminPage( 'index.php' );
test.skip(
! ( await wpVersionAtLeast( page, 6, 3 ) ),
'Command Palette requires WordPress 6.3+'
);

// Open the command palette via keyboard shortcut.
await page.keyboard.press( 'ControlOrMeta+k' );

const input = page.getByRole( 'combobox', {
name: 'Search commands and settings',
} );
await expect( input ).toBeVisible();

Check failure on line 28 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands

1) [chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('combobox', { name: 'Search commands and settings' }) Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 26 | name: 'Search commands and settings', 27 | } ); > 28 | await expect( input ).toBeVisible(); | ^ 29 | 30 | // Search for a create command — these are always registered by SCF. 31 | await input.fill( 'Create New Field Group' ); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:28:25

Check failure on line 28 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands

1) [chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('combobox', { name: 'Search commands and settings' }) Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 26 | name: 'Search commands and settings', 27 | } ); > 28 | await expect( input ).toBeVisible(); | ^ 29 | 30 | // Search for a create command — these are always registered by SCF. 31 | await input.fill( 'Create New Field Group' ); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:28:25

Check failure on line 28 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands

1) [chromium] › tests/e2e/command-palette.spec.ts:15:2 › Command Palette › should register SCF create commands Error: expect(locator).toBeVisible() failed Locator: getByRole('combobox', { name: 'Search commands and settings' }) Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 26 | name: 'Search commands and settings', 27 | } ); > 28 | await expect( input ).toBeVisible(); | ^ 29 | 30 | // Search for a create command — these are always registered by SCF. 31 | await input.fill( 'Create New Field Group' ); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:28:25

// Search for a create command — these are always registered by SCF.
await input.fill( 'Create New Field Group' );
await expect(
page.getByRole( 'option', { name: /Create New Field Group/ } )
).toBeVisible();
} );

test( 'should register SCF view commands without duplicates', async ( {
page,
admin,
} ) => {
await admin.visitAdminPage( 'index.php' );
test.skip(
! ( await wpVersionAtLeast( page, 6, 3 ) ),
'Command Palette requires WordPress 6.3+'
);

await page.keyboard.press( 'ControlOrMeta+k' );

const input = page.getByRole( 'combobox', {
name: 'Search commands and settings',
} );

// Search for a view command that exists in both WP's auto-registered
// admin menu commands and SCF's view commands list.
await input.fill( 'Field Groups' );

Check failure on line 55 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates

2) [chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 53 | // Search for a view command that exists in both WP's auto-registered 54 | // admin menu commands and SCF's view commands list. > 55 | await input.fill( 'Field Groups' ); | ^ 56 | 57 | const options = page.getByRole( 'option', { 58 | name: /Field Groups/, at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:55:15

Check failure on line 55 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates

2) [chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 53 | // Search for a view command that exists in both WP's auto-registered 54 | // admin menu commands and SCF's view commands list. > 55 | await input.fill( 'Field Groups' ); | ^ 56 | 57 | const options = page.getByRole( 'option', { 58 | name: /Field Groups/, at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:55:15

Check failure on line 55 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates

2) [chromium] › tests/e2e/command-palette.spec.ts:37:2 › Command Palette › should register SCF view commands without duplicates TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 53 | // Search for a view command that exists in both WP's auto-registered 54 | // admin menu commands and SCF's view commands list. > 55 | await input.fill( 'Field Groups' ); | ^ 56 | 57 | const options = page.getByRole( 'option', { 58 | name: /Field Groups/, at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:55:15

const options = page.getByRole( 'option', {
name: /Field Groups/,
} );

// There should be exactly one match, not two (no duplicate).
await expect( options ).toHaveCount( 1 );
} );

test( 'should navigate to field groups via command palette', async ( {
page,
admin,
} ) => {
await admin.visitAdminPage( 'index.php' );
// Command Palette was introduced in WordPress 6.3
test.skip(
! ( await wpVersionAtLeast( page, 6, 3 ) ),
'Command Palette requires WordPress 6.3+'
);

await page.keyboard.press( 'ControlOrMeta+k' );

const input = page.getByRole( 'combobox', {
name: 'Search commands and settings',
} );

await input.fill( 'Field Groups' );

Check failure on line 82 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette

3) [chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 80 | } ); 81 | > 82 | await input.fill( 'Field Groups' ); | ^ 83 | await page 84 | .getByRole( 'option', { name: /Field Groups/ } ) 85 | .click(); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:82:15

Check failure on line 82 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette

3) [chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 80 | } ); 81 | > 82 | await input.fill( 'Field Groups' ); | ^ 83 | await page 84 | .getByRole( 'option', { name: /Field Groups/ } ) 85 | .click(); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:82:15

Check failure on line 82 in tests/e2e/command-palette.spec.ts

View workflow job for this annotation

GitHub Actions / E2E - Node 22 / WP 6.8 / Shard 2/4

[chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette

3) [chromium] › tests/e2e/command-palette.spec.ts:65:2 › Command Palette › should navigate to field groups via command palette TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log: - waiting for getByRole('combobox', { name: 'Search commands and settings' }) 80 | } ); 81 | > 82 | await input.fill( 'Field Groups' ); | ^ 83 | await page 84 | .getByRole( 'option', { name: /Field Groups/ } ) 85 | .click(); at /home/runner/work/secure-custom-fields/secure-custom-fields/tests/e2e/command-palette.spec.ts:82:15
await page
.getByRole( 'option', { name: /Field Groups/ } )
.click();

await expect( page ).toHaveURL(
/edit\.php\?post_type=acf-field-group/
);
} );
} );
Loading
Loading