',
+ $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( '
', $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(
+ '
',
+ '',
+ ),
+ $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(
+ '
',
+ $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( '
', $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( '
', $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(
+ '
',
+ '',
+ ),
+ $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 ''
+ . '';
+ }
+
+ /**
+ * 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', " '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( '/^
]*>/', $output, $row ), 'The row should start with a tr tag.' );
+ $this->assertSame( 1, preg_match( "#
(.*?)
#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 );
+ }
+ }
+}