Skip to content
12 changes: 12 additions & 0 deletions src/js/_enqueues/admin/edit-comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,18 @@ window.setCommentsList = function() {
updateCountText( 'span.trash-count', trashDiff );
}

/*
* Status links (e.g. "Trash") are hidden entirely when their count is
* zero, so the count updates above have nothing to show/hide. Replace
* the whole view tabs list with a freshly rendered, authoritative copy
* from the server so a status that just went from zero to non-zero
* (or vice versa) appears or disappears immediately. This must run
* after the count updates above, since it supersedes them.
*/
if ( response.supplemental && response.supplemental.comment_status_links ) {
$( '.subsubsub' ).replaceWith( response.supplemental.comment_status_links );
}

if (
( ( 'trash' === settings.data.comment_status ) && !getCount( $( 'span.trash-count' ) ) ) ||
( ( 'spam' === settings.data.comment_status ) && !getCount( $( 'span.spam-count' ) ) )
Expand Down
33 changes: 33 additions & 0 deletions src/wp-admin/includes/ajax-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,38 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
$comment = get_comment( $comment_id );
$counts = wp_count_comments();

/*
* Re-render the comment status view tabs so that a status link which just
* went from zero to non-zero comments (e.g. "Trash" after trashing the
* first comment) shows up immediately, instead of only after a full page
* reload. Status links are hidden entirely when their count is zero (see
* WP_Comments_List_Table::get_views()), so updating a count in place via
* JS has nothing to update if the link was never rendered.
*/
$parsed_url = parse_url( $url );
$query_vars = array();

if ( ! empty( $parsed_url['query'] ) ) {
parse_str( $parsed_url['query'], $query_vars );
}

$valid_comment_statuses = array( 'all', 'mine', 'moderated', 'approved', 'spam', 'trash' );
$current_comment_status = ! empty( $query_vars['comment_status'] ) ? $query_vars['comment_status'] : 'all';

if ( ! in_array( $current_comment_status, $valid_comment_statuses, true ) ) {
$current_comment_status = 'all';
}

$wp_list_table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );

global $post_id, $comment_status;
$post_id = ! empty( $query_vars['p'] ) ? (int) $query_vars['p'] : 0;
$comment_status = $current_comment_status;

ob_start();
$wp_list_table->views();
$comment_status_links = ob_get_clean();

$response = new WP_Ajax_Response(
array(
'what' => 'comment',
Expand All @@ -589,6 +621,7 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
_n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
number_format_i18n( $counts->moderated )
),
'comment_status_links' => $comment_status_links,
),
)
);
Expand Down
15 changes: 15 additions & 0 deletions src/wp-admin/includes/class-wp-comments-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,21 @@ protected function get_views() {
$num_comments->$status = 10;
}

/*
* Don't show "Mine", "Spam", or "Trash" when they have zero comments, to avoid
* cluttering the moderation queue with filters that return no results. "All",
* "Pending", and "Approved" always stay visible: pending is an active task queue
* where "(0)" is reassuring confirmation, and approved is the default working view.
* The currently requested view is never hidden, regardless of its count, so the
* admin always has a visual indicator of what's being filtered (e.g. landing on
* ?comment_status=moderated from a notification after it's already been cleared).
*/
$always_visible = array( 'all', 'moderated', 'approved' );

if ( ! in_array( $status, $always_visible, true ) && empty( $num_comments->$status ) && $status !== $comment_status ) {
continue;
}

$link = add_query_arg( 'comment_status', $status, $link );

