fix: prevent asset conflicts between React and Grid.js versions

Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

107
node_modules/@wordpress/hooks/src/createAddHook.js generated vendored Normal file
View File

@@ -0,0 +1,107 @@
/**
* Internal dependencies
*/
import validateNamespace from './validateNamespace.js';
import validateHookName from './validateHookName.js';
/**
* @callback AddHook
*
* Adds the hook to the appropriate hooks container.
*
* @param {string} hookName Name of hook to add
* @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`.
* @param {import('.').Callback} callback Function to call when the hook is run
* @param {number} [priority=10] Priority of this hook
*/
/**
* Returns a function which, when invoked, will add a hook.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
*
* @return {AddHook} Function that adds a new hook.
*/
function createAddHook( hooks, storeKey ) {
return function addHook( hookName, namespace, callback, priority = 10 ) {
const hooksStore = hooks[ storeKey ];
if ( ! validateHookName( hookName ) ) {
return;
}
if ( ! validateNamespace( namespace ) ) {
return;
}
if ( 'function' !== typeof callback ) {
// eslint-disable-next-line no-console
console.error( 'The hook callback must be a function.' );
return;
}
// Validate numeric priority
if ( 'number' !== typeof priority ) {
// eslint-disable-next-line no-console
console.error(
'If specified, the hook priority must be a number.'
);
return;
}
const handler = { callback, priority, namespace };
if ( hooksStore[ hookName ] ) {
// Find the correct insert index of the new hook.
const handlers = hooksStore[ hookName ].handlers;
/** @type {number} */
let i;
for ( i = handlers.length; i > 0; i-- ) {
if ( priority >= handlers[ i - 1 ].priority ) {
break;
}
}
if ( i === handlers.length ) {
// If append, operate via direct assignment.
handlers[ i ] = handler;
} else {
// Otherwise, insert before index via splice.
handlers.splice( i, 0, handler );
}
// We may also be currently executing this hook. If the callback
// we're adding would come after the current callback, there's no
// problem; otherwise we need to increase the execution index of
// any other runs by 1 to account for the added element.
hooksStore.__current.forEach( ( hookInfo ) => {
if (
hookInfo.name === hookName &&
hookInfo.currentIndex >= i
) {
hookInfo.currentIndex++;
}
} );
} else {
// This is the first hook of its type.
hooksStore[ hookName ] = {
handlers: [ handler ],
runs: 0,
};
}
if ( hookName !== 'hookAdded' ) {
hooks.doAction(
'hookAdded',
hookName,
namespace,
callback,
priority
);
}
};
}
export default createAddHook;

22
node_modules/@wordpress/hooks/src/createCurrentHook.js generated vendored Normal file
View File

@@ -0,0 +1,22 @@
/**
* Returns a function which, when invoked, will return the name of the
* currently running hook, or `null` if no hook of the given type is currently
* running.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
*
* @return {() => string | null} Function that returns the current hook name or null.
*/
function createCurrentHook( hooks, storeKey ) {
return function currentHook() {
const hooksStore = hooks[ storeKey ];
return (
hooksStore.__current[ hooksStore.__current.length - 1 ]?.name ??
null
);
};
}
export default createCurrentHook;

39
node_modules/@wordpress/hooks/src/createDidHook.js generated vendored Normal file
View File

@@ -0,0 +1,39 @@
/**
* Internal dependencies
*/
import validateHookName from './validateHookName.js';
/**
* @callback DidHook
*
* Returns the number of times an action has been fired.
*
* @param {string} hookName The hook name to check.
*
* @return {number | undefined} The number of times the hook has run.
*/
/**
* Returns a function which, when invoked, will return the number of times a
* hook has been called.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
*
* @return {DidHook} Function that returns a hook's call count.
*/
function createDidHook( hooks, storeKey ) {
return function didHook( hookName ) {
const hooksStore = hooks[ storeKey ];
if ( ! validateHookName( hookName ) ) {
return;
}
return hooksStore[ hookName ] && hooksStore[ hookName ].runs
? hooksStore[ hookName ].runs
: 0;
};
}
export default createDidHook;

37
node_modules/@wordpress/hooks/src/createDoingHook.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
/**
* @callback DoingHook
* Returns whether a hook is currently being executed.
*
* @param {string} [hookName] The name of the hook to check for. If
* omitted, will check for any hook being executed.
*
* @return {boolean} Whether the hook is being executed.
*/
/**
* Returns a function which, when invoked, will return whether a hook is
* currently being executed.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
*
* @return {DoingHook} Function that returns whether a hook is currently
* being executed.
*/
function createDoingHook( hooks, storeKey ) {
return function doingHook( hookName ) {
const hooksStore = hooks[ storeKey ];
// If the hookName was not passed, check for any current hook.
if ( 'undefined' === typeof hookName ) {
return 'undefined' !== typeof hooksStore.__current[ 0 ];
}
// Return the __current hook.
return hooksStore.__current[ 0 ]
? hookName === hooksStore.__current[ 0 ].name
: false;
};
}
export default createDoingHook;

