Repository navigation
Tests: Add unit test coverage for the row classes and checkbox in WP_Plugins_List_Table::single_row() #14606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
huzaifaalmesbah
wants to merge
2
commits into
WordPress:trunk
Choose a base branch
from
huzaifaalmesbah:tests/plugins-list-table-row-checkbox
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+351
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
351 changes: 351 additions & 0 deletions
351
tests/phpunit/tests/admin/wpPluginsListTableRowCheckbox.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,351 @@ | ||
| <?php | ||
|
|
||
| require_once __DIR__ . '/plugin-dependencies/base.php'; | ||
|
|
||
| /** | ||
| * Tests for the row classes and checkbox WP_Plugins_List_Table::single_row() outputs. | ||
| * | ||
| * @group admin | ||
| * @group plugins | ||
| * | ||
| * @covers WP_Plugins_List_Table::single_row | ||
| */ | ||
| class Tests_Admin_wpPluginsListTableRowCheckbox extends WP_PluginDependencies_UnitTestCase { | ||
|
|
||
| /** | ||
| * Number used to give each rendered plugin a unique slug. | ||
| * | ||
| * @var int | ||
| */ | ||
| private static $counter = 0; | ||
|
|
||
| /** | ||
| * The original globals the list table reads. | ||
| * | ||
| * @var array | ||
| */ | ||
| private $original_globals = array(); | ||
|
|
||
| /** | ||
| * The slug of the plugin rendered by the current test. | ||
| * | ||
| * @var string | ||
| */ | ||
| private $slug; | ||
|
|
||
| /** | ||
| * Sets up the Plugins screen, the list table globals and the dependency state. | ||
| */ | ||
| public function set_up() { | ||
| parent::set_up(); | ||
|
|
||
| foreach ( array( 'status', 'page', 's', 'totals', '_paused_plugins', 'current_screen' ) as $name ) { | ||
| $this->original_globals[ $name ] = $GLOBALS[ $name ] ?? null; | ||
| } | ||
|
|
||
| set_current_screen( 'plugins' ); | ||
|
|
||
| $GLOBALS['status'] = 'all'; | ||
| $GLOBALS['page'] = 1; | ||
| $GLOBALS['s'] = ''; | ||
| $GLOBALS['totals'] = array(); | ||
| $GLOBALS['_paused_plugins'] = array(); | ||
|
|
||
| $this->set_property_value( 'plugins', array() ); | ||
| $this->set_property_value( 'dependencies', array() ); | ||
| $this->set_property_value( 'dependency_slugs', array() ); | ||
| $this->set_property_value( 'dependency_filepaths', array() ); | ||
| $this->set_property_value( 'circular_dependencies_slugs', array() ); | ||
| $this->set_property_value( 'circular_dependencies_pairs', array() ); | ||
| $this->set_property_value( 'initialized', true ); | ||
|
|
||
| update_option( 'active_plugins', array() ); | ||
| update_option( 'uninstall_plugins', array() ); | ||
| update_site_option( 'active_sitewide_plugins', array() ); | ||
|
|
||
| $this->slug = 'row-checkbox-plugin-' . ++self::$counter; | ||
| } | ||
|
|
||
| /** | ||
| * Restores the globals and removes the test plugin. | ||
| */ | ||
| public function tear_down() { | ||
| foreach ( $this->original_globals as $name => $value ) { | ||
| $GLOBALS[ $name ] = $value; | ||
| } | ||
|
|
||
| unset( $_REQUEST['plugin_status'] ); | ||
|
|
||
| $this->remove_plugin(); | ||
|
|
||
| parent::tear_down(); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the row and checkbox of an inactive plugin. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_output_inactive_row_with_checkbox() { | ||
| $this->assertSame( | ||
| array( | ||
| '<tr class="inactive" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', | ||
| $this->get_checkbox( '' ), | ||
| ), | ||
| $this->render() | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the class of an active plugin's row. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_mark_active_row() { | ||
| update_option( 'active_plugins', array( $this->get_file() ) ); | ||
|
|
||
| $this->assertSame( '<tr class="active" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', $this->render()[0] ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that the checkbox is disabled for a plugin with dependents or unmet dependencies. | ||
| * | ||
| * @ticket 65819 | ||
| * | ||
| * @dataProvider data_dependency_states | ||
| * | ||
| * @param string $state The dependency state, 'dependents' or 'unmet'. | ||
| */ | ||
| public function test_should_disable_checkbox_for_dependency_state( $state ) { | ||
| if ( 'dependents' === $state ) { | ||
| $this->set_property_value( 'plugins', array( 'dependent/dependent.php' => array( 'Name' => 'Dependent' ) ) ); | ||
| $this->set_property_value( 'dependencies', array( 'dependent/dependent.php' => array( $this->slug ) ) ); | ||
| $this->set_property_value( 'dependency_slugs', array( $this->slug ) ); | ||
| } else { | ||
| $this->set_property_value( 'dependencies', array( $this->get_file() => array( 'missing' ) ) ); | ||
| $this->set_property_value( 'dependency_filepaths', array( 'missing' => false ) ); | ||
| } | ||
|
|
||
| $this->assertSame( $this->get_checkbox( 'disabled' ), $this->render()[1] ); | ||
| } | ||
|
|
||
| /** | ||
| * Data provider. | ||
| * | ||
| * @return array[] | ||
| */ | ||
| public static function data_dependency_states() { | ||
| return array( | ||
| 'plugin with dependents' => array( 'dependents' ), | ||
| 'plugin with unmet dependencies' => array( 'unmet' ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the row of a plugin whose PHP requirement is not met: no checkbox, marked for update. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_hide_checkbox_for_incompatible_php() { | ||
| $this->assertSame( | ||
| array( | ||
| '<tr class="inactive update" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', | ||
| '', | ||
| ), | ||
| $this->render( array( 'RequiresPHP' => '99.0' ) ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the row of a plugin whose WordPress requirement is not met: a checkbox, marked for update. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_keep_checkbox_for_incompatible_wordpress() { | ||
| $this->assertSame( | ||
| array( | ||
| '<tr class="inactive update" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', | ||
| $this->get_checkbox( '' ), | ||
| ), | ||
| $this->render( array( 'RequiresWP' => '99.0' ) ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that a plugin with an update is only marked while the upgrade view has updates. | ||
| * | ||
| * @ticket 65819 | ||
| * | ||
| * @dataProvider data_update_states | ||
| * | ||
| * @param array $totals The status totals. | ||
| * @param bool $update Whether the plugin data has an update. | ||
| * @param string $expected The expected row class. | ||
| */ | ||
| public function test_should_mark_plugin_with_update( $totals, $update, $expected ) { | ||
| $GLOBALS['totals'] = $totals; | ||
|
|
||
| $row = $this->render( $update ? array( 'update' => true ) : array() )[0]; | ||
|
|
||
| $this->assertSame( '<tr class="' . $expected . '" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', $row ); | ||
| } | ||
|
|
||
| /** | ||
| * Data provider. | ||
| * | ||
| * @return array[] | ||
| */ | ||
| public static function data_update_states() { | ||
| return array( | ||
| 'update and upgrade total' => array( array( 'upgrade' => 1 ), true, 'inactive update' ), | ||
| 'update without upgrade total' => array( array(), true, 'inactive' ), | ||
| 'upgrade total without update' => array( array( 'upgrade' => 1 ), false, 'inactive' ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the classes of a paused plugin and a plugin that can be uninstalled. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_mark_paused_and_uninstallable_plugin() { | ||
| update_option( 'active_plugins', array( $this->get_file() ) ); | ||
| update_option( 'uninstall_plugins', array( $this->get_file() => 'uninstall_callback' ) ); | ||
| $GLOBALS['_paused_plugins'] = array( | ||
| $this->slug => array( | ||
| 'type' => E_ERROR, | ||
| 'line' => 1, | ||
| 'file' => WP_PLUGIN_DIR . '/' . $this->get_file(), | ||
| 'message' => 'Error.', | ||
| ), | ||
| ); | ||
|
|
||
| $this->assertSame( '<tr class="active paused is-uninstallable" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', $this->render()[0] ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that must-use plugins have no checkbox and are active. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_hide_checkbox_for_must_use_plugins() { | ||
| // The list table reads the status from the request. | ||
| $_REQUEST['plugin_status'] = 'mustuse'; | ||
|
|
||
| $this->assertSame( | ||
| array( | ||
| '<tr class="active" data-slug="' . $this->slug . '" data-plugin="' . $this->get_file() . '">', | ||
| '', | ||
| ), | ||
| $this->render() | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that a plugin that is network active, or network only, has no checkbox on a site. | ||
| * | ||
| * @ticket 65819 | ||
| * | ||
| * @group ms-required | ||
| * | ||
| * @dataProvider data_network_restrictions | ||
| * | ||
| * @param bool $network_active Whether the plugin is network active. | ||
| * @param string $headers Additional plugin headers. | ||
| */ | ||
| public function test_should_hide_checkbox_for_network_restricted_plugin( $network_active, $headers ) { | ||
| if ( $network_active ) { | ||
| update_site_option( 'active_sitewide_plugins', array( $this->get_file() => time() ) ); | ||
| } | ||
|
|
||
| $this->assertSame( '', $this->render( array(), $headers )[1] ); | ||
| } | ||
|
|
||
| /** | ||
| * Data provider. | ||
| * | ||
| * @return array[] | ||
| */ | ||
| public static function data_network_restrictions() { | ||
| return array( | ||
| 'network active' => array( true, '' ), | ||
| 'network only' => array( false, " * Network: true\n" ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the expected checkbox markup. | ||
| * | ||
| * @param string $disabled The disabled attribute, or an empty string. | ||
| * @return string The markup. | ||
| */ | ||
| private function get_checkbox( $disabled ) { | ||
| $id = 'checkbox_' . md5( $this->get_file() ); | ||
|
|
||
| return '<label class="label-covers-full-cell" for="' . $id . '"><span class="screen-reader-text">Select My Plugin</span></label>' | ||
| . '<input type="checkbox" name="checked[]" value="' . $this->get_file() . '" id="' . $id . '" ' . $disabled . '/>'; | ||
| } | ||
|
|
||
| /** | ||
| * Renders the row of the test plugin. | ||
| * | ||
| * @param array $plugin_data The plugin data to add. | ||
| * @param string $headers Additional plugin headers. | ||
| * @return string[] The opening row tag and the checkbox markup. | ||
| */ | ||
| private function render( $plugin_data = array(), $headers = '' ) { | ||
| // is_network_only_plugin() reads the plugin headers on multisite. | ||
| $dir = WP_PLUGIN_DIR . '/' . $this->slug; | ||
| $this->remove_plugin(); | ||
| $this->assertTrue( mkdir( $dir ), 'The test plugin directory should be created.' ); | ||
| $this->assertNotFalse( file_put_contents( $dir . '/' . $this->slug . '.php', "<?php\n/**\n * Plugin Name: My Plugin\n{$headers} */\n" ), 'The test plugin should be written.' ); | ||
|
|
||
| $table = _get_list_table( 'WP_Plugins_List_Table', array( 'screen' => 'plugins' ) ); | ||
| $output = get_echo( | ||
| array( $table, 'single_row' ), | ||
| array( | ||
| array( | ||
| $this->get_file(), | ||
| array_merge( | ||
| array( | ||
| 'Name' => 'My Plugin', | ||
| 'slug' => $this->slug, | ||
| 'Description' => '', | ||
| 'Version' => '', | ||
| 'Author' => '', | ||
| 'AuthorURI' => '', | ||
| 'PluginURI' => '', | ||
| ), | ||
| $plugin_data | ||
| ), | ||
| ), | ||
| ) | ||
| ); | ||
|
|
||
| $this->assertSame( 1, preg_match( '/^<tr [^>]*>/', $output, $row ), 'The row should start with a tr tag.' ); | ||
| $this->assertSame( 1, preg_match( "#<td class='check-column'>(.*?)</td>#s", $output, $checkbox ), 'The row should have a checkbox column.' ); | ||
|
|
||
| return array( $row[0], $checkbox[1] ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the plugin file of the test plugin. | ||
| * | ||
| * @return string The plugin file. | ||
| */ | ||
| private function get_file() { | ||
| return "{$this->slug}/{$this->slug}.php"; | ||
| } | ||
|
|
||
| /** | ||
| * Removes the test plugin, including one left over by an interrupted run. | ||
| */ | ||
| private function remove_plugin() { | ||
| $dir = WP_PLUGIN_DIR . '/' . $this->slug; | ||
|
|
||
| if ( is_dir( $dir ) ) { | ||
| $this->rmdir( $dir ); | ||
| rmdir( $dir ); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In core tests,
WP_PLUGIN_DIRistests/phpunit/data/plugins, which is a tracked directory. If a run stops partway, such as on a fatal error or Ctrl+C,tear_down()never runs and this fixture folder stays there. The slug comes from a counter that starts at 1 again on the next run, somkdir()fails on the same folder name. So every test in the class fails here until someone deletes the folder by hand. The leftover also shows up ingit status.Could this helper, or
set_up(), remove a leftover folder first? #14600 already does this: itsremove_plugin()helper runs in bothset_up()andtear_down().There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! Added a
remove_plugin()helper that clears a leftover folder beforemkdir()and intear_down(), in d8f2c3c. I checked it with a leftover folder in place: the old code failed and the new code passes.