Make WordPress Core

Opened 6 days ago

Last modified 5 days ago

#66491 new enhancement

Toolbar: Improve unit test coverage for admin-bar.php

Reported by: hmbashar Owned by:
Priority: normal Milestone: Awaiting Review
Component: Toolbar Version:
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses:

Description

src/wp-includes/admin-bar.php contains most of the functions that build and render the Toolbar (Admin Bar), but only a small part of it is covered by the PHPUnit suite today. The existing tests live in tests/phpunit/tests/adminbar.php.

A search of tests/phpunit/tests shows that the following functions are currently covered:

  • _wp_admin_bar_init()
  • wp_admin_bar_wp_menu()
  • wp_admin_bar_customize_menu()
  • wp_admin_bar_new_content_menu()
  • show_admin_bar()
  • is_admin_bar_showing()

The following functions have no direct test references:

  • wp_admin_bar_render()
  • wp_admin_bar_sidebar_toggle()
  • wp_admin_bar_my_account_item()
  • wp_admin_bar_my_account_menu()
  • wp_admin_bar_site_menu()
  • wp_admin_bar_edit_site_menu()
  • wp_admin_bar_my_sites_menu()
  • wp_admin_bar_shortlink_menu()
  • wp_admin_bar_edit_menu()
  • wp_admin_bar_comments_menu()
  • wp_admin_bar_appearance_menu()
  • wp_admin_bar_updates_menu()
  • wp_admin_bar_search_menu()
  • wp_admin_bar_recovery_mode_menu()
  • wp_admin_bar_add_secondary_groups()
  • wp_enqueue_admin_bar_header_styles()
  • wp_enqueue_admin_bar_bump_styles()
  • _get_admin_bar_pref()

Adding tests for these functions will make future changes to the Toolbar safer to review, and will help catch regressions around capability checks, multisite behavior, and the output/escaping of menu items.

Proposed scope

Add tests (new methods in tests/phpunit/tests/adminbar.php, or a new test class where it makes more sense) that verify, for example:

  • Nodes added by each wp_admin_bar_*_menu() callback: the expected node IDs, parent, title, and href.
  • Capability handling: the nodes shown (or not shown) for different roles such as administrator, editor, subscriber and logged-out users.
  • Context handling: the front end vs. the admin screens (e.g. the edit/view links in wp_admin_bar_edit_menu(), wp_admin_bar_appearance_menu(), wp_admin_bar_customize_menu()).
  • Multisite: wp_admin_bar_my_sites_menu() and wp_admin_bar_site_menu() for users with one site, several sites, and super admins.
  • wp_admin_bar_updates_menu() and wp_admin_bar_comments_menu(): the counts and the resulting title/label.
  • wp_admin_bar_search_menu(): the form output and its escaping.
  • wp_admin_bar_recovery_mode_menu(): shown only while recovery mode is active.
  • wp_admin_bar_render(): the wp_before_admin_bar_render / wp_after_admin_bar_render actions and the HTML output.
  • wp_enqueue_admin_bar_header_styles() and wp_enqueue_admin_bar_bump_styles(): the inline styles that are added.
  • _get_admin_bar_pref(): the user option, default value and get_user_option_* filter handling.

This ticket is only about test coverage. No change in behavior is expected, and if a test uncovers a bug, it should be reported in a separate ticket.

The work can be split across several patches/PRs, each covering a group of related functions, so it is easy to review. Contributors are welcome to pick one group and mention it in a comment, to avoid overlapping work.

Change History (7)

This ticket was mentioned in ​PR #15521 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#1

  • Keywords has-patch has-unit-tests added

Adds unit tests for the Toolbar (Admin Bar) My Account and My Sites menus in tests/phpunit/tests/adminbar.php. This is the first group of the test coverage work proposed in the ticket. No behavior is changed.

Functions covered:

  • wp_admin_bar_my_account_item() and wp_admin_bar_my_account_menu()
    • Nothing is added for logged-out visitors.
    • Node hierarchy: my-account, user-actions, user-info and logout.
    • "Edit Profile" label and tabindex, with and without a profile link.
    • Avatar vs. fallback icon, depending on show_avatars.
  • wp_admin_bar_my_sites_menu()
    • Not added on single site installs, for logged-out visitors, or for users with no sites.
    • Single site user: node hierarchy, titles and URLs of the site and its Dashboard, New Post, Manage Comments and Visit Site items.
    • Multiple sites: each site links to its own admin and front end.
    • Site where the user lacks the read capability: links to the front end and only offers "Visit Site".
    • Site name fallback to the URL (without scheme and www.) when the name is empty.
    • Site icon and the wp_admin_bar_show_site_icons filter.
    • Network Admin item URLs and the secondary class on the site list for super admins.

Testing:

  • vendor/bin/phpunit --filter Tests_AdminBar
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter Tests_AdminBar
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (avatar class, logged-out guard, no-sites guard, read capability branch, site name fallback, site icon filter, secondary class) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Drafting the tests and running them. The tests and this description should be reviewed and edited by the author before the PR is marked ready for review.

This ticket was mentioned in ​PR #15522 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#2

Adds unit tests for the Toolbar (Admin Bar) edit, Edit Site and appearance menus in tests/phpunit/tests/adminbar.php. This is the second group of the test coverage work proposed in the ticket, and is independent from the first group in #15521. No behavior is changed.

Functions covered:

  • wp_admin_bar_edit_menu()
    • Front end: the "Edit" link for the queried post, page, term and user, including the title, URL and icon.
    • Front end: no link without the edit capability, for post types hidden from the Toolbar, or when nothing is queried.
    • Admin: "View Post" for a published post, "Preview" for a draft, nothing on the screen for adding a post, and "View Post" on the Comments screen for a single post.
    • Admin: "View Category" for a publicly viewable term and nothing for a term that is not, and "View User" on the user edit screen.
  • wp_admin_bar_edit_site_menu()
    • "Edit Site" link and its URL for block themes.
    • Not added for classic themes, for users who cannot edit theme options, or in the admin.
  • wp_admin_bar_appearance_menu()
    • The group is always added, with no items for users without theme capabilities.
    • Themes link for users who can switch themes.
    • Widgets, Menus, Background and Header links depending on the theme features.

Testing:

  • vendor/bin/phpunit --filter Tests_AdminBar
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter Tests_AdminBar
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (draft preview branch, edit_theme_options guard, Edit Site admin guard, publicly viewable term check, user view link) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15523 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#3

Adds unit tests for the Toolbar (Admin Bar) comments, updates, search and recovery mode items in tests/phpunit/tests/adminbar.php. This is the third group of the test coverage work proposed in the ticket, and is independent from the other groups. No behavior is changed.

Functions covered:

  • wp_admin_bar_comments_menu()
    • Not added for users who cannot edit posts.
    • Link, icon and the label for 0, 1 and several comments in moderation.
  • wp_admin_bar_updates_menu()
    • Not added when there are no updates, or for users who cannot update anything.
    • Link, icon, total and singular/plural label.
    • The total includes plugin updates for users who can update plugins.
  • wp_admin_bar_search_menu()
    • Form markup and node settings on the front end.
    • Also added for users without capabilities.
    • The form action is escaped.
    • Not added in the admin.
  • wp_admin_bar_recovery_mode_menu()
    • Not added when recovery mode is inactive.
    • "Exit Recovery Mode" link with the nonce when it is active.

Testing:

  • vendor/bin/phpunit --filter Tests_AdminBar
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter Tests_AdminBar
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (comments capability guard, no updates guard, updates URL, search admin guard, search action escaping, recovery mode guard) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15524 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#4

Adds unit tests for how the Toolbar (Admin Bar) is rendered, styled and shown. This is the fourth group of the test coverage work proposed in the ticket, and is independent from the other groups. No behavior is changed.

