Skip to content

Revert ToolbarItem Badge feature (#34669) and follow-up (#34963)#34984

Closed
jfversluis wants to merge 3 commits intonet11.0from
revert/badge-feature-34669
Closed

Revert ToolbarItem Badge feature (#34669) and follow-up (#34963)#34984
jfversluis wants to merge 3 commits intonet11.0from
revert/badge-feature-34669

Conversation

@jfversluis
Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Reverts PR #34669 ("Add BadgeText, BadgeColor, and BadgeTextColor support to ToolbarItem") and its follow-up PR #34963 ("Fix CS8602 nullable dereference in ToolbarBadgePage").

Reason

PR #34669 introduced a runtime crash in the TestCases.HostApp that causes 100% UITest failure on the net11.0 branch since April 9th. The app crashes on startup across all platforms (Windows and Android confirmed, iOS/macCatalyst blocked by separate actool infra issue).

Evidence

Build Date Commit Result
1369882 Apr 8 c43df61 (before #34669) 94 passes, 0 failures
1371858 Apr 9 53be2cd (after #34669) 0 passes, 188 failures
  • Windows: NoSuchWindowException: Currently selected window has been app window dies during startupclosed
  • Android: The app was expected to be running app crashes before any test runsstill
  • All net11.0 UITest builds since April 9 show the same failure pattern

Plan

The badge feature (BadgeText, BadgeColor, BadgeTextColor on ToolbarItem) can be re-landed after the crash is root-caused and fixed. This revert restores UITest CI health on net11.0.

/cc @PureWeen

Copilot AI review requested due to automatic review settings April 15, 2026 15:10
@jfversluis jfversluis self-assigned this Apr 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34984

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34984"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reverts the previously added ToolbarItem badge feature (and its follow-up) to restore net11.0 UI test stability by removing the related public API, platform implementations, samples, and tests.

Changes:

  • Removes ToolbarItem badge API surface and related platform implementations (Android/iOS/Windows).
  • Deletes the badge sample page + issue UI tests/unit tests and associated screenshot baselines.
  • Adjusts a few iOS device test helper implementations (nullability/guards).

Reviewed changes

Copilot reviewed 20 out of 29 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/TestUtils/src/DeviceTests/UINSWindow.iOS.cs Updates NSWindow lookup logic used by iOS device tests.
src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs Updates iOS bitmap capture helper used by device tests.
src/TestUtils/src/DeviceTests.Runners/HeadlessRunner/iOS/MauiTestApplicationDelegate.cs Tweaks exception marshaling event handler signatures in the headless iOS runner.
src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Updates Maps public API baseline entries.
src/Controls/src/Core/Toolbar/ToolbarItem.cs Removes ToolbarItem badge-related bindable properties and docs.
src/Controls/src/Core/Toolbar/Toolbar.Windows.cs Removes Windows toolbar badge rendering and related property change handling.
src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs Removes Android badge drawable implementation and cleanup logic.
src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs Removes iOS/macOS badge update logic from primary toolbar items.
src/Controls/tests/Core.UnitTests/ToolbarItemBadgeTests.cs Deletes unit tests covering the removed badge properties.
src/Controls/tests/TestCases.HostApp/Issues/Issue8305_Toolbar.cs Deletes HostApp repro page for ToolbarItem badges.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8305_Toolbar.cs Deletes UI tests validating ToolbarItem badges.
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemBadgesDisplay.png Removes/updates Android screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemBadgesClear.png Removes/updates Android screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemBadgeColorChanges.png Removes/updates Android screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ToolbarItemBadgesDisplay.png Removes/updates iOS screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ToolbarItemBadgesClear.png Removes/updates iOS screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ToolbarItemBadgeColorChanges.png Removes/updates iOS screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemBadgesDisplay.png Removes/updates Windows screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemBadgesClear.png Removes/updates Windows screenshot baseline for the deleted UI tests.
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemBadgeColorChanges.png Removes/updates Windows screenshot baseline for the deleted UI tests.
src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs Removes the Toolbar badge sample entry from the gallery list.
src/Controls/samples/Controls.Sample/Pages/Core/ToolbarBadgePage.cs Deletes the Toolbar badge sample page implementation.
src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.
src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt Removes ToolbarItem badge-related API entries from Controls baseline.

Comment on lines 81 to +84
var sharedApp = nsapp.PerformSelector(SharedApplicationSelector);
if (sharedApp is null)
return null;

var windows = sharedApp.PerformSelector(WindowsSelector) as NSArray;
if (windows is null)
return null;

for (nuint i = 0; i < windows.Count; i++)
for (nuint i = 0; i < windows!.Count; i++)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous null-guards around Objective-C selector results were removed. sharedApp (from PerformSelector) can be null, and windows can be null; the current code immediately calls into sharedApp and then dereferences windows!. If either is null this will throw and can break iOS device tests. Consider restoring the early-return checks (or using pattern matching) before looping.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to 91
var uiwindows = nswin.PerformSelector(UIWindowsSelector) as NSArray;

for (nuint j = 0; j < uiwindows.Count; j++)
for (nuint j = 0; j < uiwindows!.Count; j++)
{
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nswin.PerformSelector(UIWindowsSelector) as NSArray can yield null, but the code uses uiwindows! in the loop condition. If uiwindows is null this will throw. Consider restoring the previous is not NSArray uiwindows guard (or an equivalent null check) before entering the inner loop.

Copilot uses AI. Check for mistakes.
Comment on lines 249 to 253
var context = UIGraphics.GetCurrentContext();
if (context is not null)
view.Layer.RenderInContext(context);
view.Layer.RenderInContext(context);
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
#pragma warning restore CA1416 // Validate platform compatibility
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIGraphics.GetCurrentContext() and UIGraphics.GetImageFromCurrentImageContext() are nullable APIs; the updated code now unconditionally calls view.Layer.RenderInContext(context) and then uses the resulting image without checking. If either returns null this will throw in test infrastructure. Consider restoring the null guards and/or throwing a clearer exception when the context/image cannot be created.

Copilot uses AI. Check for mistakes.
var uiwin = uiwindows.GetItem<UIWindow>(j);

if (uiwin is not null && uiwin.Handle == uiWindow.Handle)
if (uiwin.Handle == uiWindow.Handle)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uiwindows.GetItem<UIWindow>(j) can return null; the updated code removed the uiwin is not null guard and now directly accesses uiwin.Handle. Please add the null check back to avoid a potential NullReferenceException when enumerating the NSArray.

Suggested change
if (uiwin.Handle == uiWindow.Handle)
if (uiwin is not null && uiwin.Handle == uiWindow.Handle)

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

🧪 PR Test Evaluation

Overall Verdict: ⚠️ Tests need improvement

This is a revert PR — deleting tests alongside the reverted feature code is expected and appropriate. However, the second revert commit reintroduces potential null-dereference regressions in shared iOS test infrastructure (AssertionExtensions.iOS.cs, UINSWindow.iOS.cs) that could affect unrelated device tests.

👍 / 👎 — Was this evaluation helpful? React to let us know!

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34984 — Revert "Add BadgeText, BadgeColor, and BadgeTextColor support to ToolbarItem (#34669)"
Commits: 2 (both reverts)
Test files deleted: 3 (ToolbarItemBadgeTests.cs, Issue8305_Toolbar.cs HostApp + SharedTests)
Fix files changed: ~10 (feature code, TestUtils, PublicAPI files)

⚠️ Revert PR context: This PR removes a previously merged feature. The standard "were tests added?" framework does not directly apply — test deletion is the correct action when reverting a feature. Criteria below are evaluated with that context in mind.


Overall Verdict

⚠️ Tests need improvement

Test deletion alongside the reverted feature is correct. The concern is the second commit (Revert "Fix CS8602 nullable dereference in ToolbarBadgePage") which also reverts null safety improvements from shared iOS test infrastructure, reintroducing potential null dereferences in AssertionExtensions.iOS.cs and UINSWindow.iOS.cs that could cause CI flakiness for all iOS/MacCatalyst device tests.


1. Fix Coverage — ✅

This is a revert — removing tests for the removed feature is the correct behavior. The deleted tests covered the feature comprehensively:

  • ToolbarItemBadgeTests.cs: 12 unit tests covering defaults, set/clear, property-changed events, and same-value guard
  • Issue8305_Toolbar.cs: 4 UI tests covering badge display, increment, clear, and color change with VerifyScreenshot

No new tests are expected or needed for a pure revert.


2. Edge Cases & Gaps — ✅ (N/A for revert)

The deleted unit tests covered the key edge cases for the reverted feature:

  • Null defaults (BadgeText, BadgeColor default to null) ✅
  • Set/clear cycles ✅
  • PropertyChanged event firing ✅
  • Same-value guard (no spurious change events) ✅

No gaps are applicable since the feature is being removed.


3. Test Type Appropriateness — ✅ (N/A for revert)

For reference, the deleted tests used appropriate types:

  • Unit tests for pure property logic (defaults, set/clear, events) — correct choice ✅
  • UI tests with screenshots for visual badge rendering — appropriate, as rendering requires native platform context ✅

4. Convention Compliance — ✅ (N/A for revert)

The deleted test files followed conventions correctly:

  • Unit tests: [Fact] xUnit attributes ✅
  • UI test: _IssuesUITest base class, [Category(UITestCategories.ToolbarItem)] per method, WaitForElement before taps, retryTimeout: TimeSpan.FromSeconds(2) on VerifyScreenshot
  • HostApp: [Issue()] attribute ✅

5. Flakiness Risk — ⚠️ Medium

Primary risk introduced by this PR:

The second revert commit reverts null safety guards from shared iOS test infrastructure used by all iOS/MacCatalyst device tests:

AssertionExtensions.iOS.cs:

// Before (safe — from the reverted fix):
if (context is not null)
    view.Layer.RenderInContext(context);
logger?.LogDebug($"Finish: {image?.Size}");

// After (reverted — potentially unsafe):
view.Layer.RenderInContext(context);  // null context if GetCurrentContext() returns null
logger?.LogDebug($"Finish: {image.Size}");  // NRE if GetImageFromCurrentImageContext() returns null

UINSWindow.iOS.cs:
Several null-guard checks removed and replaced with ! null-forgiving operators, which suppress compiler warnings but do not prevent runtime NullReferenceException.

These changes reinstate CS8602 nullable dereference warnings and could cause crashes in device test runs on iOS/MacCatalyst under certain simulator conditions.


6. Duplicate Coverage — ✅ No duplicates

No similar tests existed before the original feature was added. All related tests are being removed as part of the revert.


7. Platform Scope — ✅ (N/A for revert)

The deleted tests had good platform coverage — snapshots existed for Android, Windows, and iOS-26. The revert correctly removes all platform-specific assets.


8. Assertion Quality — ✅ (N/A for revert)

The deleted tests had good assertion quality:

  • UI tests asserted on specific label text values ("Count badge: 4", "All badges cleared", "Badge color: Red") ✅
  • Screenshot verification with retryTimeout: TimeSpan.FromSeconds(2) for async badge rendering ✅

9. Fix-Test Alignment — ✅

The revert correctly removes feature tests alongside the feature code. The scope of the second reverted commit (#34963) was broader than just the sample page — it also improved shared test infrastructure, which is the concern flagged in Flakiness Risk above.


Recommendations

  1. ⚠️ Restore null guards in AssertionExtensions.iOS.cs — The null check for context before view.Layer.RenderInContext(context) and the null-conditional image?.Size protect all iOS/MacCatalyst device tests. Consider cherry-picking only the ToolbarBadgePage.cs portion of Fix CS8602 nullable dereference in ToolbarBadgePage #34963 rather than reverting the entire commit.

  2. ⚠️ Review UINSWindow.iOS.cs null guards — The ! null-forgiving operators suppress compiler warnings without runtime safety. Retaining the original null-check guards avoids CS8602 regressions.

  3. ℹ️ Test deletion is correct — Removing ToolbarItemBadgeTests.cs and Issue8305_Toolbar.cs is the right approach for a revert. The tests were well-written and will be preserved in git history if the feature is re-introduced.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • dc.services.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "dc.services.visualstudio.com"

See Network Configuration for more information.

🧪 Test evaluation by Evaluate PR Tests

@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

The revert of ToolbarItem badges (#34669) also removed BaseShellItem
badge entries from PublicAPI.Unshipped.txt, but the BaseShellItem
badge code (from Shell badges PR #34659) was not reverted. This
caused RS0016 build errors on all platforms.

Restore only the BaseShellItem and ShellItemRenderer badge entries;
ToolbarItem entries remain removed as intended by the revert.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@jfversluis
Copy link
Copy Markdown
Member Author

Superseded by #34991

@jfversluis jfversluis closed this Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants