diff --git a/tests/unit/Helpers/ScriptHelperTest.php b/tests/unit/Helpers/ScriptHelperTest.php new file mode 100644 index 000000000..09e265590 --- /dev/null +++ b/tests/unit/Helpers/ScriptHelperTest.php @@ -0,0 +1,105 @@ + true]); + WP_Mock::userFunction('wp_add_inline_script', ['return' => true]); + } + + /** + * @covers ::addInlineScript + */ + public function testNoWarningOnFrontendBeforeFooterScriptsFire() : void + { + WP_Mock::userFunction('is_admin', ['return' => false]); + WP_Mock::userFunction('did_action', [ + 'return' => static function (string $hook) : int { + return 0; // neither hook has fired + }, + ]); + $this->mockScriptFunctions(); + + ScriptHelper::addInlineScript('test-handle', 'console.log(1);'); + $this->addToAssertionCount(1); + } + + /** + * @covers ::addInlineScript + */ + public function testWarningOnFrontendAfterFooterScriptsFire() : void + { + WP_Mock::userFunction('is_admin', ['return' => false]); + WP_Mock::userFunction('did_action', [ + 'return' => static function (string $hook) : int { + return $hook === 'wp_print_footer_scripts' ? 1 : 0; + }, + ]); + WP_Mock::userFunction('_doing_it_wrong', ['times' => 1]); + $this->mockScriptFunctions(); + + ScriptHelper::addInlineScript('test-handle', 'console.log(1);'); + $this->addToAssertionCount(1); + } + + /** + * In admin, WP 6.9+'s _wp_get_iframed_editor_assets() calls wp_print_footer_scripts() + * on throwaway WP_Scripts instances, bumping its counter before admin_footer fires. + * addInlineScript must not warn when wp_print_footer_scripts has fired but + * admin_print_footer_scripts has not — this is the key regression the fix addresses. + * + * @covers ::addInlineScript + */ + public function testNoWarningInAdminWhenOnlyWpPrintFooterScriptsFired() : void + { + WP_Mock::userFunction('is_admin', ['return' => true]); + WP_Mock::userFunction('did_action', [ + 'return' => static function (string $hook) : int { + // Simulates the WP 6.9+ iframed-assets scenario: wp_print_footer_scripts + // counter is elevated but admin_print_footer_scripts has not fired yet. + return $hook === 'wp_print_footer_scripts' ? 1 : 0; + }, + ]); + $this->mockScriptFunctions(); + + // _doing_it_wrong is intentionally not mocked; any call would fail the test. + ScriptHelper::addInlineScript('test-handle', 'console.log(1);'); + $this->addToAssertionCount(1); + } + + /** + * In admin, the warning fires only after admin_print_footer_scripts has run, + * not merely because wp_print_footer_scripts fired (which the iframed-assets + * collector triggers early). + * + * @covers ::addInlineScript + */ + public function testWarningInAdminAfterAdminFooterScriptsFire() : void + { + WP_Mock::userFunction('is_admin', ['return' => true]); + WP_Mock::userFunction('did_action', [ + 'return' => static function (string $hook) : int { + return $hook === 'admin_print_footer_scripts' ? 1 : 0; + }, + ]); + WP_Mock::userFunction('_doing_it_wrong', ['times' => 1]); + $this->mockScriptFunctions(); + + ScriptHelper::addInlineScript('test-handle', 'console.log(1);'); + $this->addToAssertionCount(1); + } +} diff --git a/woocommerce/Helpers/ScriptHelper.php b/woocommerce/Helpers/ScriptHelper.php index 2768991ab..9e59fb50a 100644 --- a/woocommerce/Helpers/ScriptHelper.php +++ b/woocommerce/Helpers/ScriptHelper.php @@ -37,7 +37,15 @@ class ScriptHelper */ public static function addInlineScript(string $handle, string $javaScriptString) : bool { - if (did_action('wp_print_footer_scripts')) { + // In admin, wp_print_footer_scripts() is called early by _wp_get_iframed_editor_assets() + // (WP 6.9+) on throwaway WP_Scripts instances before admin_footer fires, so its counter + // is unreliable there. admin_print_footer_scripts only fires in admin-footer.php, after + // admin_footer (where notice JS is queued at priority 20), so it is the correct sentinel. + $tooLate = is_admin() + ? did_action('admin_print_footer_scripts') + : did_action('wp_print_footer_scripts'); + + if ($tooLate) { _doing_it_wrong(__METHOD__, 'Inline scripts should be added before the wp_print_footer_scripts action.', '6.0.1'); }