Functions covered, in tests/phpunit/tests/adminbar.php:

  • wp_admin_bar_add_secondary_groups()
    • The top-secondary and wp-logo-external groups, their parent and CSS classes, both when called directly and in the standard Toolbar.
  • wp_enqueue_admin_bar_header_styles()
    • The print style that hides the Toolbar, on the front end and in the admin, and the removal of the legacy wp_admin_bar_header action.
    • Nothing is added when the legacy action was unhooked.
  • wp_enqueue_admin_bar_bump_styles()
    • The inline styles that make room for the Toolbar, and the removal of the legacy callback.
    • Nothing is added when the legacy callback was unhooked, or when the theme provides its own callback.
  • _get_admin_bar_pref()
    • Defaults to showing the Toolbar when no preference is stored.
    • Stored true and false values, the preference of a given user, and the preference of each context.

And in a new file, tests/phpunit/tests/general/wpAdminBarRender.php:

  • wp_admin_bar_render()
    • Nothing is rendered, and no action fires, when the Toolbar is hidden or there is no Toolbar object.
    • The Toolbar is rendered once, between admin_bar_menu, wp_before_admin_bar_render and wp_after_admin_bar_render.

The wp_admin_bar_render() function remembers that it already rendered the Toolbar in a static variable, so these tests run in a separate process. They are in their own file because a separate process also runs the fixtures clean up of its class, which would remove the users shared by the other tests in adminbar.php.

The tests that add inline styles use a fresh WP_Styles instance, which is restored afterwards, so styles added by one test are not visible to the next.

Testing:

  • vendor/bin/phpunit --filter 'Tests_AdminBar|Tests_General_WpAdminBarRender'
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter 'Tests_AdminBar|Tests_General_WpAdminBarRender'
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php tests/phpunit/tests/general/wpAdminBarRender.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (render guards and rendered flag, print style, custom callback check, preference default and user argument, secondary group ID) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15525 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#5

Adds unit tests for the Toolbar (Admin Bar) icon helper, sidebar toggle and command palette button in tests/phpunit/tests/adminbar.php. This is the last group of the test coverage work proposed in the ticket, and is independent from the other groups. No behavior is changed.

Functions covered:

  • _wp_admin_bar_icon()
    • Wraps the SVG of a registered icon in the ab-icon svg-icon element.
    • Returns an empty string for an icon that is not registered.
  • wp_admin_bar_sidebar_toggle()
    • Added in the admin, with its icon, URL and hidden label.
    • Not added on the front end.
  • wp_admin_bar_command_palette_menu()
    • Added in the admin when the wp-core-commands script is enqueued: icon, URL, onclick, class, label and inline script.
    • The Ctrl+K label by default and the ⌘K label for an Apple user agent.
    • Not added when the script is not enqueued, or on the front end.

The tests that depend on the enqueued scripts use a fresh WP_Scripts instance, which is restored afterwards, and the user agent is restored too.

Testing:

  • vendor/bin/phpunit --filter Tests_AdminBar
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter Tests_AdminBar
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (sidebar toggle admin guard, command palette script and admin guards, Apple user agent detection, icon empty check and wrapper class) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

This ticket was mentioned in ​PR #15526 on ​WordPress/wordpress-develop by ​@hmbashar.


6 days ago
#6

Adds unit tests for the Toolbar (Admin Bar) New and Customize menus, and for when the Toolbar is showing, in tests/phpunit/tests/adminbar.php. These functions only had a little coverage. This is an additional group for the test coverage work in the ticket, and is independent from the other PRs. No behavior is changed.