40
node_modules/@wordpress/hooks/src/createHasHook.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
/**
* @callback HasHook
*
* Returns whether any handlers are attached for the given hookName and optional namespace.
*
* @param {string} hookName The name of the hook to check for.
* @param {string} [namespace] Optional. The unique namespace identifying the callback
* in the form `vendor/plugin/function`.
*
* @return {boolean} Whether there are handlers that are attached to the given hook.
*/
/**
* Returns a function which, when invoked, will return whether any handlers are
* attached to a particular hook.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
*
* @return {HasHook} Function that returns whether any handlers are
* attached to a particular hook and optional namespace.
*/
function createHasHook( hooks, storeKey ) {
return function hasHook( hookName, namespace ) {
const hooksStore = hooks[ storeKey ];
// Use the namespace if provided.
if ( 'undefined' !== typeof namespace ) {
return (
hookName in hooksStore &&
hooksStore[ hookName ].handlers.some(
( hook ) => hook.namespace === namespace
)
);
}
return hookName in hooksStore;
};
}
export default createHasHook;

59
node_modules/@wordpress/hooks/src/createHooks.js generated vendored Normal file
View File

@@ -0,0 +1,59 @@
/**
* Internal dependencies
*/
import createAddHook from './createAddHook';
import createRemoveHook from './createRemoveHook';
import createHasHook from './createHasHook';
import createRunHook from './createRunHook';
import createCurrentHook from './createCurrentHook';
import createDoingHook from './createDoingHook';
import createDidHook from './createDidHook';
/**
* Internal class for constructing hooks. Use `createHooks()` function
*
* Note, it is necessary to expose this class to make its type public.
*
* @private
*/
export class _Hooks {
constructor() {
/** @type {import('.').Store} actions */
this.actions = Object.create( null );
this.actions.__current = [];
/** @type {import('.').Store} filters */
this.filters = Object.create( null );
this.filters.__current = [];
this.addAction = createAddHook( this, 'actions' );
this.addFilter = createAddHook( this, 'filters' );
this.removeAction = createRemoveHook( this, 'actions' );
this.removeFilter = createRemoveHook( this, 'filters' );
this.hasAction = createHasHook( this, 'actions' );
this.hasFilter = createHasHook( this, 'filters' );
this.removeAllActions = createRemoveHook( this, 'actions', true );
this.removeAllFilters = createRemoveHook( this, 'filters', true );
this.doAction = createRunHook( this, 'actions' );
this.applyFilters = createRunHook( this, 'filters', true );
this.currentAction = createCurrentHook( this, 'actions' );
this.currentFilter = createCurrentHook( this, 'filters' );
this.doingAction = createDoingHook( this, 'actions' );
this.doingFilter = createDoingHook( this, 'filters' );
this.didAction = createDidHook( this, 'actions' );
this.didFilter = createDidHook( this, 'filters' );
}
}
/** @typedef {_Hooks} Hooks */
/**
* Returns an instance of the hooks object.
*
* @return {Hooks} A Hooks instance.
*/
function createHooks() {
return new _Hooks();
}
export default createHooks;

88
node_modules/@wordpress/hooks/src/createRemoveHook.js generated vendored Normal file
View File

@@ -0,0 +1,88 @@
/**
* Internal dependencies
*/
import validateNamespace from './validateNamespace.js';
import validateHookName from './validateHookName.js';
/**
* @callback RemoveHook
* Removes the specified callback (or all callbacks) from the hook with a given hookName
* and namespace.
*
* @param {string} hookName The name of the hook to modify.
* @param {string} namespace The unique namespace identifying the callback in the
* form `vendor/plugin/function`.
*
* @return {number | undefined} The number of callbacks removed.
*/
/**
* Returns a function which, when invoked, will remove a specified hook or all
* hooks by the given name.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
* @param {boolean} [removeAll=false] Whether to remove all callbacks for a hookName,
* without regard to namespace. Used to create
* `removeAll*` functions.
*
* @return {RemoveHook} Function that removes hooks.
*/
function createRemoveHook( hooks, storeKey, removeAll = false ) {
return function removeHook( hookName, namespace ) {
const hooksStore = hooks[ storeKey ];
if ( ! validateHookName( hookName ) ) {
return;
}
if ( ! removeAll && ! validateNamespace( namespace ) ) {
return;
}
// Bail if no hooks exist by this name.
if ( ! hooksStore[ hookName ] ) {
return 0;
}
let handlersRemoved = 0;
if ( removeAll ) {
handlersRemoved = hooksStore[ hookName ].handlers.length;
hooksStore[ hookName ] = {
runs: hooksStore[ hookName ].runs,
handlers: [],
};
} else {
// Try to find the specified callback to remove.
const handlers = hooksStore[ hookName ].handlers;
for ( let i = handlers.length - 1; i >= 0; i-- ) {
if ( handlers[ i ].namespace === namespace ) {
handlers.splice( i, 1 );
handlersRemoved++;
// This callback may also be part of a hook that is
// currently executing. If the callback we're removing
// comes after the current callback, there's no problem;
// otherwise we need to decrease the execution index of any
// other runs by 1 to account for the removed element.
hooksStore.__current.forEach( ( hookInfo ) => {
if (
hookInfo.name === hookName &&
hookInfo.currentIndex >= i
) {
hookInfo.currentIndex--;
}
} );
}
}
}
if ( hookName !== 'hookRemoved' ) {
hooks.doAction( 'hookRemoved', hookName, namespace );
}
return handlersRemoved;
};
}
export default createRemoveHook;

