@@ -65,6 +65,63 @@ function phpString(value: string): string {
6565 return `'${ value . replace ( / \\ / g, "\\\\" ) . replace ( / ' / g, "\\'" ) } '`
6666}
6767
68+ function assertDeferredHookReplayUsesWordPressLifecycle ( source : string ) : void {
69+ const tempDir = mkdtempSync ( join ( tmpdir ( ) , "wp-codebox-deferred-hook-" ) )
70+ const scriptPath = join ( tempDir , "assert-deferred-hook.php" )
71+ writeFileSync ( scriptPath , `<?php
72+ class WP_Hook {
73+ public array $callbacks = array();
74+ public function add_filter($hook, $callback, $priority, $accepted_args): void {
75+ $id = is_string($callback) ? $callback : spl_object_hash($callback);
76+ $this->callbacks[$priority][$id] = array('function' => $callback, 'accepted_args' => $accepted_args);
77+ }
78+ public function do_action($args): void {
79+ $completed = array();
80+ while (true) {
81+ ksort($this->callbacks);
82+ $next = null;
83+ foreach ($this->callbacks as $priority => $callbacks) {
84+ foreach ($callbacks as $id => $callback) {
85+ $key = $priority . ':' . $id;
86+ if (!isset($completed[$key])) {
87+ $next = array($key, $callback);
88+ break 2;
89+ }
90+ }
91+ }
92+ if ($next === null) return;
93+ $completed[$next[0]] = true;
94+ call_user_func_array($next[1]['function'], array_slice($args, 0, $next[1]['accepted_args']));
95+ }
96+ }
97+ }
98+ function add_filter($hook, $callback, $priority = 10, $accepted_args = 1): bool {
99+ global $wp_filter;
100+ if (!isset($wp_filter[$hook])) $wp_filter[$hook] = new WP_Hook();
101+ $wp_filter[$hook]->add_filter($hook, $callback, $priority, $accepted_args);
102+ return true;
103+ }
104+ function add_action($hook, $callback, $priority = 10, $accepted_args = 1): bool {
105+ return add_filter($hook, $callback, $priority, $accepted_args);
106+ }
107+ ${ extractPhpFunction ( source , "pg_run_deferred_wordpress_hook_callbacks" ) }
108+ $events = array();
109+ $wp_current_filter = array();
110+ $wp_filter = array('init' => new WP_Hook());
111+ add_action('init', function() use (&$events) { $events[] = 'core-must-not-replay'; }, 5, 0);
112+ $early = function() use (&$events) {
113+ $events[] = 'early';
114+ add_action('init', function() use (&$events) { $events[] = 'late'; }, 15, 0);
115+ };
116+ pg_run_deferred_wordpress_hook_callbacks(array(array('priority' => 0, 'callback' => array('function' => $early, 'accepted_args' => 0))), array(), 'init');
117+ if ($events !== array('early', 'late')) throw new RuntimeException('deferred init order was not faithful: ' . json_encode($events));
118+ if (count($wp_filter['init']->callbacks, COUNT_RECURSIVE) < 6) throw new RuntimeException('replayed callbacks were not retained');
119+ if ($wp_current_filter !== array()) throw new RuntimeException('current filter stack leaked');
120+ echo "ok\n";
121+ ` )
122+ assert . equal ( execFileSync ( "php" , [ scriptPath ] , { encoding : "utf8" } ) , "ok\n" )
123+ }
124+
68125function assertPhpunitConfigurationAndDiscoveryFailures ( source : string , functionName : string , discoveryFunctionName : string , logFunctionName : string , supportsImplicitFallback : boolean ) : void {
69126 const tempDir = mkdtempSync ( join ( tmpdir ( ) , "wp-codebox-phpunit-config-" ) )
70127 const malformedXml = join ( tempDir , "phpunit.xml.dist" )
@@ -663,6 +720,8 @@ const managedModeCode = phpunitRunCode({
663720 databaseType : "sqlite" ,
664721} )
665722
723+ assertDeferredHookReplayUsesWordPressLifecycle ( managedModeCode )
724+
666725assert . ok ( managedModeCode . includes ( "configured PHPUnit harness autoload file is not readable" ) )
667726assert . ok ( managedModeCode . includes ( "define('DB_NAME', ':memory:');" ) , "default managed PHPUnit remains on SQLite" )
668727assert . ok ( managedModeCode . includes ( "'cacheResult' => false" ) )
0 commit comments