Functions covered:

  • wp_admin_bar_new_content_menu()
    • The New menu node, and the items an editor, a contributor and a subscriber can create.
    • The menu links to the first item.
    • Custom post types: shown in the Toolbar, hidden from the Toolbar, without the create capability, and the post type named content, which would clash with the parent node.
    • The Site item on multisite for super admins only, and no Site item on single site.
  • wp_admin_bar_customize_menu()
    • The link for the current URL, its icon and class, and the Customizer support script.
    • Not added without the customize capability or in the admin.
    • Block themes: only added when a plugin uses the Customizer.
  • is_admin_bar_showing() and show_admin_bar()
    • Logged-out visitors, the default for logged-in users, and the user preference.
    • Always showing in the admin, but not on the login screen, for JSON requests or in embeds.
    • The show_admin_bar filter and the show_admin_bar() function, which overrides the user preference.
  • _wp_admin_bar_init()
    • Not set up when the Toolbar is not showing.
    • Sets up the Toolbar and its default menus.
    • The wp_admin_bar_class filter, including a class that does not exist.

Testing:

  • vendor/bin/phpunit --filter Tests_AdminBar
  • vendor/bin/phpunit -c tests/phpunit/multisite.xml --filter Tests_AdminBar
  • vendor/bin/phpcs tests/phpunit/tests/adminbar.php

I also temporarily broke the related lines in src/wp-includes/admin-bar.php (the capability, custom post type, content clash and empty menu checks, the Site item check, the Customize guards, each of the is_admin_bar_showing() guards, the boolean cast, and the _wp_admin_bar_init() checks) and confirmed that a new test fails for each one.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5.5
Used for: Written with AI and manually reviewed by me.

#7 @mindctrl
5 days ago

Thanks for the PRs! Having 6 separate PRs for the same test file makes this harder to review. Having said that, I asked Claude to review the 6 PRs attached to this ticket. Below are the findings:

Review: #66491 — Toolbar: improve unit test coverage for admin-bar.php

Reviewed 2026-11-06. The six PRs on this ticket are ​PR #15521, ​PR #15522, ​PR #15523, ​PR #15524, ​PR #15525 and ​PR #15526. Together they add about 2,550 lines, almost all to tests/phpunit/tests/adminbar.php. ​PR #15524 also adds tests/phpunit/tests/general/wpAdminBarRender.php.

How this was verified

  • Each PR applies cleanly to current trunk. All six PRs are based on the same blob of adminbar.php (22c3328582616).
  • All six also stack on top of each other with no conflicts and no duplicate method names, in order 15521 → 15526 using git apply --3way.
  • Results with all six applied together:
    • Single site: npm run test:php -- --filter 'Tests_AdminBar|Tests_General_WpAdminBarRender' gave OK (150 tests, 590 assertions).
    • Multisite: -c tests/phpunit/multisite.xml gave OK (181 tests, 767 assertions).
    • --order-by=random: 2 single-site runs and 1 multisite run all passed, so no order dependence was found.
  • I also wrote scratch tests to check specific claims below. They're marked "verified" where they appear. The working tree was restored afterwards.

Cross-cutting notes (apply to all six)

1. One ticket, six PRs. All six change the same file for the same ticket. A committer will have to merge them into one commit anyway, and reviewers have to switch between six PRs to get a picture of the file. I recommend asking for a single combined PR, or at least one branch that stacks all six, before committing. The six-way split also means the PRs restore global state in different ways (next point).

2. Global state is restored in different ways.

The inline style is fine when assertions fail, because the restore has already run. If get_standard_admin_bar() throws, though, the state leaks into the rest of the class. $_wp_theme_features is a particular risk because WP_UnitTestCase_Base::tear_down() does not reset it. Pick one pattern (save in the test, restore in tear_down()) and use it everywhere.

3. Some cleanup duplicates what the test framework already does.

  • WP_UnitTestCase_Base::set_up() already calls reset_post_types() and reset_taxonomies(). That makes the unregister_post_type() and unregister_taxonomy() calls unnecessary.
  • tear_down() already sets $GLOBALS['post'] to null, so the unset( $GLOBALS['post'] ) calls are unnecessary too.

It's harmless, but it adds noise. Not all of the cleanup is redundant: $tag, $user_id, $pagenow, HTTP_USER_AGENT and HTTP_ACCEPT are not reset by the framework (tests_reset__SERVER() only resets the HOST, REQUEST_* and SERVER_* keys), so restoring those is correct.

