Skip to content

Commit 3436b13

Browse files
authored
Merge pull request #17 from akshat009/test/init-hooks-contract
test(templates): uniform init_hooks() contract test across all 22 module classes (D)
2 parents 4dffd80 + e07281b commit 3436b13

3 files changed

Lines changed: 369 additions & 0 deletions

File tree

‎index.js‎

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,10 +1060,82 @@ function scaffoldInto(answers, targetDir) {
10601060
].join('\n'));
10611061
}
10621062

1063+
// One data-provider case per module class that implements init_hooks(),
1064+
// for the generated Module_Hooks_Test.php ({{MODULE_HOOK_CASES}}) --
1065+
// verifies each module actually registers the hooks it claims to,
1066+
// instead of only ever being constructed inside a single-assertion test
1067+
// for one of its other methods. Collected as structured data rather than
1068+
// pre-rendered PHP text, because WordPress-Extra requires every `=>` in
1069+
// this array to align to its widest key -- unknowable until every module
1070+
// block below has run, so rendering is deferred to renderModuleHookCases().
1071+
const moduleHookCases = [];
1072+
1073+
/**
1074+
* Register one module's init_hooks() contract.
1075+
*
1076+
* @param {string} short PHP class path relative to {{NS}} (e.g.
1077+
* 'Admin\\Assets') -- also the data-provider key, unique since each
1078+
* module contributes at most one entry.
1079+
* @param {string} ctorExpr A `new \{{NS}}\...( ... )` PHP expression,
1080+
* wrapped in a factory closure so a constructor-injected service can be
1081+
* a fresh Mockery double built when the test runs, not when this
1082+
* provider is collected.
1083+
* @param {{type: 'action'|'filter'|'shortcode'|'cli_command', hook: string, raw?: boolean}[]} hooks
1084+
* The hooks init_hooks() is expected to register. `hook` is emitted as
1085+
* a single-quoted PHP string unless `raw` is set, for the two hooks
1086+
* built from a class constant at runtime rather than a literal.
1087+
*/
1088+
function addModuleHookCase(short, ctorExpr, hooks) {
1089+
moduleHookCases.push({ short, ctorExpr, hooks });
1090+
}
1091+
1092+
/**
1093+
* Render the collected module-hook cases as one `module_hook_provider()`
1094+
* array body ({{MODULE_HOOK_CASES}}), WPCS-formatted: every top-level
1095+
* `=>` aligned to the widest data-provider key (WordPress.Arrays.
1096+
* MultipleStatementAlignment), and every multi-item associative array --
1097+
* the provider entries themselves, and each hook's { type, hook } pair --
1098+
* one key per line (WordPress.Arrays.ArrayDeclarationSpacing).
1099+
*
1100+
* @param {{short: string, ctorExpr: string, hooks: {type: string, hook: string, raw?: boolean}[]}[]} cases
1101+
* @return {string}
1102+
*/
1103+
function renderModuleHookCases(cases) {
1104+
const maxKeyLen = Math.max(...cases.map(({ short }) => `'${short}'`.length));
1105+
return cases
1106+
.map(({ short, ctorExpr, hooks }) => {
1107+
const key = `'${short}'`.padEnd(maxKeyLen, ' ');
1108+
const phpHooks = hooks
1109+
.map(({ type, hook, raw }) => {
1110+
const hookExpr = raw ? hook : `'${hook.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
1111+
return [
1112+
'\t\t\t\t\tarray(',
1113+
`\t\t\t\t\t\t'type' => '${type}',`,
1114+
`\t\t\t\t\t\t'hook' => ${hookExpr},`,
1115+
'\t\t\t\t\t),',
1116+
].join('\n');
1117+
})
1118+
.join('\n');
1119+
return [
1120+
`\t\t\t${key} => array(`,
1121+
`\t\t\t\tstatic fn () => ${ctorExpr},`,
1122+
'\t\t\t\tarray(',
1123+
phpHooks,
1124+
'\t\t\t\t),',
1125+
'\t\t\t),',
1126+
].join('\n');
1127+
})
1128+
.join('\n');
1129+
}
1130+
10631131
if (selectedModules.includes('cli')) {
10641132
writeTemplateFile(path.join(templatesDir, 'src/CLI/Commands.php'), 'src/CLI/Commands.php');
10651133
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Commands_Test.php'), 'tests/Unit/Commands_Test.php');
10661134
bootLines.push("\t\tif ( defined( 'WP_CLI' ) && WP_CLI ) {\n\t\t\t( new CLI\\Commands() )->init_hooks();\n\t\t}");
1135+
addModuleHookCase('CLI\\Commands', 'new \\{{NS}}\\CLI\\Commands()', [
1136+
{ type: 'cli_command', hook: '{{PREFIX}} status' },
1137+
{ type: 'cli_command', hook: '{{PREFIX}} cache clear' },
1138+
]);
10671139
}
10681140
if (selectedModules.includes('admin_settings')) {
10691141
writeTemplateFile(path.join(templatesDir, 'src/Admin/Settings_Repository.php'), 'src/Admin/Settings_Repository.php');
@@ -1075,16 +1147,30 @@ function scaffoldInto(answers, targetDir) {
10751147

10761148
bootLines.push('\t\t( new Admin\\Settings_Registrar( Services::settings_repository() ) )->init_hooks();');
10771149
addService('settings_repository', 'Admin\\Settings_Repository');
1150+
addModuleHookCase(
1151+
'Admin\\Settings_Registrar',
1152+
'new \\{{NS}}\\Admin\\Settings_Registrar( \\Mockery::mock( \\{{NS}}\\Admin\\Settings_Repository::class ) )',
1153+
[
1154+
{ type: 'action', hook: 'admin_menu' },
1155+
{ type: 'action', hook: 'admin_init' },
1156+
]
1157+
);
10781158
}
10791159
if (selectedModules.includes('shortcode')) {
10801160
writeTemplateFile(path.join(templatesDir, 'src/Frontend/Shortcode.php'), 'src/Frontend/Shortcode.php');
10811161
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Shortcode_Test.php'), 'tests/Unit/Shortcode_Test.php');
10821162
bootLines.push('\t\t( new Frontend\\Shortcode() )->init_hooks();');
1163+
addModuleHookCase('Frontend\\Shortcode', 'new \\{{NS}}\\Frontend\\Shortcode()', [
1164+
{ type: 'shortcode', hook: '{{PREFIX}}_display' },
1165+
]);
10831166
}
10841167
if (selectedModules.includes('rest_api')) {
10851168
writeTemplateFile(path.join(templatesDir, 'src/Rest/Rest_Controller.php'), 'src/Rest/Rest_Controller.php');
10861169
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Rest_Controller_Test.php'), 'tests/Unit/Rest_Controller_Test.php');
10871170
bootLines.push('\t\t( new Rest\\Rest_Controller() )->init_hooks();');
1171+
addModuleHookCase('Rest\\Rest_Controller', 'new \\{{NS}}\\Rest\\Rest_Controller()', [
1172+
{ type: 'action', hook: 'rest_api_init' },
1173+
]);
10881174
}
10891175
if (selectedModules.includes('ajax_handler')) {
10901176
writeTemplateFile(path.join(templatesDir, 'src/Ajax/Ajax_Handler.php'), 'src/Ajax/Ajax_Handler.php');
@@ -1094,16 +1180,28 @@ function scaffoldInto(answers, targetDir) {
10941180
// file rides along with the module instead of the baseline.
10951181
writeTemplateFile(path.join(templatesDir, 'assets/js/main.js'), 'assets/js/main.js');
10961182
bootLines.push('\t\t( new Ajax\\Ajax_Handler() )->init_hooks();');
1183+
// allow_nopriv defaults to false, so the wp_ajax_nopriv_ variant isn't
1184+
// registered on a bare `new Ajax_Handler()` -- only the two below are.
1185+
addModuleHookCase('Ajax\\Ajax_Handler', 'new \\{{NS}}\\Ajax\\Ajax_Handler()', [
1186+
{ type: 'action', hook: 'wp_ajax_{{PREFIX}}_action' },
1187+
{ type: 'action', hook: 'wp_enqueue_scripts' },
1188+
]);
10971189
}
10981190
if (selectedModules.includes('cpt_taxonomy')) {
10991191
writeTemplateFile(path.join(templatesDir, 'src/PostTypes/Post_Types.php'), 'src/PostTypes/Post_Types.php');
11001192
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Post_Types_Test.php'), 'tests/Unit/Post_Types_Test.php');
11011193
bootLines.push('\t\t( new PostTypes\\Post_Types() )->init_hooks();');
1194+
addModuleHookCase('PostTypes\\Post_Types', 'new \\{{NS}}\\PostTypes\\Post_Types()', [
1195+
{ type: 'action', hook: 'init' },
1196+
]);
11021197
}
11031198
if (selectedModules.includes('cron')) {
11041199
writeTemplateFile(path.join(templatesDir, 'src/Cron/Scheduler.php'), 'src/Cron/Scheduler.php');
11051200
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Scheduler_Test.php'), 'tests/Unit/Scheduler_Test.php');
11061201
bootLines.push('\t\t( new Cron\\Scheduler() )->init_hooks();');
1202+
addModuleHookCase('Cron\\Scheduler', 'new \\{{NS}}\\Cron\\Scheduler()', [
1203+
{ type: 'action', hook: '{{PREFIX}}_cron_event' },
1204+
]);
11071205
}
11081206
if (selectedModules.includes('caching')) {
11091207
writeTemplateFile(path.join(templatesDir, 'src/Cache/Cache_Service.php'), 'src/Cache/Cache_Service.php');
@@ -1117,6 +1215,9 @@ function scaffoldInto(answers, targetDir) {
11171215
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Item_Repository_Test.php'), 'tests/Unit/Item_Repository_Test.php');
11181216
bootLines.push('\t\t( new Database\\Schema() )->init_hooks();');
11191217
addService('item_repository', 'Database\\Item_Repository');
1218+
addModuleHookCase('Database\\Schema', 'new \\{{NS}}\\Database\\Schema()', [
1219+
{ type: 'action', hook: 'plugins_loaded' },
1220+
]);
11201221
}
11211222
if (selectedModules.includes('elementor_widget')) {
11221223
if (selectedModules.includes('editor_config')) {
@@ -1130,6 +1231,15 @@ function scaffoldInto(answers, targetDir) {
11301231
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Widget_Registrar_Test.php'), 'tests/Unit/Widget_Registrar_Test.php');
11311232
bootLines.push('\t\t( new Elementor\\Dependency_Notice() )->init_hooks();');
11321233
bootLines.push('\t\t( new Elementor\\Widget_Registrar() )->init_hooks();');
1234+
addModuleHookCase('Elementor\\Dependency_Notice', 'new \\{{NS}}\\Elementor\\Dependency_Notice()', [
1235+
{ type: 'action', hook: 'admin_notices' },
1236+
]);
1237+
addModuleHookCase('Elementor\\Widget_Registrar', 'new \\{{NS}}\\Elementor\\Widget_Registrar()', [
1238+
{ type: 'filter', hook: '{{PREFIX}}_cache_keys' },
1239+
{ type: 'action', hook: 'wp_enqueue_scripts' },
1240+
{ type: 'action', hook: 'elementor/editor/after_enqueue_styles' },
1241+
{ type: 'action', hook: 'elementor/widgets/register' },
1242+
]);
11331243
}
11341244
if (hasWooGateway) {
11351245
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Gateway_Provider.php'), 'src/Woo/Providers/Gateway_Provider.php');
@@ -1138,12 +1248,19 @@ function scaffoldInto(answers, targetDir) {
11381248
writeTemplateFile(path.join(templatesDir, 'react/assets/src/wc-gateway-block.js'), 'assets/src/wc-gateway-block.js');
11391249
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Gateway_Test.php'), 'tests/Unit/Gateway_Test.php');
11401250
wooBootLines.push('\t\t\t( new Woo\\Providers\\Gateway_Provider() )->init_hooks();');
1251+
addModuleHookCase('Woo\\Providers\\Gateway_Provider', 'new \\{{NS}}\\Woo\\Providers\\Gateway_Provider()', [
1252+
{ type: 'filter', hook: 'woocommerce_payment_gateways' },
1253+
{ type: 'action', hook: 'woocommerce_blocks_loaded' },
1254+
]);
11411255
}
11421256
if (hasWooShipping) {
11431257
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Shipping_Provider.php'), 'src/Woo/Providers/Shipping_Provider.php');
11441258
writeTemplateFile(path.join(templatesDir, 'src/Woo/Shipping/Shipping_Method.php'), 'src/Woo/Shipping/Shipping_Method.php');
11451259
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Shipping_Method_Test.php'), 'tests/Unit/Shipping_Method_Test.php');
11461260
wooBootLines.push('\t\t\t( new Woo\\Providers\\Shipping_Provider() )->init_hooks();');
1261+
addModuleHookCase('Woo\\Providers\\Shipping_Provider', 'new \\{{NS}}\\Woo\\Providers\\Shipping_Provider()', [
1262+
{ type: 'filter', hook: 'woocommerce_shipping_methods' },
1263+
]);
11471264
}
11481265
if (hasWooEmail) {
11491266
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Email_Provider.php'), 'src/Woo/Providers/Email_Provider.php');
@@ -1152,12 +1269,20 @@ function scaffoldInto(answers, targetDir) {
11521269
writeTemplateFile(path.join(templatesDir, 'woo-email-templates/emails/plain/custom-email.php'), `templates/emails/plain/${answers.prefix.toLowerCase()}-custom-email.php`);
11531270
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Custom_Email_Test.php'), 'tests/Unit/Custom_Email_Test.php');
11541271
wooBootLines.push('\t\t\t( new Woo\\Providers\\Email_Provider() )->init_hooks();');
1272+
addModuleHookCase('Woo\\Providers\\Email_Provider', 'new \\{{NS}}\\Woo\\Providers\\Email_Provider()', [
1273+
{ type: 'filter', hook: 'woocommerce_email_classes' },
1274+
]);
11551275
}
11561276
if (hasWooProductType) {
11571277
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Product_Type_Provider.php'), 'src/Woo/Providers/Product_Type_Provider.php');
11581278
writeTemplateFile(path.join(templatesDir, 'src/Woo/Products/Custom_Product.php'), 'src/Woo/Products/Custom_Product.php');
11591279
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Custom_Product_Test.php'), 'tests/Unit/Custom_Product_Test.php');
11601280
wooBootLines.push('\t\t\t( new Woo\\Providers\\Product_Type_Provider() )->init_hooks();');
1281+
addModuleHookCase('Woo\\Providers\\Product_Type_Provider', 'new \\{{NS}}\\Woo\\Providers\\Product_Type_Provider()', [
1282+
{ type: 'filter', hook: 'woocommerce_product_class' },
1283+
{ type: 'filter', hook: 'product_type_selector' },
1284+
{ type: 'action', hook: 'woocommerce_single_product_summary' },
1285+
]);
11611286
}
11621287
if (hasWooBlocks) {
11631288
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Blocks_Provider.php'), 'src/Woo/Providers/Blocks_Provider.php');
@@ -1169,27 +1294,54 @@ function scaffoldInto(answers, targetDir) {
11691294
writeTemplateFile(path.join(templatesDir, 'react/assets/src/blocks/cart-summary/render.php'), 'assets/src/blocks/cart-summary/render.php');
11701295
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Cart_Summary_Block_Test.php'), 'tests/Unit/Cart_Summary_Block_Test.php');
11711296
wooBootLines.push('\t\t\t( new Woo\\Providers\\Blocks_Provider() )->init_hooks();');
1297+
addModuleHookCase('Woo\\Providers\\Blocks_Provider', 'new \\{{NS}}\\Woo\\Providers\\Blocks_Provider()', [
1298+
{ type: 'action', hook: 'init' },
1299+
{ type: 'action', hook: 'woocommerce_blocks_loaded' },
1300+
]);
11721301
}
11731302
if (hasWooOrderStatus) {
11741303
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Order_Status_Provider.php'), 'src/Woo/Providers/Order_Status_Provider.php');
11751304
writeTemplateFile(path.join(templatesDir, 'src/Woo/Orders/Order_Status_Service.php'), 'src/Woo/Orders/Order_Status_Service.php');
11761305
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Order_Status_Service_Test.php'), 'tests/Unit/Order_Status_Service_Test.php');
11771306
wooBootLines.push('\t\t\t( new Woo\\Providers\\Order_Status_Provider( Services::order_status_service() ) )->init_hooks();');
11781307
addService('order_status_service', 'Woo\\Orders\\Order_Status_Service');
1308+
addModuleHookCase(
1309+
'Woo\\Providers\\Order_Status_Provider',
1310+
'new \\{{NS}}\\Woo\\Providers\\Order_Status_Provider( \\Mockery::mock( \\{{NS}}\\Woo\\Orders\\Order_Status_Service::class ) )',
1311+
[
1312+
{ type: 'action', hook: 'init' },
1313+
{ type: 'filter', hook: 'wc_order_statuses' },
1314+
]
1315+
);
11791316
}
11801317
if (hasWooActionScheduler) {
11811318
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Action_Scheduler_Provider.php'), 'src/Woo/Providers/Action_Scheduler_Provider.php');
11821319
writeTemplateFile(path.join(templatesDir, 'src/Woo/Tasks/Action_Scheduler_Service.php'), 'src/Woo/Tasks/Action_Scheduler_Service.php');
11831320
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Action_Scheduler_Service_Test.php'), 'tests/Unit/Action_Scheduler_Service_Test.php');
11841321
wooBootLines.push('\t\t\t( new Woo\\Providers\\Action_Scheduler_Provider( Services::action_scheduler_service() ) )->init_hooks();');
11851322
addService('action_scheduler_service', 'Woo\\Tasks\\Action_Scheduler_Service');
1323+
addModuleHookCase(
1324+
'Woo\\Providers\\Action_Scheduler_Provider',
1325+
'new \\{{NS}}\\Woo\\Providers\\Action_Scheduler_Provider( \\Mockery::mock( \\{{NS}}\\Woo\\Tasks\\Action_Scheduler_Service::class ) )',
1326+
[
1327+
{ type: 'action', hook: 'init' },
1328+
// A class constant, not a literal -- built the same way at
1329+
// test-run time instead of duplicating its value here.
1330+
{ type: 'action', hook: '\\{{NS}}\\Woo\\Tasks\\Action_Scheduler_Service::HOOK', raw: true },
1331+
]
1332+
);
11861333
}
11871334
if (hasWooStoreApi) {
11881335
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Store_Api_Provider.php'), 'src/Woo/Providers/Store_Api_Provider.php');
11891336
writeTemplateFile(path.join(templatesDir, 'src/Woo/Api/Store_Api_Extension.php'), 'src/Woo/Api/Store_Api_Extension.php');
11901337
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Store_Api_Extension_Test.php'), 'tests/Unit/Store_Api_Extension_Test.php');
11911338
wooBootLines.push('\t\t\t( new Woo\\Providers\\Store_Api_Provider( Services::store_api_extension() ) )->init_hooks();');
11921339
addService('store_api_extension', 'Woo\\Api\\Store_Api_Extension');
1340+
addModuleHookCase(
1341+
'Woo\\Providers\\Store_Api_Provider',
1342+
'new \\{{NS}}\\Woo\\Providers\\Store_Api_Provider( \\Mockery::mock( \\{{NS}}\\Woo\\Api\\Store_Api_Extension::class ) )',
1343+
[{ type: 'action', hook: 'woocommerce_blocks_loaded' }]
1344+
);
11931345
}
11941346
if (hasWooMyAccount) {
11951347
writeTemplateFile(path.join(templatesDir, 'src/Woo/Providers/Account_Endpoint_Provider.php'), 'src/Woo/Providers/Account_Endpoint_Provider.php');
@@ -1198,12 +1350,30 @@ function scaffoldInto(answers, targetDir) {
11981350
writeTemplateFile(path.join(templatesDir, 'tests/Unit/Account_Endpoint_Service_Test.php'), 'tests/Unit/Account_Endpoint_Service_Test.php');
11991351
wooBootLines.push('\t\t\t( new Woo\\Providers\\Account_Endpoint_Provider( Services::account_endpoint_service() ) )->init_hooks();');
12001352
addService('account_endpoint_service', 'Woo\\Account\\Account_Endpoint_Service');
1353+
addModuleHookCase(
1354+
'Woo\\Providers\\Account_Endpoint_Provider',
1355+
'new \\{{NS}}\\Woo\\Providers\\Account_Endpoint_Provider( \\Mockery::mock( \\{{NS}}\\Woo\\Account\\Account_Endpoint_Service::class ) )',
1356+
[
1357+
{ type: 'action', hook: 'init' },
1358+
{ type: 'filter', hook: 'woocommerce_account_menu_items' },
1359+
// Built from a class constant at runtime, same as above.
1360+
{
1361+
type: 'action',
1362+
hook: "'woocommerce_account_' . \\{{NS}}\\Woo\\Account\\Account_Endpoint_Service::ENDPOINT . '_endpoint'",
1363+
raw: true,
1364+
},
1365+
]
1366+
);
12011367
}
12021368
if (selectedModules.includes('interactivity')) {
12031369
writeTemplateFile(path.join(templatesDir, 'src/Frontend/Interactivity.php'), 'src/Frontend/Interactivity.php');
12041370
// Hand-written ESM served directly as a script module — no build step.
12051371
writeTemplateFile(path.join(templatesDir, 'interactivity/view.js'), 'assets/js/view.js');
12061372
bootLines.push('\t\t( new Frontend\\Interactivity() )->init_hooks();');
1373+
addModuleHookCase('Frontend\\Interactivity', 'new \\{{NS}}\\Frontend\\Interactivity()', [
1374+
{ type: 'action', hook: 'init' },
1375+
{ type: 'shortcode', hook: '{{PREFIX}}_interactivity_demo' },
1376+
]);
12071377
}
12081378
if (hasBlock) {
12091379
// Block_Registrar globs assets/build/blocks/*, so it's variant-agnostic;
@@ -1224,6 +1394,9 @@ function scaffoldInto(answers, targetDir) {
12241394
writeTemplateFile(path.join(templatesDir, 'blocks/example-static/save.js'), 'assets/src/blocks/example-static/save.js');
12251395
}
12261396
bootLines.push('\t\t( new Blocks\\Block_Registrar() )->init_hooks();');
1397+
addModuleHookCase('Blocks\\Block_Registrar', 'new \\{{NS}}\\Blocks\\Block_Registrar()', [
1398+
{ type: 'action', hook: 'init' },
1399+
]);
12271400
}
12281401

12291402
// React admin app (wp-admin only) + WooCommerce Blocks/Gateway + native
@@ -1249,6 +1422,9 @@ function scaffoldInto(answers, targetDir) {
12491422
// Assets.php scopes its enqueue via a {{#if admin_settings}}/{{else}} block.
12501423
writeTemplateFile(path.join(templatesDir, 'src/Admin/Assets.php'), 'src/Admin/Assets.php');
12511424
bootLines.push('\t\t( new Admin\\Assets() )->init_hooks();');
1425+
addModuleHookCase('Admin\\Assets', 'new \\{{NS}}\\Admin\\Assets()', [
1426+
{ type: 'action', hook: 'admin_enqueue_scripts' },
1427+
]);
12521428
}
12531429

12541430
if (needsBuildPipeline) {
@@ -1447,6 +1623,19 @@ ${entries.join('\n')}
14471623
fs.writeFileSync(servicesTestDest, servicesTest, 'utf8');
14481624
}
14491625

1626+
// Module_Hooks_Test.php: one data-provider test asserting every selected
1627+
// module's init_hooks() registers exactly the hooks it claims to. Only
1628+
// ships when at least one module contributed a case (a zero-module,
1629+
// non-React build has none).
1630+
if (moduleHookCases.length > 0) {
1631+
let moduleHooksTest = fs.readFileSync(path.join(templatesDir, 'tests/Unit/Module_Hooks_Test.php'), 'utf8');
1632+
moduleHooksTest = moduleHooksTest.replace('{{MODULE_HOOK_CASES}}', () => renderModuleHookCases(moduleHookCases));
1633+
moduleHooksTest = processTemplateContent(moduleHooksTest, 'tests/Unit/Module_Hooks_Test.php');
1634+
const moduleHooksTestDest = path.join(targetDir, 'tests/Unit/Module_Hooks_Test.php');
1635+
fs.mkdirSync(path.dirname(moduleHooksTestDest), { recursive: true });
1636+
fs.writeFileSync(moduleHooksTestDest, moduleHooksTest, 'utf8');
1637+
}
1638+
14501639
// Single supported PHP line — see MIN_PHP. The matrix also runs the next
14511640
// minor so a scaffold surfaces forward-compat breakage early.
14521641
const ciPhpMatrix = "['8.2', '8.3', '8.4']";

0 commit comments

Comments
 (0)