Repository navigation
Tests: Add unit test coverage for the row meta in WP_Plugins_List_Table::single_row() #14603
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-meta
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.
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
324 changes: 324 additions & 0 deletions
324
tests/phpunit/tests/admin/wpPluginsListTableRowMeta.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,324 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Tests for the version, author and details links WP_Plugins_List_Table::single_row() shows under a plugin. | ||
| * | ||
| * @group admin | ||
| * @group plugins | ||
| * | ||
| * @covers WP_Plugins_List_Table::single_row | ||
| */ | ||
| class Tests_Admin_wpPluginsListTableRowMeta extends WP_UnitTestCase { | ||
|
|
||
| /** | ||
| * Administrator user ID, a super admin on multisite. | ||
| * | ||
| * @var int | ||
| */ | ||
| private static $admin_id; | ||
|
|
||
| /** | ||
| * ID of a user who can activate but not install plugins. | ||
| * | ||
| * @var int | ||
| */ | ||
| private static $activator_id; | ||
|
|
||
| /** | ||
| * Number used to give each rendered plugin a unique file. | ||
| * | ||
| * @var int | ||
| */ | ||
| private static $counter = 0; | ||
|
|
||
| /** | ||
| * The original globals the list table reads. | ||
| * | ||
| * @var array | ||
| */ | ||
| private $original_globals = array(); | ||
|
|
||
| /** | ||
| * The arguments passed to the plugin_row_meta filter. | ||
| * | ||
| * @var array|null | ||
| */ | ||
| private $filter_args; | ||
|
|
||
| /** | ||
| * The slug of the plugin rendered by the current test. | ||
| * | ||
| * @var string | ||
| */ | ||
| private $slug; | ||
|
|
||
| /** | ||
| * Sets up the users. | ||
| * | ||
| * @param WP_UnitTest_Factory $factory Test factory. | ||
| */ | ||
| public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { | ||
| self::$admin_id = $factory->user->create( array( 'role' => 'administrator' ) ); | ||
| self::$activator_id = $factory->user->create( array( 'role' => 'subscriber' ) ); | ||
| get_userdata( self::$activator_id )->add_cap( 'activate_plugins' ); | ||
|
|
||
| if ( is_multisite() ) { | ||
| grant_super_admin( self::$admin_id ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Revokes the super admin grant before the fixture users are deleted. | ||
| */ | ||
| public static function wpTearDownAfterClass() { | ||
| if ( is_multisite() ) { | ||
| revoke_super_admin( self::$admin_id ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Sets up the Plugins screen and the list table globals. | ||
| */ | ||
| public function set_up() { | ||
| parent::set_up(); | ||
|
|
||
| foreach ( array( 'status', 'page', 's', 'totals', '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(); | ||
|
|
||
| $this->filter_args = null; | ||
| $this->slug = 'row-meta-plugin-' . ++self::$counter; | ||
|
|
||
| add_filter( 'plugin_row_meta', array( $this, 'capture_meta' ), PHP_INT_MAX, 4 ); | ||
|
|
||
| wp_set_current_user( self::$admin_id ); | ||
| } | ||
|
|
||
| /** | ||
| * Restores the globals and removes the test plugin. | ||
| */ | ||
| public function tear_down() { | ||
| foreach ( $this->original_globals as $name => $value ) { | ||
| $GLOBALS[ $name ] = $value; | ||
| } | ||
|
|
||
| $this->remove_plugin(); | ||
|
|
||
| parent::tear_down(); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the version, linked author and View details link of a plugin from the directory. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_list_version_author_and_details_link() { | ||
| $this->assertSame( | ||
| array( | ||
| 'Version 1.2.3', | ||
| 'By <a href="https://example.org/jane/">Jane</a>', | ||
| '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/' . ( is_multisite() ? 'network/' : '' ) . 'plugin-install.php?tab=plugin-information&plugin=' . $this->slug . '&TB_iframe=true&width=600&height=550"' | ||
| . ' class="thickbox open-plugin-details-modal" aria-label="More information about My "Plugin"" data-title="My "Plugin"">View details</a>', | ||
| ), | ||
| $this->get_meta( | ||
| array( | ||
| 'Name' => 'My "Plugin"', | ||
| 'Version' => '1.2.3', | ||
| 'Author' => 'Jane', | ||
| 'AuthorURI' => 'https://example.org/jane/', | ||
| 'slug' => $this->slug, | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the author without a link and the plugin site link of a plugin that is not from the directory. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_link_plugin_site_without_directory_slug() { | ||
| $this->assertSame( | ||
| array( | ||
| 'Version 1.2.3', | ||
| 'By Jane', | ||
| '<a href="https://example.org/plugin/?a=1&b=2" aria-label="Visit plugin site for My Plugin">Visit plugin site</a>', | ||
| ), | ||
| $this->get_meta( | ||
| array( | ||
| 'Version' => '1.2.3', | ||
| 'Author' => 'Jane', | ||
| 'PluginURI' => 'https://example.org/plugin/?a=1&b=2', | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that a user who cannot install plugins gets the plugin site link instead of View details. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_link_plugin_site_for_user_without_install_capability() { | ||
| wp_set_current_user( self::$activator_id ); | ||
|
|
||
| $this->assertSame( | ||
| array( '<a href="https://example.org/plugin/" aria-label="Visit plugin site for My Plugin">Visit plugin site</a>' ), | ||
| $this->get_meta( | ||
| array( | ||
| 'slug' => $this->slug, | ||
| 'PluginURI' => 'https://example.org/plugin/', | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that empty fields are left out. | ||
| * | ||
| * @ticket 65819 | ||
| * | ||
| * @dataProvider data_empty_values | ||
| * | ||
| * @param mixed $value The empty value of the version, author, author URI and plugin URI. | ||
| */ | ||
| public function test_should_leave_out_empty_fields( $value ) { | ||
| $this->assertSame( | ||
| array(), | ||
| $this->get_meta( | ||
| array( | ||
| 'Version' => $value, | ||
| 'Author' => $value, | ||
| 'AuthorURI' => 'https://example.org/jane/', | ||
| 'PluginURI' => $value, | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Data provider. | ||
| * | ||
| * @return array[] | ||
| */ | ||
| public static function data_empty_values() { | ||
| return array( | ||
| 'empty string' => array( '' ), | ||
| 'zero string' => array( '0' ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that the filter receives the plugin file, data and status, and its links are joined. | ||
| * | ||
| * @ticket 65819 | ||
| */ | ||
| public function test_should_filter_and_join_meta() { | ||
| add_filter( | ||
| 'plugin_row_meta', | ||
| static function ( $meta ) { | ||
| $meta[] = '<a href="#">Docs</a>'; | ||
|
|
||
| return $meta; | ||
| } | ||
| ); | ||
|
|
||
| $output = $this->render( array( 'Version' => '1.2.3' ) ); | ||
|
|
||
| $this->assertSame( $this->get_file(), $this->filter_args[1], 'The filter should receive the plugin file.' ); | ||
| $this->assertSame( 'My Plugin', $this->filter_args[2]['Name'], 'The filter should receive the plugin data.' ); | ||
| $this->assertSame( 'all', $this->filter_args[3], 'The filter should receive the status.' ); | ||
| $this->assertStringContainsString( 'plugin-version-author-uri\'>Version 1.2.3 | <a href="#">Docs</a></div>', $output, 'The links should be joined.' ); | ||
| } | ||
|
|
||
| /** | ||
| * Captures the arguments of the plugin_row_meta filter. | ||
| * | ||
| * @param string[] $meta The row meta. | ||
| * @param string $plugin_file The plugin file. | ||
| * @param array $plugin_data The plugin data. | ||
| * @param string $status The plugin status. | ||
| * @return string[] The unchanged row meta. | ||
| */ | ||
| public function capture_meta( $meta, $plugin_file, $plugin_data, $status ) { | ||
| $this->filter_args = array( $meta, $plugin_file, $plugin_data, $status ); | ||
|
|
||
| return $meta; | ||
| } | ||
|
|
||
| /** | ||
| * Renders the row of the test plugin and returns its row meta. | ||
| * | ||
| * @param array $plugin_data The plugin data to add. | ||
| * @return string[] The row meta, before the plugin_row_meta filter. | ||
| */ | ||
| private function get_meta( $plugin_data ) { | ||
| $this->render( $plugin_data ); | ||
|
|
||
| return $this->filter_args[0]; | ||
| } | ||
|
|
||
| /** | ||
| * Renders the row of the test plugin. | ||
| * | ||
| * @param array $plugin_data The plugin data to add. | ||
| * @return string The row markup. | ||
| */ | ||
| private function render( $plugin_data ) { | ||
| // 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 */\n" ), 'The test plugin should be written.' ); | ||
|
|
||
| $table = _get_list_table( 'WP_Plugins_List_Table', array( 'screen' => 'plugins' ) ); | ||
|
|
||
| return get_echo( | ||
| array( $table, 'single_row' ), | ||
| array( | ||
| array( | ||
| $this->get_file(), | ||
| array_merge( | ||
| array( | ||
| 'Name' => 'My Plugin', | ||
| 'Description' => '', | ||
| 'Version' => '', | ||
| 'Author' => '', | ||
| 'AuthorURI' => '', | ||
| 'PluginURI' => '', | ||
| ), | ||
| $plugin_data | ||
| ), | ||
| ), | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * 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 4f70c2e. I checked it with a leftover folder in place: the old code failed and the new code passes.