4. Some expected values are computed the same way the code under test computes them. Several assertions build the expected value with the same calls the implementation makes, so the test can't catch a change in what those calls produce. Examples: esc_url( home_url( '/' ) ) in the search test, the wp_nonce_url( add_query_arg( ... ) ) chain in the recovery-mode test, and wp_get_icon( $name ) in the icon test. Specific fixes are listed under each PR.

5. Overlap with existing tests. Three new tests largely repeat existing ones. They're called out under ​PR #15521, ​PR #15522 and ​PR #15526.

6. Out of scope, but found while reviewing (verified): a core bug. With add_theme_support( 'admin-bar' ) and no arguments, get_theme_support( 'admin-bar' ) returns true. Then $admin_bar_args[0]['callback'] raises "Trying to access array offset on value of type bool". This happens in two places:

  • src/wp-includes/admin-bar.php:1428 (wp_enqueue_admin_bar_bump_styles())
  • src/wp-includes/class-wp-admin-bar.php:63 (WP_Admin_Bar::initialize())

I confirmed it with a scratch test. It's worth searching Trac for an existing ticket and opening one if none exists. None of these PRs exercise this path.

PR #15521 — My Account and My Sites menus

Verdict: Good. No blocking issues; the suggestions are optional.

Where (PR's adminbar.php) Finding Suggestion
L1337 test_my_sites_menu_network_admin_items_have_expected_urls() Mostly repeats the existing test_my_sites_network_menu_for_super_admin() (trunk L757), which already checks that each network-admin-* node exists, using data_my_sites_network_menu_items(). The only new checks are the hrefs, the parents, and the ab-sub-secondary class. Add an expected-URL column to data_my_sites_network_menu_items() and assert the href in the existing test. Keep only the ab-sub-secondary class assertion as a new test.
L1173 test_my_sites_menu_is_not_added_for_user_without_sites() Covers only one half of the guard count( $wp_admin_bar->user->blogs ) < 1 && ! current_user_can( 'manage_network' ) (admin-bar.php:621). The fallback $my_sites_url = admin_url( 'my-sites.php' ) when active_blog is empty (admin-bar.php:625-629) is not covered at all. Add a test where a super admin who belongs to no sites still gets the my-sites node, with href === admin_url( 'my-sites.php' ).
L1380, L1404 (create_upload_object()) Upload files are left in the uploads directory after each test. The existing site-icon tests do the same (trunk L837, L855), so this follows the file's convention. Optional: call wp_delete_attachment( $attachment_id, true ) at the end, or $this->remove_added_uploads() in tear_down().

PR #15522 — Edit, Edit Site and Appearance menus

Verdict: Good coverage of wp_admin_bar_edit_menu(). The state handling needs tidying.

Where Finding Suggestion
L1265, L1295, L1321, L1347, L1382 ($_wp_theme_features tests) The tests save $_wp_theme_features and restore it inline (L1278, L1308, L1333, L1360, L1393). The framework doesn't reset this global, so an exception from get_standard_admin_bar() would leak theme features into every later test in the class. Save it in each test and restore it in tear_down(), or wrap the code in try/finally as ​PR #15523 does. Bypassing remove_theme_support() is correct here: _remove_theme_support() touches the custom-header/custom-background globals, which was the bug fixed in commit 69f9b3be.
L1122, L1160, L1181, L1202 (switch_theme()) Same pattern for the theme: switch, build the toolbar, switch back, then assert. Same fix. switch_theme( 'default' ) at L1162 can also go, because the test environment's theme is already default (WP_DEFAULT_THEME).
L1382 test_appearance_menu_excludes_background_and_header_when_theme_does_not_support_them() $has_nodes combines two null !== checks with a logical OR (L1391), followed by assertFalse( $has_nodes ). On failure this only reports "false is not true", not which node exists. Use two assertNull() calls, as the sibling tests do.
L745 test_edit_menu_on_front_end_links_to_edit_screen_for_queried_post() Covers more than the existing test_admin_bar_has_edit_link_for_existing_posts() (trunk L350), which only checks that the node is not null. Fine to keep. Consider deleting the older test or adding a reference to it in the docblock.
L762, L787, L819, and the term/user variants Set $GLOBALS['wp_the_query']->queried_object directly. The existing tests do the same, so this is consistent. Optional: $this->go_to( get_permalink( $post_id ) ), go_to( get_term_link( $term ) ) and go_to( get_author_posts_url( $id ) ) would run the real query path.
L824, L1087, L952, L984, L1010 Redundant cleanup, as described in cross-cutting note 3. Remove unregister_post_type(), unregister_taxonomy() and unset( $GLOBALS['post'] ). Keep the unset() calls for $tag and $user_id.
L1122 test_edit_site_menu_links_to_site_editor_for_block_theme() The case where $_wp_current_template_id is unset isn't covered. add_query_arg() drops the null postId. Optional: add an assertion that the URL has no postId when the global is unset.

PR #15523 — Comments, Updates, Search and Recovery Mode

Verdict: Solid. A few assertions compute their expected value the same way the code does and could be made stronger.

Where Finding Suggestion
L741 wp_cache_delete( 'comments-0', 'counts' ) in test_comments_menu_shows_number_of_comments_in_moderation() Unneeded (verified). I removed the line and its comment and all three data sets still pass. Inserting a comment calls wp_update_comment_count_now(), which already deletes this key (src/wp-includes/comment.php:3235). clean_up_global_scope() also flushes the cache before every test. The comment "counted per request" is wrong. Remove both lines.
L729 same test Creates real comments, which is slow, and can't practically cover large counts. The label uses number_format_i18n() while the class uses the raw integer (admin-bar.php:1187), and nothing checks that difference. Short-circuit with the wp_count_comments filter (return (object) array( 'moderated' => 1234, ... )). Then add a data set asserting count-1234 in the class and 1,234 in the label.
L822 data_updates_menu_totals() number_format_i18n() in the label and screen-reader text (admin-bar.php:1299-1302) is never tested with a number that gets formatted. Add 'thousands' => array( 1234, '1,234 updates available' ), and change the label assertion at L813 to use number_format_i18n( $total ).
L875 test_updates_menu_is_not_added_for_users_who_cannot_update() Exercises the capability checks inside wp_get_update_data(). wp_admin_bar_updates_menu() itself has only the ! total guard (admin-bar.php:1292), which L773 already covers. Keep it as an integration test, but add @covers ::wp_get_update_data, or drop it.
L959 test_search_menu_escapes_form_action() The second assertion computes its expected value with esc_url( home_url( '/' ) ), the same function the code under test uses. Assert the literal instead: action="http://example.org/?foo=bar&#038;baz=script". I confirmed this is the exact output of esc_url() for this input.
L1015 test_recovery_mode_menu_links_to_exit_action_when_recovery_mode_is_active() The expected URL (L1034-1035) repeats the implementation's add_query_arg() + wp_nonce_url() chain exactly. The test also depends on the private property WP_Recovery_Mode::$is_active through reflection (L1018). There's no public way to set it, so this is acceptable given the finally. Check behaviour instead of rebuilding the string. html_entity_decode() the href first (the real value is ...wp-login.php?action=exit_recovery_mode&amp;_wpnonce=..., verified). Then use wp_parse_url() and wp_parse_str(), and assert the path ends with wp-login.php, action === WP_Recovery_Mode::EXIT_ACTION, and wp_verify_nonce( $nonce, WP_Recovery_Mode::EXIT_ACTION ).
L999 / L1004 assertFalse( wp_is_recovery_mode() ) is a precondition, but it runs after the toolbar is built. Move it above get_standard_admin_bar().

PR #15524 — Rendering, header/bump styles and _get_admin_bar_pref()

Verdict: Useful coverage. The new file is in the wrong place, and the styles restore helper is more complicated than it needs to be.

Where Finding Suggestion
tests/phpunit/tests/general/wpAdminBarRender.php (new file), @group general tests/general/ holds tests for general-template.php. wp_admin_bar_render() is in admin-bar.php. Move it to tests/phpunit/tests/adminbar/wpAdminBarRender.php (class Tests_AdminBar_WpAdminBarRender) and drop @group general.
wpAdminBarRender.php, three @runInSeparateProcess tests Process isolation is justified: the static $rendered flag in wp_admin_bar_render() (admin-bar.php:75) would otherwise make the two negative tests pass for the wrong reason if an earlier test had rendered the toolbar. It does cost three PHP bootstraps. No change needed. A docblock sentence explaining why the negative tests also need isolation would help.
wpAdminBarRender.php L60, L87, L128, L148 ob_start() / ob_get_clean() Use the get_echo( 'wp_admin_bar_render' ) helper.
adminbar.php L22 $saved_styles, L37 tear_down(), L51 use_fresh_styles() The property wraps the value in an array so that "saved null" and "not saved" look different. It's documented as @var array but holds WP_Styles|null. ​PR #15525 needs the same thing for $wp_scripts and does it inline instead. Use two plain properties, e.g. private $original_wp_styles; private $wp_styles_replaced = false;. Better still, add one use_fresh_scripts()/use_fresh_styles() pair that both PRs share, restored in tear_down().
L991 test_enqueue_admin_bar_bump_styles_does_nothing_when_theme_uses_custom_callback() global $_wp_theme_features; comes after $this->use_fresh_styles();, and $_wp_theme_features is restored inline (L1002). Put the global first and restore in tear_down() (cross-cutting note 2).
wp_enqueue_admin_bar_bump_styles() coverage Two paths are missing: theme support with an explicit 'callback' => '_admin_bar_bump_cb' (passes through admin-bar.php:1426-1435), and argument-less add_theme_support( 'admin-bar' ), which hits the PHP warning in cross-cutting note 6. Add the explicit-callback case now. The argument-less case belongs with the bug fix.
L863 assertEmpty( $node_secondary->parent ) parent is false, not just empty. assertFalse(). The same applies to ​PR #15526 L825.
L1038 data_get_admin_bar_pref_stored_values() Only 'true' and 'false' are tested. The strict 'true' === $pref check (admin-bar.php:1540) isn't pinned down. Add 'unexpected value' => array( '1', false ).

PR #15525 — _wp_admin_bar_icon(), sidebar toggle and command palette

Verdict: Fine, but there's repeated setup to pull out and one test that does more than it needs to.

Where Finding Suggestion
L433, L475, L529 wp_register_script( 'wp-core-commands', false ) Does nothing. wp-core-commands is a default package script (src/wp-includes/assets/script-loader-packages.php:245). Once $wp_scripts is set to null, wp_scripts() creates a new instance and the default scripts register it again, so wp_register_script() returns false. Only the wp_enqueue_script() call matters. Remove the wp_register_script() lines.
L427-466, L469-500, L502-523, L525-546 The same save/null/restore code for $GLOBALS['wp_scripts'] (and for $_SERVER['HTTP_USER_AGENT']) appears four times. Pull it into a shared helper and restore in tear_down(), shared with ​PR #15524's styles helper. Restoring HTTP_USER_AGENT is needed, because tests_reset__SERVER() doesn't reset it.
L354 test_admin_bar_icon_wraps_registered_icon() Registers a made-up icon in the core-admin/ namespace through WP_Icons_Registry::register() and unregisters it afterwards. That's unnecessary and ties the test to the registry's API. The expected value also calls wp_get_icon( $name ), the same function the code under test calls. Use a core icon, as the existing test_icon_is_kept_when_node_is_updated() does: assertSame( '<span class="ab-icon svg-icon" aria-hidden="true">' . wp_get_icon( 'core-admin/plus' ) . '</span>', _wp_admin_bar_icon( 'core-admin/plus' ) ).
L469 test_command_palette_menu_shows_apple_shortcut_for_apple_user_agent() Only one user agent is tested. The pattern Macintosh|Mac OS X|Mac_PowerPC is case-insensitive, and there's a fallback $_SERVER['HTTP_USER_AGENT'] ?? '' (admin-bar.php:1026-1027). Turn it into a data provider: Macintosh, Mac OS X, Mac_PowerPC, lower-case macintosh (expects ⌘K), Windows (expects Ctrl+K), and HTTP_USER_AGENT unset (expects Ctrl+K with no warning).
L427 inline script assertions Only <script and sourceURL= are checked. Optional: also assert that the JSON-encoded Apple pattern and label are passed to the script.

PR #15526 — New and Customize menus, is_admin_bar_showing(), show_admin_bar() and _wp_admin_bar_init()

Verdict: The largest PR. It has the most overlap with existing tests and leaves out the most branches.

Where Finding Suggestion
L807 test_new_content_menu_avoids_clash_for_custom_post_type_named_content() Duplicates the existing test_content_post_type() (trunk L42, ticket #21117), which already checks the new-content parent and the add-new-content node. The only new check is the href. Drop it and add the href assertion to test_content_post_type().
L649 / L659 test_new_content_menu_lists_items_the_user_can_create() The assertion labelled "The menu should link to the first item" can't tell admin_url( array_key_first( $actions ) ) (admin-bar.php:1135) apart from a hard-coded 'post-new.php', because Post is always first for an editor. Add a case where Post isn't first. For example, a user who can only upload_files, by denying create_posts through a map_meta_cap filter, should get href === admin_url( 'media-new.php' ).
wp_admin_bar_new_content_menu() coverage The new-link branch (admin-bar.php:1096-1098) is untested. manage_links maps to do_not_allow unless link_manager_enabled is set (capabilities.php:691-697). Add a test with update_option( 'link_manager_enabled', 1 ) and an administrator, expecting the new-link node with admin_url( 'link-add.php' ).
wp_admin_bar_customize_menu() coverage The one non-trivial branch is still untested: a user who can customize but can't edit the customize_changeset post being previewed (admin-bar.php:577-581). The existing test_customize_link() (trunk L706) only covers the success case. Add a test that builds a changeset owned by another user, removes edit_post for the current user, and asserts there's no customize node.
L1483, L1514, L1533, L1553, L1572, L1595 $GLOBALS['wp_customize'] = null Repeated six times because the existing test_customize_link() leaves a WP_Customize_Manager in the global. Reset $wp_customize once in tear_down(), or make test_customize_link() clean up after itself, and remove the six lines.
L1485, L1516, L1535, L1555 switch_theme( 'default' ) Does nothing useful, since the test environment already runs default. It still writes options and fires the switch_theme hooks. Nothing switches back, unlike the block-theme tests. Remove.
L1501 strtok( $node_customize->href, '?' ) $parsed_url is already computed two lines earlier. assertStringStartsWith( wp_customize_url() . '?', $node_customize->href ), or compare the $parsed_url parts.
L738, L767, L795, L822 unregister_post_type() Redundant, as described in cross-cutting note 3. Remove.
L1847 get_class( new class() extends WP_Admin_Bar {} ) Works (it passes), but anonymous class names contain a NUL byte and the line is hard to read. Optional: a small named test class, e.g. class Tests_AdminBar_Custom_Class extends WP_Admin_Bar {} in tests/phpunit/includes/, or at the bottom of the file.
L825 assertEmpty( $node_new_content->parent ) Same as ​PR #15524 L863. assertFalse().
is_admin_bar_showing() tests (L1617-L1791) Good coverage of the guards that can be tested without separate processes. XMLRPC_REQUEST, DOING_AJAX and IFRAME_REQUEST are constants and reasonably left out. One behaviour isn't documented: the result is cached in the $show_admin_bar global, which is why set_up() unsets it. Optional: add a test showing that changing the user preference after the first call has no effect until the global is cleared.
Note: See TracTickets for help on using tickets.