68
node_modules/@wordpress/hooks/src/createRunHook.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
/**
* Returns a function which, when invoked, will execute all callbacks
* registered to a hook of the specified type, optionally returning the final
* value of the call chain.
*
* @param {import('.').Hooks} hooks Hooks instance.
* @param {import('.').StoreKey} storeKey
* @param {boolean} [returnFirstArg=false] Whether each hook callback is expected to
* return its first argument.
*
* @return {(hookName:string, ...args: unknown[]) => undefined|unknown} Function that runs hook callbacks.
*/
function createRunHook( hooks, storeKey, returnFirstArg = false ) {
return function runHooks( hookName, ...args ) {
const hooksStore = hooks[ storeKey ];
if ( ! hooksStore[ hookName ] ) {
hooksStore[ hookName ] = {
handlers: [],
runs: 0,
};
}
hooksStore[ hookName ].runs++;
const handlers = hooksStore[ hookName ].handlers;
// The following code is stripped from production builds.
if ( 'production' !== process.env.NODE_ENV ) {
// Handle any 'all' hooks registered.
if ( 'hookAdded' !== hookName && hooksStore.all ) {
handlers.push( ...hooksStore.all.handlers );
}
}
if ( ! handlers || ! handlers.length ) {
return returnFirstArg ? args[ 0 ] : undefined;
}
const hookInfo = {
name: hookName,
currentIndex: 0,
};
hooksStore.__current.push( hookInfo );
while ( hookInfo.currentIndex < handlers.length ) {
const handler = handlers[ hookInfo.currentIndex ];
const result = handler.callback.apply( null, args );
if ( returnFirstArg ) {
args[ 0 ] = result;
}
hookInfo.currentIndex++;
}
hooksStore.__current.pop();
if ( returnFirstArg ) {
return args[ 0 ];
}
return undefined;
};
}
export default createRunHook;

82
node_modules/@wordpress/hooks/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,82 @@
/**
* Internal dependencies
*/
import createHooks from './createHooks';
/** @typedef {(...args: any[])=>any} Callback */
/**
* @typedef Handler
* @property {Callback} callback The callback
* @property {string} namespace The namespace
* @property {number} priority The namespace
*/
/**
* @typedef Hook
* @property {Handler[]} handlers Array of handlers
* @property {number} runs Run counter
*/
/**
* @typedef Current
* @property {string} name Hook name
* @property {number} currentIndex The index
*/
/**
* @typedef {Record<string, Hook> & {__current: Current[]}} Store
*/
/**
* @typedef {'actions' | 'filters'} StoreKey
*/
/**
* @typedef {import('./createHooks').Hooks} Hooks
*/
export const defaultHooks = createHooks();
const {
addAction,
addFilter,
removeAction,
removeFilter,
hasAction,
hasFilter,
removeAllActions,
removeAllFilters,
doAction,
applyFilters,
currentAction,
currentFilter,
doingAction,
doingFilter,
didAction,
didFilter,
actions,
filters,
} = defaultHooks;
export {
createHooks,
addAction,
addFilter,
removeAction,
removeFilter,
hasAction,
hasFilter,
removeAllActions,
removeAllFilters,
doAction,
applyFilters,
currentAction,
currentFilter,
doingAction,
doingFilter,
didAction,
didFilter,
actions,
filters,
};

945
node_modules/@wordpress/hooks/src/test/index.test.js generated vendored Normal file
View File

