From 3edf4789a4cd105ec3c9b0a81b09dd2c31dfc190 Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Tue, 20 Oct 2026 08:53:08 +0600 Subject: [PATCH] Tests: Add unit test coverage for the plugin activation, deactivation and uninstall hook functions. --- .../functions/registerActivationHook.php | 60 ++++++++ .../functions/registerDeactivationHook.php | 60 ++++++++ .../tests/functions/registerUninstallHook.php | 145 ++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 tests/phpunit/tests/functions/registerActivationHook.php create mode 100644 tests/phpunit/tests/functions/registerDeactivationHook.php create mode 100644 tests/phpunit/tests/functions/registerUninstallHook.php diff --git a/tests/phpunit/tests/functions/registerActivationHook.php b/tests/phpunit/tests/functions/registerActivationHook.php new file mode 100644 index 0000000000000..077a88a387349 --- /dev/null +++ b/tests/phpunit/tests/functions/registerActivationHook.php @@ -0,0 +1,60 @@ +assertSame( 10, has_action( 'activate_' . $basename, 'my_plugin_activate' ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_plugin_files() { + return array( + 'plugin in a directory' => array( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'my-plugin/my-plugin.php' ), + 'single-file plugin' => array( WP_PLUGIN_DIR . '/my-plugin.php', 'my-plugin.php' ), + 'relative path' => array( 'my-plugin/my-plugin.php', 'my-plugin/my-plugin.php' ), + 'must-use plugin' => array( WPMU_PLUGIN_DIR . '/my-mu-plugin.php', 'my-mu-plugin.php' ), + 'backslash separators' => array( str_replace( '/', '\\', WP_PLUGIN_DIR . '/my-plugin/my-plugin.php' ), 'my-plugin/my-plugin.php' ), + ); + } + + /** + * Tests that the callback runs when the plugin is activated. + * + * @ticket 65819 + */ + public function test_should_run_callback_on_activation() { + $action = new MockAction(); + + register_activation_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', array( $action, 'action' ) ); + + $plugin = 'my-plugin/my-plugin.php'; + do_action( "activate_{$plugin}", true ); + + $this->assertSame( 1, $action->get_call_count(), 'The callback should run once.' ); + $this->assertSame( array( array( true ) ), $action->get_args(), 'The callback should receive the network-wide flag.' ); + } +} diff --git a/tests/phpunit/tests/functions/registerDeactivationHook.php b/tests/phpunit/tests/functions/registerDeactivationHook.php new file mode 100644 index 0000000000000..998ad34e4d33a --- /dev/null +++ b/tests/phpunit/tests/functions/registerDeactivationHook.php @@ -0,0 +1,60 @@ +assertSame( 10, has_action( 'deactivate_' . $basename, 'my_plugin_deactivate' ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_plugin_files() { + return array( + 'plugin in a directory' => array( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'my-plugin/my-plugin.php' ), + 'single-file plugin' => array( WP_PLUGIN_DIR . '/my-plugin.php', 'my-plugin.php' ), + 'relative path' => array( 'my-plugin/my-plugin.php', 'my-plugin/my-plugin.php' ), + 'must-use plugin' => array( WPMU_PLUGIN_DIR . '/my-mu-plugin.php', 'my-mu-plugin.php' ), + 'backslash separators' => array( str_replace( '/', '\\', WP_PLUGIN_DIR . '/my-plugin/my-plugin.php' ), 'my-plugin/my-plugin.php' ), + ); + } + + /** + * Tests that the callback runs when the plugin is deactivated. + * + * @ticket 65819 + */ + public function test_should_run_callback_on_deactivation() { + $action = new MockAction(); + + register_deactivation_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', array( $action, 'action' ) ); + + $plugin = 'my-plugin/my-plugin.php'; + do_action( "deactivate_{$plugin}", true ); + + $this->assertSame( 1, $action->get_call_count(), 'The callback should run once.' ); + $this->assertSame( array( array( true ) ), $action->get_args(), 'The callback should receive the network-wide flag.' ); + } +} diff --git a/tests/phpunit/tests/functions/registerUninstallHook.php b/tests/phpunit/tests/functions/registerUninstallHook.php new file mode 100644 index 0000000000000..338f6a069ce93 --- /dev/null +++ b/tests/phpunit/tests/functions/registerUninstallHook.php @@ -0,0 +1,145 @@ +assertSame( array( 'my-plugin/my-plugin.php' => $callback ), get_option( 'uninstall_plugins' ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_callbacks() { + return array( + 'function name' => array( 'my_plugin_uninstall' ), + 'static method as string' => array( 'My_Plugin::uninstall' ), + 'static method as array' => array( array( 'My_Plugin', 'uninstall' ) ), + ); + } + + /** + * Tests that an object method is rejected. + * + * @ticket 65819 + * + * @expectedIncorrectUsage register_uninstall_hook + */ + public function test_should_reject_object_method() { + register_uninstall_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', array( new stdClass(), 'uninstall' ) ); + + $this->assertSame( array(), get_option( 'uninstall_plugins' ), 'No callback should be stored.' ); + $this->assertSame( 0, $this->option_updates, 'The option should not be updated.' ); + } + + /** + * Tests that callbacks of other plugins are kept. + * + * @ticket 65819 + */ + public function test_should_keep_callbacks_of_other_plugins() { + update_option( 'uninstall_plugins', array( 'other-plugin/other-plugin.php' => 'other_plugin_uninstall' ) ); + + register_uninstall_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'my_plugin_uninstall' ); + + $this->assertSame( + array( + 'other-plugin/other-plugin.php' => 'other_plugin_uninstall', + 'my-plugin/my-plugin.php' => 'my_plugin_uninstall', + ), + get_option( 'uninstall_plugins' ) + ); + } + + /** + * Tests that a different callback for the same plugin replaces the stored one. + * + * @ticket 65819 + */ + public function test_should_replace_changed_callback() { + update_option( 'uninstall_plugins', array( 'my-plugin/my-plugin.php' => 'old_uninstall' ) ); + $this->option_updates = 0; + + register_uninstall_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'new_uninstall' ); + + $this->assertSame( array( 'my-plugin/my-plugin.php' => 'new_uninstall' ), get_option( 'uninstall_plugins' ), 'The callback should be replaced.' ); + $this->assertSame( 1, $this->option_updates, 'The option should be updated once.' ); + } + + /** + * Tests that the option is not saved again when the callback is unchanged. + * + * @ticket 65819 + */ + public function test_should_not_update_option_when_callback_is_unchanged() { + update_option( 'uninstall_plugins', array( 'my-plugin/my-plugin.php' => 'my_plugin_uninstall' ) ); + $this->option_updates = 0; + + register_uninstall_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'my_plugin_uninstall' ); + + $this->assertSame( 0, $this->option_updates ); + } + + /** + * Tests that a stored callback of another type is replaced. + * + * @ticket 65819 + */ + public function test_should_replace_callback_of_another_type() { + update_option( 'uninstall_plugins', array( 'my-plugin/my-plugin.php' => array( 'My_Plugin', 'uninstall' ) ) ); + + register_uninstall_hook( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', 'My_Plugin::uninstall' ); + + $this->assertSame( array( 'my-plugin/my-plugin.php' => 'My_Plugin::uninstall' ), get_option( 'uninstall_plugins' ) ); + } + + /** + * Counts updates of the uninstall_plugins option. + * + * @param mixed $value The new option value. + * @return mixed The unchanged value. + */ + public function count_option_update( $value ) { + ++$this->option_updates; + + return $value; + } +}