if ( $post_id ) {
Expand Down
57 changes: 46 additions & 11 deletions tests/phpunit/tests/admin/wpCommentsListTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,22 +196,57 @@ public function test_sortable_columns_with_current_ordering() {
}

/**
* @ticket 42066
*
* @covers WP_Comments_List_Table::get_views
*/
public function test_get_views_should_return_views_by_default() {
public function test_get_views_should_hide_empty_status_links() {
$this->table->prepare_items();

$expected = array(
'all' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=all" class="current" aria-current="page">All <span class="count">(<span class="all-count">0</span>)</span></a>',
'mine' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=mine&#038;user_id=0">Mine <span class="count">(<span class="mine-count">0</span>)</span></a>',
'moderated' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=moderated">Pending <span class="count">(<span class="pending-count">0</span>)</span></a>',
'approved' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=approved">Approved <span class="count">(<span class="approved-count">0</span>)</span></a>',
'spam' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=spam">Spam <span class="count">(<span class="spam-count">0</span>)</span></a>',
'trash' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=trash">Trash <span class="count">(<span class="trash-count">0</span>)</span></a>',
$views = $this->table->get_views();

$this->assertArrayHasKey( 'all', $views, 'The "All" view should always be shown.' );
$this->assertArrayHasKey( 'moderated', $views, '"Pending" should always be shown, even at zero.' );
$this->assertArrayHasKey( 'approved', $views, '"Approved" should always be shown, even at zero.' );
$this->assertArrayNotHasKey( 'mine', $views, 'Views with a zero count should be hidden.' );
$this->assertArrayNotHasKey( 'spam', $views, 'Views with a zero count should be hidden.' );
$this->assertArrayNotHasKey( 'trash', $views, 'Views with a zero count should be hidden.' );
}

/**
* @covers WP_Comments_List_Table::get_views
*/
public function test_get_views_should_show_status_link_with_nonzero_count() {
$post_id = self::factory()->post->create();
self::factory()->comment->create(
array(
'comment_post_ID' => $post_id,
'comment_approved' => '1',
)
);
$this->assertSame( $expected, $this->table->get_views() );

$this->table->prepare_items();

$views = $this->table->get_views();

$this->assertArrayHasKey( 'approved', $views, 'A view with a nonzero count should be shown.' );
$this->assertArrayHasKey( 'moderated', $views, '"Pending" should always be shown, even at zero.' );
$this->assertArrayNotHasKey( 'spam', $views, 'Views with a zero count should still be hidden.' );
$this->assertArrayNotHasKey( 'trash', $views, 'Views with a zero count should still be hidden.' );
}

/**
* @covers WP_Comments_List_Table::get_views
*/
public function test_get_views_should_not_hide_the_currently_active_status() {
$_REQUEST['comment_status'] = 'spam';

$this->table->prepare_items();

$views = $this->table->get_views();

$this->assertArrayHasKey( 'spam', $views, 'The currently active view must never be hidden, even at zero count.' );
$this->assertStringContainsString( 'class="current"', $views['spam'], 'The currently active view should be marked current.' );

unset( $_REQUEST['comment_status'] );
}

/**
Expand Down
21 changes: 9 additions & 12 deletions tests/phpunit/tests/admin/wpPostCommentsListTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@ public function set_up() {
}

/**
* @ticket 42066
*
* @covers WP_Post_Comments_List_Table::get_views
*/
public function test_get_views_should_return_views_by_default() {
public function test_get_views_should_hide_empty_status_links() {
$this->table->prepare_items();

$expected = array(
'all' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=all" class="current" aria-current="page">All <span class="count">(<span class="all-count">0</span>)</span></a>',
'mine' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=mine&#038;user_id=0">Mine <span class="count">(<span class="mine-count">0</span>)</span></a>',
'moderated' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=moderated">Pending <span class="count">(<span class="pending-count">0</span>)</span></a>',
'approved' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=approved">Approved <span class="count">(<span class="approved-count">0</span>)</span></a>',
'spam' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=spam">Spam <span class="count">(<span class="spam-count">0</span>)</span></a>',
'trash' => '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/edit-comments.php?comment_status=trash">Trash <span class="count">(<span class="trash-count">0</span>)</span></a>',
);
$this->assertSame( $expected, $this->table->get_views() );
$views = $this->table->get_views();

$this->assertArrayHasKey( 'all', $views, 'The "All" view should always be shown.' );
$this->assertArrayHasKey( 'moderated', $views, '"Pending" should always be shown, even at zero.' );
$this->assertArrayHasKey( 'approved', $views, '"Approved" should always be shown, even at zero.' );
$this->assertArrayNotHasKey( 'mine', $views, 'Views with a zero count should be hidden.' );
$this->assertArrayNotHasKey( 'spam', $views, 'Views with a zero count should be hidden.' );
$this->assertArrayNotHasKey( 'trash', $views, 'Views with a zero count should be hidden.' );
}
}
41 changes: 41 additions & 0 deletions tests/phpunit/tests/ajax/wpAjaxDeleteComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,47 @@
}
}

/**
* Trashing the only trashable comment should make the previously-hidden
* "Trash" status link appear in the same response, rather than only
* after a full page reload.
*
* @covers ::_wp_ajax_delete_comment_response
*/
public function test_trashing_only_trashed_comment_includes_trash_link_in_response() {
$this->_setRole( 'administrator' );

$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_approved' => '1',
)
);

$_POST['id'] = $comment_id;
$_POST['_ajax_nonce'] = wp_create_nonce( 'delete-comment_' . $comment_id );
$_POST['trash'] = '1';
$_POST['_total'] = '1';
$_POST['_per_page'] = '20';
$_POST['_page'] = '1';
$_POST['_url'] = admin_url( 'edit-comments.php' );

try {
$this->_handleAjax( 'delete-comment' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}

$xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );

Check warning on line 250 in tests/phpunit/tests/ajax/wpAjaxDeleteComment.php

View workflow job for this annotation

GitHub Actions / Coding standards / PHP checks

Equals sign not aligned with surrounding assignments; expected 18 spaces but found 19 spaces
$comment_status_links = (string) $xml->response[0]->comment[0]->supplemental[0]->comment_status_links[0];

Check warning on line 251 in tests/phpunit/tests/ajax/wpAjaxDeleteComment.php

View workflow job for this annotation

GitHub Actions / Coding standards / PHP checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces

$this->assertStringContainsString(
'comment_status=trash',
$comment_status_links,
'The "Trash" status link should appear once a comment has actually been trashed.'
);
}

/**
* Tests doubling the action (e.g. trash a trashed comment).
*
Expand Down
Loading