@@ -0,0 +1,945 @@
/**
* Internal dependencies
*/
import {
createHooks,
addAction,
addFilter,
removeAction,
removeFilter,
hasAction,
hasFilter,
removeAllActions,
removeAllFilters,
doAction,
applyFilters,
currentAction,
currentFilter,
doingAction,
doingFilter,
didAction,
didFilter,
actions,
filters,
} from '..';
function filterA( str ) {
return str + 'a';
}
function filterB( str ) {
return str + 'b';
}
function filterC( str ) {
return str + 'c';
}
function filterCRemovesSelf( str ) {
removeFilter( 'test.filter', 'my_callback_filter_c_removes_self' );
return str + 'b';
}
function filterRemovesB( str ) {
removeFilter( 'test.filter', 'my_callback_filter_b' );
return str;
}
function filterRemovesC( str ) {
removeFilter( 'test.filter', 'my_callback_filter_c' );
return str;
}
function actionA() {
window.actionValue += 'a';
}
function actionB() {
window.actionValue += 'b';
}
function actionC() {
window.actionValue += 'c';
}
beforeEach( () => {
window.actionValue = '';
// Reset state in between tests (clear all callbacks, `didAction` counts,
// etc.) Just reseting actions and filters is not enough
// because the internal functions have references to the original objects.
[ actions, filters ].forEach( ( hooks ) => {
for ( const k in hooks ) {
if ( '__current' === k ) {
continue;
}
delete hooks[ k ];
}
delete hooks.all;
} );
} );
test( 'hooks can be instantiated', () => {
const hooks = createHooks();
expect( typeof hooks ).toBe( 'object' );
} );
test( 'run a filter with no callbacks', () => {
expect( applyFilters( 'test.filter', 42 ) ).toBe( 42 );
} );
test( 'add and remove a filter', () => {
addFilter( 'test.filter', 'my_callback', filterA );
expect( removeAllFilters( 'test.filter' ) ).toBe( 1 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'test' );
expect( removeAllFilters( 'test.filter' ) ).toBe( 0 );
} );
test( 'add a filter and run it', () => {
addFilter( 'test.filter', 'my_callback', filterA );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testa' );
} );
test( 'add 2 filters in a row and run them', () => {
addFilter( 'test.filter', 'my_callback', filterA );
addFilter( 'test.filter', 'my_callback', filterB );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testab' );
} );
test( 'remove a non-existent filter', () => {
expect( removeFilter( 'test.filter', 'my_callback', filterA ) ).toBe( 0 );
expect( removeAllFilters( 'test.filter' ) ).toBe( 0 );
} );
test( 'remove an invalid namespace from a filter', () => {
expect( removeFilter( 'test.filter', 42 ) ).toBe( undefined );
expect( console ).toHaveErroredWith(
'The namespace must be a non-empty string.'
);
} );
test( 'cannot add filters with non-string hook names', () => {
addFilter( 42, 'my_callback', () => null );
expect( console ).toHaveErroredWith(
'The hook name must be a non-empty string.'
);
} );
test( 'cannot add filters with empty-string hook names', () => {
addFilter( '', 'my_callback', () => null );
expect( console ).toHaveErroredWith(
'The hook name must be a non-empty string.'
);
} );
test( 'cannot add filters with empty-string namespaces', () => {
addFilter( 'hook_name', '', () => null );
expect( console ).toHaveErroredWith(
'The namespace must be a non-empty string.'
);
} );
test( 'cannot add filters with invalid namespaces', () => {
addFilter( 'hook_name', 'invalid_%&name', () => null );
expect( console ).toHaveErroredWith(
'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.'
);
} );
test( 'cannot add filters with namespaces starting with a slash', () => {
addFilter( 'hook_name', '/invalid_name', () => null );
expect( console ).toHaveErroredWith(
'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.'
);
} );
test( 'Can add filters with dashes in namespaces', () => {
addFilter( 'hook_name', 'with-dashes', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'Can add filters with capitals in namespaces', () => {
addFilter( 'hook_name', 'My_Name-OhNoaction', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'Can add filters with slashes in namespaces', () => {
addFilter( 'hook_name', 'my/name/action', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'Can add filters with periods in namespaces', () => {
addFilter( 'hook_name', 'my.name.action', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'Can add filters with capitals in hookName', () => {
addFilter( 'hookName', 'action', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'Can add filters with periods in hookName', () => {
addFilter( 'hook.name', 'action', () => null );
expect( console ).not.toHaveErrored();
} );
test( 'cannot add filters with namespace containing backslash', () => {
addFilter( 'hook_name', 'i\n\valid\name', () => null );
expect( console ).toHaveErroredWith(
'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.'
);
} );
test( 'cannot add filters named with __ prefix', () => {
addFilter( '__test', 'my_callback', () => null );
expect( console ).toHaveErroredWith(
'The hook name cannot begin with `__`.'
);
} );
test( 'cannot add filters with non-function callbacks', () => {
addFilter( 'test', 'my_callback', '42' );
expect( console ).toHaveErroredWith(
'The hook callback must be a function.'
);
} );
test( 'cannot add filters with non-numeric priorities', () => {
addFilter( 'test', 'my_callback', () => null, '42' );
expect( console ).toHaveErroredWith(
'If specified, the hook priority must be a number.'
);
} );
test( 'add 3 filters with different priorities and run them', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 8 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testbca' );
} );
test( 'filters with the same and different priorities', () => {
const callbacks = {};
[ 1, 2, 3, 4 ].forEach( ( priority ) => {
[ 'a', 'b', 'c', 'd' ].forEach( ( string ) => {
callbacks[ 'fn_' + priority + string ] = ( value ) => {
return value.concat( priority + string );
};
} );
} );
addFilter( 'test_order', 'my_callback_fn_3a', callbacks.fn_3a, 3 );
addFilter( 'test_order', 'my_callback_fn_3b', callbacks.fn_3b, 3 );
addFilter( 'test_order', 'my_callback_fn_3c', callbacks.fn_3c, 3 );
addFilter( 'test_order', 'my_callback_fn_2a', callbacks.fn_2a, 2 );
addFilter( 'test_order', 'my_callback_fn_2b', callbacks.fn_2b, 2 );
addFilter( 'test_order', 'my_callback_fn_2c', callbacks.fn_2c, 2 );
expect( applyFilters( 'test_order', [] ) ).toEqual( [
'2a',
'2b',
'2c',
'3a',
'3b',
'3c',
] );
removeFilter( 'test_order', 'my_callback_fn_2b', callbacks.fn_2b );
removeFilter( 'test_order', 'my_callback_fn_3a', callbacks.fn_3a );
expect( applyFilters( 'test_order', [] ) ).toEqual( [
'2a',
'2c',
'3b',
'3c',
] );
addFilter( 'test_order', 'my_callback_fn_4a', callbacks.fn_4a, 4 );
addFilter( 'test_order', 'my_callback_fn_4b', callbacks.fn_4b, 4 );
addFilter( 'test_order', 'my_callback_fn_1a', callbacks.fn_1a, 1 );
addFilter( 'test_order', 'my_callback_fn_4c', callbacks.fn_4c, 4 );
addFilter( 'test_order', 'my_callback_fn_1b', callbacks.fn_1b, 1 );
addFilter( 'test_order', 'my_callback_fn_3d', callbacks.fn_3d, 3 );
addFilter( 'test_order', 'my_callback_fn_4d', callbacks.fn_4d, 4 );
addFilter( 'test_order', 'my_callback_fn_1c', callbacks.fn_1c, 1 );
addFilter( 'test_order', 'my_callback_fn_2d', callbacks.fn_2d, 2 );
addFilter( 'test_order', 'my_callback_fn_1d', callbacks.fn_1d, 1 );
expect( applyFilters( 'test_order', [] ) ).toEqual( [
// All except 2b and 3a, which we removed earlier.
'1a',
'1b',
'1c',
'1d',
'2a',
'2c',
'2d',
'3b',
'3c',
'3d',
'4a',
'4b',
'4c',
'4d',
] );
} );
test( 'add and remove an action', () => {
addAction( 'test.action', 'my_callback', actionA );
expect( removeAllActions( 'test.action' ) ).toBe( 1 );
expect( doAction( 'test.action' ) ).toBe( undefined );
expect( window.actionValue ).toBe( '' );
} );
test( 'add an action and run it', () => {
addAction( 'test.action', 'my_callback', actionA );
doAction( 'test.action' );
expect( window.actionValue ).toBe( 'a' );
} );
test( 'add 2 actions in a row and then run them', () => {
addAction( 'test.action', 'my_callback', actionA );
addAction( 'test.action', 'my_callback', actionB );
doAction( 'test.action' );
expect( window.actionValue ).toBe( 'ab' );
} );
test( 'add 3 actions with different priorities and run them', () => {
addAction( 'test.action', 'my_callback', actionA );
addAction( 'test.action', 'my_callback', actionB, 2 );
addAction( 'test.action', 'my_callback', actionC, 8 );
doAction( 'test.action' );
expect( window.actionValue ).toBe( 'bca' );
} );
test( 'pass in two arguments to an action', () => {
const arg1 = { a: 10 };
const arg2 = { b: 20 };
addAction( 'test.action', 'my_callback', ( a, b ) => {
expect( a ).toBe( arg1 );
expect( b ).toBe( arg2 );
} );
doAction( 'test.action', arg1, arg2 );
} );
test( 'fire action multiple times', () => {
expect.assertions( 2 );
function func() {
expect( true ).toBe( true );
}
addAction( 'test.action', 'my_callback', func );
doAction( 'test.action' );
doAction( 'test.action' );
} );
test( 'add a filter before the one currently executing', () => {
addFilter(
'test.filter',
'my_callback',
( outerValue ) => {
addFilter(
'test.filter',
'my_callback',
( innerValue ) => innerValue + 'a',
1
);
return outerValue + 'b';
},
2
);
expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_b' );
} );
test( 'add a filter after the one currently executing', () => {
addFilter(
'test.filter',
'my_callback',
( outerValue ) => {
addFilter(
'test.filter',
'my_callback',
( innerValue ) => innerValue + 'b',
2
);
return outerValue + 'a';
},
1
);
expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_ab' );
} );
test( 'add a filter immediately after the one currently executing', () => {
addFilter(
'test.filter',
'my_callback',
( outerValue ) => {
addFilter(
'test.filter',
'my_callback',
( innerValue ) => innerValue + 'b',
1
);
return outerValue + 'a';
},
1
);
expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_ab' );
} );
test( 'remove specific action callback', () => {
addAction( 'test.action', 'my_callback_action_a', actionA );
addAction( 'test.action', 'my_callback_action_b', actionB, 2 );
addAction( 'test.action', 'my_callback_action_c', actionC, 8 );
expect( removeAction( 'test.action', 'my_callback_action_b' ) ).toBe( 1 );
doAction( 'test.action' );
expect( window.actionValue ).toBe( 'ca' );
} );
test( 'remove all action callbacks', () => {
addAction( 'test.action', 'my_callback_action_a', actionA );
addAction( 'test.action', 'my_callback_action_b', actionB, 2 );
addAction( 'test.action', 'my_callback_action_c', actionC, 8 );
expect( removeAllActions( 'test.action' ) ).toBe( 3 );
doAction( 'test.action' );
expect( window.actionValue ).toBe( '' );
} );
test( 'remove specific filter callback', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 8 );
expect( removeFilter( 'test.filter', 'my_callback_filter_b' ) ).toBe( 1 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testca' );
} );
test( 'filter removes a callback that has already executed', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 );
addFilter(
'test.filter',
'my_callback_filter_removes_b',
filterRemovesB,
4
);
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' );
} );
test( 'filter removes a callback that has already executed (same priority)', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 );
addFilter(
'test.filter',
'my_callback_filter_removes_b',
filterRemovesB,
2
);
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' );
} );
test( 'filter removes the current callback', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter(
'test.filter',
'my_callback_filter_c_removes_self',
filterCRemovesSelf,
3
);
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' );
} );
test( 'filter removes a callback that has not yet executed (last)', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 );
addFilter(
'test.filter',
'my_callback_filter_removes_c',
filterRemovesC,
4
);
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testab' );
} );
test( 'filter removes a callback that has not yet executed (middle)', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 );
addFilter(
'test.filter',
'my_callback_filter_removes_b',
filterRemovesB,
2
);
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testac' );
} );
test( 'filter removes a callback that has not yet executed (same priority)', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 );
addFilter(
'test.filter',
'my_callback_filter_removes_b',
filterRemovesB,
2
);
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testac' );
} );
test( 'remove all filter callbacks', () => {
addFilter( 'test.filter', 'my_callback_filter_a', filterA );
addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 );
addFilter( 'test.filter', 'my_callback_filter_c', filterC, 8 );
expect( removeAllFilters( 'test.filter' ) ).toBe( 3 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'test' );
} );
// Test doingAction, didAction, hasAction.
test( 'doingAction, didAction and hasAction.', () => {
let actionCalls = 0;
addAction( 'another.action', 'my_callback', () => {} );
doAction( 'another.action' );
// Verify no action is running yet.
expect( doingAction( 'test.action' ) ).toBe( false );
expect( didAction( 'test.action' ) ).toBe( 0 );
expect( hasAction( 'test.action' ) ).toBe( false );
addAction( 'test.action', 'my_callback', () => {
actionCalls++;
expect( currentAction() ).toBe( 'test.action' );
expect( doingAction() ).toBe( true );
expect( doingAction( 'test.action' ) ).toBe( true );
} );
// Verify action added, not running yet.
expect( doingAction( 'test.action' ) ).toBe( false );
expect( didAction( 'test.action' ) ).toBe( 0 );
expect( hasAction( 'test.action' ) ).toBe( true );
doAction( 'test.action' );
// Verify action added and running.
expect( actionCalls ).toBe( 1 );
expect( doingAction( 'test.action' ) ).toBe( false );
expect( didAction( 'test.action' ) ).toBe( 1 );
expect( hasAction( 'test.action' ) ).toBe( true );
expect( doingAction() ).toBe( false );
expect( doingAction( 'test.action' ) ).toBe( false );
expect( doingAction( 'notatest.action' ) ).toBe( false );
expect( currentAction() ).toBe( null );
doAction( 'test.action' );
expect( actionCalls ).toBe( 2 );
expect( didAction( 'test.action' ) ).toBe( 2 );
expect( removeAllActions( 'test.action' ) ).toBe( 1 );
// Verify state is reset appropriately.
expect( doingAction( 'test.action' ) ).toBe( false );
expect( didAction( 'test.action' ) ).toBe( 2 );
expect( hasAction( 'test.action' ) ).toBe( true );
doAction( 'another.action' );
expect( doingAction( 'test.action' ) ).toBe( false );
// Verify an action with no handlers is still counted.
expect( didAction( 'unattached.action' ) ).toBe( 0 );
doAction( 'unattached.action' );
expect( doingAction( 'unattached.action' ) ).toBe( false );
expect( didAction( 'unattached.action' ) ).toBe( 1 );
doAction( 'unattached.action' );
expect( doingAction( 'unattached.action' ) ).toBe( false );
expect( didAction( 'unattached.action' ) ).toBe( 2 );
// Verify hasAction returns 0 when no matching action.
expect( hasAction( 'notatest.action' ) ).toBe( false );
} );
test( 'Verify doingFilter, didFilter and hasFilter.', () => {
let filterCalls = 0;
addFilter( 'runtest.filter', 'my_callback', ( arg ) => {
filterCalls++;
expect( currentFilter() ).toBe( 'runtest.filter' );
expect( doingFilter() ).toBe( true );
expect( doingFilter( 'runtest.filter' ) ).toBe( true );
return arg;
} );
// Verify filter added and running.
const test = applyFilters( 'runtest.filter', 'someValue' );
expect( test ).toBe( 'someValue' );
expect( filterCalls ).toBe( 1 );
expect( didFilter( 'runtest.filter' ) ).toBe( 1 );
expect( hasFilter( 'runtest.filter' ) ).toBe( true );
expect( hasFilter( 'notatest.filter' ) ).toBe( false );
expect( doingFilter() ).toBe( false );
expect( doingFilter( 'runtest.filter' ) ).toBe( false );
expect( doingFilter( 'notatest.filter' ) ).toBe( false );
expect( currentFilter() ).toBe( null );
expect( removeAllFilters( 'runtest.filter' ) ).toBe( 1 );
expect( hasFilter( 'runtest.filter' ) ).toBe( true );
expect( didFilter( 'runtest.filter' ) ).toBe( 1 );
} );
test( 'recursively calling a filter', () => {
addFilter( 'test.filter', 'my_callback', ( value ) => {
if ( value.length === 7 ) {
return value;
}
return applyFilters( 'test.filter', value + 'X' );
} );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testXXX' );
} );
test( 'current filter when multiple filters are running', () => {
addFilter( 'test.filter1', 'my_callback', ( value ) => {
return applyFilters( 'test.filter2', value.concat( currentFilter() ) );
} );
addFilter( 'test.filter2', 'my_callback', ( value ) => {
return value.concat( currentFilter() );
} );
expect( currentFilter() ).toBe( null );
expect( applyFilters( 'test.filter1', [ 'test' ] ) ).toEqual( [
'test',
'test.filter1',
'test.filter2',
] );
expect( currentFilter() ).toBe( null );
} );
test( 'adding and removing filters with recursion', () => {
function removeRecurseAndAdd2( val ) {
expect( removeFilter( 'remove_and_add', 'my_callback_recurse' ) ).toBe(
1
);
val += '-' + applyFilters( 'remove_and_add', '' ) + '-';
addFilter(
'remove_and_add',
'my_callback_recurse',
removeRecurseAndAdd2,
10
);
return val + '2';
}
addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '1', 11 );
addFilter(
'remove_and_add',
'my_callback_recurse',
removeRecurseAndAdd2,
12
);
addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '3', 13 );
addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '4', 14 );
expect( applyFilters( 'remove_and_add', '' ) ).toBe( '1-134-234' );
} );
test( 'actions preserve arguments across handlers without return value', () => {
const arg1 = { a: 10 };
const arg2 = { b: 20 };
addAction( 'test.action', 'my_callback1', ( a, b ) => {
expect( a ).toBe( arg1 );
expect( b ).toBe( arg2 );
} );
addAction( 'test.action', 'my_callback2', ( a, b ) => {
expect( a ).toBe( arg1 );
expect( b ).toBe( arg2 );
} );
doAction( 'test.action', arg1, arg2 );
} );
test( 'filters pass first argument across handlers', () => {
addFilter( 'test.filter', 'my_callback1', ( count ) => count + 1 );
addFilter( 'test.filter', 'my_callback2', ( count ) => count + 1 );
const result = applyFilters( 'test.filter', 0 );
expect( result ).toBe( 2 );
} );
// Test adding via composition.
test( 'adding hooks via composition', () => {
const testObject = {};
testObject.hooks = createHooks();
expect( typeof testObject.hooks.applyFilters ).toBe( 'function' );
} );
// Test adding as a mixin.
test( 'adding hooks as a mixin', () => {
const testObject = {};
Object.assign( testObject, createHooks() );
expect( typeof testObject.applyFilters ).toBe( 'function' );
} );
// Test context.
test( '`this` context via composition', () => {
const testObject = { test: 'test this' };
testObject.hooks = createHooks();
const theCallback = function () {
expect( this.test ).toBe( 'test this' );
};
addAction( 'test.action', 'my_callback', theCallback.bind( testObject ) );
doAction( 'test.action' );
const testObject2 = {};
Object.assign( testObject2, createHooks() );
} );
const setupActionListener = ( hookName, callback ) =>
addAction( hookName, 'my_callback', callback );
test( 'adding an action triggers a hookAdded action passing all callback details', () => {
const hookAddedSpy = jest.fn();
setupActionListener( 'hookAdded', hookAddedSpy );
addAction( 'testAction', 'my_callback2', actionA, 9 );
expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookAddedSpy ).toHaveBeenCalledWith(
'testAction',
'my_callback2',
actionA,
9
);
// Private instance.
const hooksPrivateInstance = createHooks();
removeAction( 'hookAdded', 'my_callback' );
hookAddedSpy.mockClear();
hooksPrivateInstance.addAction( 'hookAdded', 'my_callback', hookAddedSpy );
hooksPrivateInstance.addAction( 'testAction', 'my_callback2', actionA, 9 );
expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookAddedSpy ).toHaveBeenCalledWith(
'testAction',
'my_callback2',
actionA,
9
);
} );
test( 'adding a filter triggers a hookAdded action passing all callback details', () => {
const hookAddedSpy = jest.fn();
setupActionListener( 'hookAdded', hookAddedSpy );
addFilter( 'testFilter', 'my_callback3', filterA, 8 );
expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookAddedSpy ).toHaveBeenCalledWith(
'testFilter',
'my_callback3',
filterA,
8
);
// Private instance.
const hooksPrivateInstance = createHooks();
removeAction( 'hookAdded', 'my_callback' );
hookAddedSpy.mockClear();
hooksPrivateInstance.addAction( 'hookAdded', 'my_callback', hookAddedSpy );
hooksPrivateInstance.addFilter( 'testFilter', 'my_callback3', filterA, 8 );
expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookAddedSpy ).toHaveBeenCalledWith(
'testFilter',
'my_callback3',
filterA,
8
);
} );
test( 'removing an action triggers a hookRemoved action passing all callback details', () => {
const hookRemovedSpy = jest.fn();
setupActionListener( 'hookRemoved', hookRemovedSpy );
addAction( 'testAction', 'my_callback2', actionA, 9 );
removeAction( 'testAction', 'my_callback2' );
expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookRemovedSpy ).toHaveBeenCalledWith(
'testAction',
'my_callback2'
);
// Private instance.
const hooksPrivateInstance = createHooks();
removeAction( 'hookRemoved', 'my_callback' );
hookRemovedSpy.mockClear();
hooksPrivateInstance.addAction(
'hookRemoved',
'my_callback',
hookRemovedSpy
);
hooksPrivateInstance.addAction( 'testAction', 'my_callback2', actionA, 9 );
hooksPrivateInstance.removeAction( 'testAction', 'my_callback2' );
expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookRemovedSpy ).toHaveBeenCalledWith(
'testAction',
'my_callback2'
);
} );
test( 'removing a filter triggers a hookRemoved action passing all callback details', () => {
const hookRemovedSpy = jest.fn();
setupActionListener( 'hookRemoved', hookRemovedSpy );
addFilter( 'testFilter', 'my_callback3', filterA, 8 );
removeFilter( 'testFilter', 'my_callback3' );
expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookRemovedSpy ).toHaveBeenCalledWith(
'testFilter',
'my_callback3'
);
// Private instance.
const hooksPrivateInstance = createHooks();
removeAction( 'hookRemoved', 'my_callback' );
hookRemovedSpy.mockClear();
hooksPrivateInstance.addAction(
'hookRemoved',
'my_callback',
hookRemovedSpy
);
hooksPrivateInstance.addFilter( 'testFilter', 'my_callback3', filterA, 8 );
hooksPrivateInstance.removeFilter( 'testFilter', 'my_callback3' );
expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 );
expect( hookRemovedSpy ).toHaveBeenCalledWith(
'testFilter',
'my_callback3'
);
} );
test( 'add an all filter and run it any hook to trigger it', () => {
addFilter( 'all', 'my_callback', filterA );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testa' );
expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testa' );
} );
test( 'add an all action and run it any hook to trigger it', () => {
addAction( 'all', 'my_callback', actionA );
addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered.
doAction( 'test.action-anything' );
expect( window.actionValue ).toBe( 'a' );
} );
test( 'add multiple all filters and run it any hook to trigger them', () => {
addFilter( 'all', 'my_callback', filterA );
addFilter( 'all', 'my_callback', filterB );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testab' );
expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testab' );
} );
test( 'add multiple all actions and run it any hook to trigger them', () => {
addAction( 'all', 'my_callback', actionA );
addAction( 'all', 'my_callback', actionB );
addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered.
doAction( 'test.action-anything' );
expect( window.actionValue ).toBe( 'ab' );
} );
test( 'add multiple all filters and run it any hook to trigger them by priority', () => {
addFilter( 'all', 'my_callback', filterA, 11 );
addFilter( 'all', 'my_callback', filterB, 10 );
expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testba' );
expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testba' );
} );
test( 'add multiple all actions and run it any hook to trigger them by priority', () => {
addAction( 'all', 'my_callback', actionA, 11 );
addAction( 'all', 'my_callback', actionB, 10 );
addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered.
doAction( 'test.action-anything' );
expect( window.actionValue ).toBe( 'ba' );
} );
test( 'checking hasAction with named callbacks and removing', () => {
addAction( 'test.action', 'my_callback', () => {} );
expect( hasAction( 'test.action', 'not_my_callback' ) ).toBe( false );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( true );
removeAction( 'test.action', 'my_callback' );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( false );
} );
test( 'checking hasAction with named callbacks and removeAllActions', () => {
addAction( 'test.action', 'my_callback', () => {} );
addAction( 'test.action', 'my_second_callback', () => {} );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( true );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( true );
removeAllActions( 'test.action' );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( false );
expect( hasAction( 'test.action', 'my_callback' ) ).toBe( false );
} );
test( 'checking hasFilter with named callbacks and removing', () => {
addFilter( 'test.filter', 'my_callback', () => {} );
expect( hasFilter( 'test.filter', 'not_my_callback' ) ).toBe( false );
expect( hasFilter( 'test.filter', 'my_callback' ) ).toBe( true );
removeFilter( 'test.filter', 'my_callback' );
expect( hasFilter( 'test.filter', 'my_callback' ) ).toBe( false );
} );
test( 'checking hasFilter with named callbacks and removeAllActions', () => {
addFilter( 'test.filter', 'my_callback', () => {} );
addFilter( 'test.filter', 'my_second_callback', () => {} );
expect( hasFilter( 'test.filter', 'my_callback' ) ).toBe( true );
expect( hasFilter( 'test.filter', 'my_second_callback' ) ).toBe( true );
removeAllFilters( 'test.filter' );
expect( hasFilter( 'test.filter', 'my_callback' ) ).toBe( false );
expect( hasFilter( 'test.filter', 'my_second_callback' ) ).toBe( false );
} );

34
node_modules/@wordpress/hooks/src/validateHookName.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
/**
* Validate a hookName string.
*
* @param {string} hookName The hook name to validate. Should be a non empty string containing
* only numbers, letters, dashes, periods and underscores. Also,
* the hook name cannot begin with `__`.
*
* @return {boolean} Whether the hook name is valid.
*/
function validateHookName( hookName ) {
if ( 'string' !== typeof hookName || '' === hookName ) {
// eslint-disable-next-line no-console
console.error( 'The hook name must be a non-empty string.' );
return false;
}
if ( /^__/.test( hookName ) ) {
// eslint-disable-next-line no-console
console.error( 'The hook name cannot begin with `__`.' );
return false;
}
if ( ! /^[a-zA-Z][a-zA-Z0-9_.-]*$/.test( hookName ) ) {
// eslint-disable-next-line no-console
console.error(
'The hook name can only contain numbers, letters, dashes, periods and underscores.'
);
return false;
}
return true;
}
export default validateHookName;

27
node_modules/@wordpress/hooks/src/validateNamespace.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/**
* Validate a namespace string.
*
* @param {string} namespace The namespace to validate - should take the form
* `vendor/plugin/function`.
*
* @return {boolean} Whether the namespace is valid.
*/
function validateNamespace( namespace ) {
if ( 'string' !== typeof namespace || '' === namespace ) {
// eslint-disable-next-line no-console
console.error( 'The namespace must be a non-empty string.' );
return false;
}
if ( ! /^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test( namespace ) ) {
// eslint-disable-next-line no-console
console.error(
'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.'
);
return false;
}
return true;
}
export default validateNamespace;