Skip to content

Add BadgeText, BadgeColor, and BadgeTextColor support to ToolbarItem#34669

Merged
jfversluis merged 9 commits intonet11.0from
feature/toolbar-badge-support
Apr 9, 2026
Merged

Add BadgeText, BadgeColor, and BadgeTextColor support to ToolbarItem#34669
jfversluis merged 9 commits intonet11.0from
feature/toolbar-badge-support

Conversation

@jfversluis
Copy link
Copy Markdown
Member

@jfversluis jfversluis commented Mar 26, 2026

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

Adds BadgeText, BadgeColor, and BadgeTextColor properties to ToolbarItem, enabling badge notifications on toolbar items across all platforms.

Fixes #8305 (partial - ToolbarItem badges; Shell tab badges handled in #34659)

API

// Set a numeric badge
toolbarItem.BadgeText = "5";

// Set a text badge  
toolbarItem.BadgeText = "New";

// Dot indicator (small badge with no text)
toolbarItem.BadgeText = "";

// Custom badge colors
toolbarItem.BadgeColor = Colors.Blue;
toolbarItem.BadgeTextColor = Colors.White;

// Clear badge
toolbarItem.BadgeText = null;

Badge Text Semantics

Value Behavior
null No badge (hidden)
"" (empty string) Dot indicator
"3" Numeric count badge
"New" Text badge

Platform Support

Platform Implementation Notes
Android BadgeDrawable via Material Components Full text + color support. ClearNumber() for dot mode. Uses BadgeUtils.attachBadgeDrawable() with race condition guard.
iOS 26+ Native UIBarButtonItem.badge API Numeric and text badges. Create(0) for dot. Requires iOS 26+, no-op on earlier versions.
macOS (Catalyst 26+) Same as iOS Shares iOS implementation via Catalyst.
Windows InfoBadge overlay on AppBarButton Numeric values show count; non-numeric text and empty string show dot indicator.

Properties

Property Type Description
BadgeText string? Text/number on badge. null = hidden, "" = dot, non-empty = text/count.
BadgeColor Color? Background color. null = platform default.
BadgeTextColor Color? Foreground (text) color. null = platform default (typically white).

Changes

  • ToolbarItem.cs — Added BadgeText, BadgeColor, BadgeTextColor bindable properties with XML docs
  • ToolbarExtensions.cs (Android) — Badge rendering via BadgeDrawable with ConcurrentDictionary tracking, stale-update guard, and cleanup in DisposeMenuItems
  • ToolbarItemExtensions.cs (iOS) — iOS 26+ native badge API with platform version check
  • Toolbar.Windows.csInfoBadge overlay on AppBarButton content grid (both icon and text-only items)
  • 27 unit tests in ToolbarItemBadgeTests.cs
  • 4 UI tests (HostApp page + NUnit test with screenshots)
  • Sample gallery page ToolbarBadgePage.cs
  • All 7 PublicAPI.Unshipped.txt files updated

Copilot AI review requested due to automatic review settings March 26, 2026 13:12
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 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 -- 34669

Or

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

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

Adds cross-platform badge support to ToolbarItem via new BadgeText/BadgeColor bindable properties, with platform renderers and accompanying tests/samples.

Changes:

  • Introduces ToolbarItem.BadgeText and ToolbarItem.BadgeColor plus PublicAPI updates.
  • Implements badge rendering on Android (Material BadgeDrawable), iOS/MacCatalyst (native UIBarButtonItem badge), and Windows (InfoBadge overlay).
  • Adds unit tests, UI tests (HostApp + Appium), and a sample gallery page demonstrating toolbar badges.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/Controls/src/Core/Toolbar/ToolbarItem.cs Adds BadgeText/BadgeColor bindable properties and XML docs.
src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs Renders badges with BadgeDrawable, includes lifecycle tracking and async attachment logic.
src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs Updates iOS primary toolbar item wrapper to apply native badge when supported.
src/Controls/src/Core/Toolbar/Toolbar.Windows.cs Adds InfoBadge overlay and updates it on ToolbarItem property changes.
src/Controls/src/Core/PublicAPI/*/PublicAPI.Unshipped.txt Adds unshipped PublicAPI entries for the new ToolbarItem badge members across TFMs.
src/Controls/tests/Core.UnitTests/ToolbarItemBadgeTests.cs Unit tests for default values, binding, and property-changed behavior.
src/Controls/tests/TestCases.HostApp/Issues/Issue8305_Toolbar.cs HostApp page used by UITests to exercise badge scenarios.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8305_Toolbar.cs Appium UI tests + screenshots for toolbar badge scenarios.
src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs Registers the new sample page in the gallery navigation.
src/Controls/samples/Controls.Sample/Pages/Core/ToolbarBadgePage.cs Sample page demonstrating numeric/text badges and color changes.
src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Removes duplicate/unannotated entries for IMap.ShowInfoWindow/HideInfoWindow.

@AlleSchonWeg
Copy link
Copy Markdown
Contributor

Hi @jfversluis
i have implemented a toolbaritem badge in my app. You have added BadgeColor (BackgroundColor) and BadgeText. Good start.
Also important is:
TextColor --> UIBarButtonItemBadge.ForegroundColor and BadgeDrawable.BadgeTextColor
IsVisible --> UIBarButtonItem.Badge = null and BadgeDrawable.SetVisible()

Nice to have:
BadgeTextFont

@jfversluis
Copy link
Copy Markdown
Member Author

Thanks for the feedback @AlleSchonWeg! Great to hear you've implemented toolbar badges in your app.

BadgeTextColor — Implemented in 374ae80! New BadgeTextColor property maps to:

  • Android: BadgeDrawable.BadgeTextColor
  • iOS/MacCatalyst 26+: UIBarButtonItemBadge.ForegroundColor
  • Windows: InfoBadge.Foreground

IsVisible — Setting BadgeText = null already clears the badge completely (which effectively hides it). Adding a separate IsVisible property would add complexity without significant benefit since the same result is achieved by toggling BadgeText. If there's a specific use case where you need to preserve the text while hiding the badge, we can revisit this.

BadgeTextFont — This is tricky as platform support varies significantly. Android's BadgeDrawable doesn't expose font customization, and iOS's UIBarButtonItemBadge is similarly limited. We'll leave this out for now but could add it in a future iteration if platform APIs evolve.

@AlleSchonWeg
Copy link
Copy Markdown
Contributor

AlleSchonWeg commented Mar 26, 2026

Thanks for implementing my suggestion @jfversluis

IsVisible — Setting BadgeText = null already clears the badge completely (which effectively hides it). Adding a separate IsVisible property would add complexity without significant benefit since the same result is achieved by toggling BadgeText. If there's a specific use case where you need to preserve the text while hiding the badge, we can revisit this.

I find setting BadgeText = null not intuitive. and the developer must know that it works this way. Almost every UI-Control has a IsVisible property. As a dev i would check this property first.
Setting an empty/null text i could also create a indicator on a toolbaritem. Many apps have a small red point on a toolbar item which indicates "that there is something". Sometimes a new setting or something thats needs attention. (https://m2.material.io/develop/android/supporting/badge)

As i implemented my badge i observed a crazy behaviour on android. I added the badge on a toolbar item on one page and on another page(s) the badge also appeared. But only the badge. The icon, color etc. was correct. I don't know if i had done something wrong. Perhaps some internal caching or whatever. My setup was a TabbedPage with 4 tabs. Every tab had a NavigationPage and on the first Root ContentPage i had the item with badge plus another item without badge. The other Root ContentPages had also toolbar items but without badges. Perhaps this info can help with your implementation.

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@jfversluis
Copy link
Copy Markdown
Member Author

Implemented dot badge support! You can now set BadgeText = "" (empty string) to show a small dot indicator badge without any text. The semantics are:

  • null → no badge (hidden)
  • "" (empty string) → dot indicator
  • "3" → count badge
  • "New" → text badge

Platform-specific behavior:

  • Android: Uses BadgeDrawable.ClearNumber() which displays the native dot mode
  • iOS/MacCatalyst: Uses UIBarButtonItemBadge.Create(0) for a minimal dot
  • Windows: Uses InfoBadge.Value = -1 which renders as a dot

This applies to both PRs (#34659 Shell tabs and #34669 ToolbarItems).

Commit: 1d1549c (toolbar) and 39c8d64 (shell)

@jfversluis jfversluis changed the title Add BadgeText and BadgeColor support to ToolbarItem Add BadgeText, BadgeColor, and BadgeTextColor support to ToolbarItem Mar 26, 2026
@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

1 similar comment
@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@bronteq
Copy link
Copy Markdown

bronteq commented Mar 27, 2026

also this issue #23802 will be solved by this pr

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

1 similar comment
@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 3, 2026

Code Review Summary

Adds BadgeText, BadgeColor, and BadgeTextColor bindable properties to ToolbarItem with platform implementations for Android (Material BadgeDrawable), iOS/macCatalyst (iOS 26+ native UIBarButtonItem.badge), and Windows (InfoBadge overlay). Targets net11.0 branch appropriately as a delighter feature with new API surface. The API naming and semantics (null=hidden, ""=dot, text=badge) are consistent with the companion Shell badge PR (#34659). PublicAPI files are updated for all 7 TFMs. Unit test coverage (27 tests) and UI tests (4 tests with screenshots on 3 platforms) are solid.

Findings

🔴 Critical

Android: WeakReference not unwrapped in race condition guard (ToolbarExtensions.cs, UpdateToolbarItemBadge)

Inside the toolbar.Post() callback, the race condition guard does:

if (_menuItemToolbarItemMap.TryGetValue(menuItemId, out var currentToolbarItem) &&
    !ReferenceEquals(currentToolbarItem, toolbarItem))
    return;

_menuItemToolbarItemMap is a ConcurrentDictionary<int, WeakReference<ToolbarItem>>, so currentToolbarItem here is a WeakReference<ToolbarItem>, not a ToolbarItem. ReferenceEquals(WeakReference<T>, T) will always return false, making !ReferenceEquals(...) always true, causing the guard to always bail out — meaning badges would never render through this path.

The existing correct pattern in the same file (UpdateMenuItemIcon, ~line 466) properly unwraps:

if (!_menuItemToolbarItemMap.TryGetValue(menuItem.ItemId, out var weakRef)
    || !weakRef.TryGetTarget(out var currentToolbarItem)
    || !ReferenceEquals(currentToolbarItem, toolBarItem))
    return;

Suggested fix: Use TryGetTarget to unwrap the WeakReference before comparing.

🟡 Warnings

  1. Android: _badgeDrawables is static but DisposeMenuItems clears ALL entries globally (ToolbarExtensions.cs)

    DisposeMenuItems iterates all _badgeDrawables.Keys and cleans up every badge. Since _badgeDrawables is static, this would remove badges from other active toolbars if multiple exist simultaneously (e.g., during page navigation transitions). Consider scoping cleanup to only the menu item IDs belonging to the current toolbar being disposed, similar to how _menuItemToolbarItemMap cleanup is scoped to specific item IDs.

  2. Windows: Grid+InfoBadge wrapper created for ALL icon-based toolbar items (Toolbar.Windows.cs)

    The code unconditionally wraps every icon-based toolbar item in a WGrid + hidden InfoBadge, even when BadgeText is null. This changes the visual tree for all toolbar items and adds overhead. Consider only creating the Grid wrapper when item.BadgeText is not null, and handling the first-badge case in OnToolbarItemPropertyChanged (similar to how text-only items are handled conditionally).

  3. Windows: Non-numeric badge text silently falls back to dot indicator

    On Windows, UpdateInfoBadge maps non-numeric text (e.g., "New") to badge.Value = -1 (dot indicator). This means Windows has less badge fidelity than Android/iOS where text badges are fully supported. The PR description documents this, but consider adding a runtime note via Debug.WriteLine when text is silently downgraded.

🔵 Info

  1. API consistency with Shell badges (PR Add Shell badge support (BadgeText, BadgeColor, BadgeTextColor) #34659): ✅ Property names, types, default values, and null/empty-string semantics are fully consistent.

  2. iOS snapshots in ios-26/ folder: The test infrastructure supports ios-26 environment-specific snapshots, which is correct since badges require iOS 26+. On pre-iOS 26 CI runners, these VerifyScreenshot tests may fail if no fallback ios/ reference images exist. Verify that CI handles missing reference images gracefully (skip vs fail).

  3. Tizen: PublicAPI entries added but no platform implementation — properties will be no-ops. This is fine but worth documenting in platform support notes.

  4. Unrelated Maps PublicAPI change: The src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt change removes a duplicate HideInfoWindow entry (without nullable annotation). This is a correct cleanup but unrelated to badge support — consider separating into its own commit for cleaner history.

  5. XML documentation: Excellent — thorough per-platform docs with remarks, lists, and cross-references on all three properties.

Overall Assessment

Well-structured feature with consistent API design, good test coverage, and thoughtful platform-specific implementations. The Android WeakReference comparison bug (🔴) needs to be fixed before merge — it follows the wrong pattern compared to the existing UpdateMenuItemIcon code in the same file and would prevent badges from rendering on Android via the Post callback path. The static _badgeDrawables cleanup scope (🟡) is also worth addressing to prevent edge cases with concurrent toolbars.


Review performed by Copilot CLI

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 3, 2026

Code Review Summary (Updated)

ToolbarItem badge support — Adds BadgeText, BadgeColor, BadgeTextColor to ToolbarItem across Android, iOS 26+, and Windows. API consistent with Shell badges (PR #34659).

Findings

Severity Finding
🔴 Critical Android WeakReference comparison bugReferenceEquals(WeakReference<T>, T) always returns false because it compares wrapper object to target object. The guard always bails out, so badges won't render via Post callback. The correct pattern (TryGetTarget) is already used elsewhere in the same file (UpdateMenuItemIcon)
🟡 Warning Static _badgeDrawables cleanup in DisposeMenuItems clears ALL badges globally, not just current toolbar's
🟡 Warning Windows creates Grid + InfoBadge for ALL icon toolbar items even when no badge is set
🟡 Warning Windows silently downgrades text badges to dots with no debug diagnostic
🔵 Info API consistent with Shell badge PR ✅
🔵 Info iOS ios-26/ snapshots correctly structured ✅

Overall Assessment

⚠️ The WeakReference comparison bug is critical and must be fixed before merge. The existing correct pattern is in the same file — use TryGetTarget instead of ReferenceEquals.


Review performed by Copilot CLI

@jfversluis jfversluis added this to the .NET 11.0-preview4 milestone Apr 7, 2026
@jfversluis jfversluis force-pushed the feature/toolbar-badge-support branch from f95a346 to 3659048 Compare April 7, 2026 14:04
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 7, 2026

Code Review — PR #34669

Independent Assessment

What this changes: Adds BadgeText, BadgeColor, and BadgeTextColor bindable properties to ToolbarItem with platform implementations for Android (Material BadgeDrawable), iOS/MacCatalyst 26+ (native UIBarButtonItem.badge), and Windows (InfoBadge overlay). Includes 27 unit tests, 4 UI tests with screenshots, a gallery sample page, and all 7 PublicAPI.Unshipped.txt files updated.

Target branch: net11.0 — correct for a new API feature.

Reconciliation with PR Narrative

Agreement: ✅ Code matches the description. All claimed API semantics (null = hidden, empty = dot, non-empty = text/count) are correctly implemented across all three platforms. The .NET 11.0-preview4 milestone is appropriate.


Findings

✅ Good — Clean API design

  • Three bindable properties with clear nullable semantics and comprehensive XML documentation
  • null = no badge, "" = dot indicator, non-empty = text/count — intuitive and consistent
  • Properties are on ToolbarItem (shared cross-platform type), with platform rendering handled in platform-specific code
  • All 7 PublicAPI.Unshipped.txt files updated with correct nullable annotations (~ prefix)

✅ Good — Android: Robust race condition handling

  • ConcurrentDictionary<int, WeakReference<ToolbarItem>> maps menu item IDs to toolbar items — prevents stale async icon callbacks from updating wrong items
  • ConcurrentDictionary<int, BadgeDrawable> tracks badge drawables for cleanup
  • toolbar.Post() defers badge attachment until layout is complete
  • Triple guard in the posted callback: (1) IsAttachedToWindow check, (2) BadgeText staleness check, (3) _menuItemToolbarItemMap identity check
  • Try/catch fallback for BadgeUtils.AttachBadgeDrawableUpdateBadgeCoordinates + overlay
  • Cleanup in DisposeMenuItems and when menu items are removed

✅ Good — iOS: Correct version check and implementation

if (!OperatingSystem.IsIOSVersionAtLeast(26) && !OperatingSystem.IsMacCatalystVersionAtLeast(26))
    return;

Matches the established codebase pattern for iOS 26+ APIs. Correct mapping: Create(0) for dot, Create(count) for numeric, Create(string) for text. #pragma warning disable CA1416 is appropriate.

✅ Good — Windows: Proper InfoBadge overlay

Grid wrapping pattern for both icon-based and text-only toolbar items. Dynamic badge changes handled via OnToolbarItemPropertyChanged which either updates existing InfoBadge or wraps content for newly-badged items.

✅ Good — Comprehensive test coverage

  • 27 unit tests covering all property defaults, get/set, clearing, PropertyChanged events, XAML data binding, and edge cases (empty string, whitespace, negative numbers, emoji)
  • 4 UI tests with VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2)) — correctly accounts for async Android badge rendering
  • Screenshots provided for Android, iOS 26, and Windows
  • All prior review threads (14) resolved

💡 Observation — Static dictionaries for Android badge tracking

_menuItemToolbarItemMap and _badgeDrawables are static readonly ConcurrentDictionary fields. This is acceptable because:

  • Menu item IDs are globally unique (AView.GenerateViewId())
  • Entries are cleaned up in DisposeMenuItems and when items are removed
  • WeakReference<ToolbarItem> prevents strong-reference leaks

However, if DisposeMenuItems is ever not called (e.g., abnormal cleanup path), entries could accumulate. The WeakReference for ToolbarItem helps, but BadgeDrawable objects are strong references. This is a minor theoretical concern — the cleanup paths appear comprehensive.

💡 Observation — ToolbarItem.cs uses #nullable disable

The file-level #nullable disable means BadgeText, BadgeColor, and BadgeTextColor are technically non-nullable at the C# level, but the BindableProperty defaults are default(string) and default(Color) (both null). The PublicAPI entries use ~ prefix (nullable-oblivious). This is consistent with the existing pattern in the file (OrderProperty, PriorityProperty also use default values under #nullable disable). No action needed — just noting for awareness.

ℹ️ Note — Tizen has no implementation

Badge properties are registered in PublicAPI for Tizen but there's no Tizen-specific rendering code. Setting BadgeText on Tizen will be a no-op. This is acceptable — the properties exist cross-platform and platforms implement rendering as they support it.

ℹ️ Note — CI Status

No CI failures detected. Clean build.


Devil's Advocate

  • "Should BadgeText be an int? instead of string??" — No. The string type correctly supports numeric counts ("3"), text badges ("New"), dot indicators (""), and hidden (null). An int would require a separate "badge mode" property.
  • "Could the static ConcurrentDictionaries cause memory pressure?" — Unlikely. Entries are cleaned up on disposal and menu item removal. Menu item IDs are ints (4 bytes per entry). The dictionaries would only grow if toolbars are created/destroyed without proper cleanup.
  • "Why iOS 26+ only?"UIBarButtonItem.badge is a new API in iOS 26. Earlier iOS versions have no native badge support for bar button items. The PR correctly no-ops on earlier versions.
  • "Will the Windows Grid wrapping break custom toolbar item templates?" — The Grid wrapping only applies when BadgeText is non-null. Standard toolbar items without badges are unaffected.

Verdict: LGTM

Confidence: high
Summary: Well-designed new feature with clean API, robust platform implementations (especially the Android race condition handling), comprehensive test coverage (27 unit + 4 UI tests), and all prior review feedback resolved. The API semantics are intuitive, the implementations follow platform conventions, and the PublicAPI entries are correct. Ready for merge.

Review performed by Copilot CLI using the code-review skill

kubaflo
kubaflo previously approved these changes Apr 7, 2026
jfversluis and others added 9 commits April 8, 2026 12:34
Extends badge support from Shell bottom tabs (PR #34659) to ToolbarItems.
Uses native platform APIs: Android BadgeUtils/BadgeDrawable, iOS 26+
UIBarButtonItem.badge, and Windows InfoBadge overlay.

Includes 17 unit tests, UI test page, and sample gallery page.

Fixes #8305

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace deprecated Frame with Border control
- Add System and Microsoft.Maui usings
- Fix nullable Color? parameter in SetBadgeColor

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test file was missing NUnit.Framework, UITest.Appium, and
UITest.Core usings, and used incorrect namespace causing build
failures in Controls.TestCases.Mac.Tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…text-only items, Android cleanup

- Add BadgeTextColor property to ToolbarItem for foreground text color customization
  - Android: BadgeDrawable.BadgeTextColor
  - iOS/MacCatalyst 26+: UIBarButtonItemBadge.ForegroundColor
  - Windows: InfoBadge.Foreground brush
- Add retryTimeout to all VerifyScreenshot() calls for async Android badge rendering
- Fix Windows text-only toolbar items: wrap in Grid+InfoBadge overlay
- Add ReferenceEquals guard in Android toolbar.Post() callback for recycled menu items
- Clean up all badge drawables in DisposeMenuItems
- Add 7 unit tests for BadgeTextColor (24 total)
- Update all 7 PublicAPI.Unshipped.txt files
- Add Badge Text Color section to sample gallery page

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change BadgeText semantics: null = no badge, empty string = dot indicator,
non-empty string = text/count badge. Previously both null and empty string
hid the badge.

Platform implementations:
- Android: ClearNumber() on BadgeDrawable for dot mode
- iOS: UIBarButtonItemBadge.Create(0) for dot indicator
- Windows: Value = -1 for empty string (dot indicator)

Added 3 unit tests for dot badge behavior.
Updated XML docs to document null vs empty string semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove WaitForElement for ToolbarItem AutomationId (not accessible via
  Appium on WinUI) — wait for page StatusLabel instead
- Add Windows reference screenshots for ToolbarItemBadgesClear and
  ToolbarItemBadgeColorChanges tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 3 iOS (ios-26) reference screenshots: ToolbarItemBadgesDisplay,
  ToolbarItemBadgesClear, ToolbarItemBadgeColorChanges
- Add 3 Android reference screenshots for the same tests
- Generated from CI build 1355044 snapshot artifacts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix critical Android bug: ReferenceEquals(WeakReference<T>, T) always
  returned false, preventing badges from rendering via Post callback.
  Now uses TryGetTarget pattern consistent with UpdateMenuItemIcon.
- Scope DisposeMenuItems badge cleanup to current toolbar's menu items
  instead of clearing all badges globally from the static dictionary.
- Windows: only create Grid+InfoBadge wrapper when BadgeText is set,
  avoiding unnecessary UI overhead for toolbar items without badges.
- Windows: handle dynamic badge addition on icon items that were
  initially created without a Grid wrapper.

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

github-actions bot commented Apr 8, 2026

🧪 PR Test Evaluation

Overall Verdict: ⚠️ Tests need improvement

The PR adds solid unit test coverage for badge property logic and appropriate screenshot-based UI tests for visual rendering. A few conventions issues and missing robustness improvements in the Shell badge UI tests should be addressed before merge.

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

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34669 — ToolbarItem & Shell Badge Support
Test files evaluated: 6 (2 unit, 2 UI HostApp, 2 UI NUnit)
Fix files (badge-related): ToolbarItem.cs, ToolbarItemExtensions.cs (iOS), ToolbarExtensions.cs (Android), Toolbar.Windows.cs, and Shell badge properties


Overall Verdict

⚠️ Tests need improvement

Unit tests for ToolbarItem badge properties are thorough. The main gaps are a missing WaitForElement guard in the Shell badge test, a naming convention violation, and VerifyScreenshot() calls without retryTimeout in the Shell UI tests.


1. Fix Coverage — ✅

ToolbarItemBadgeTests.cs covers all three new bindable properties (BadgeText, BadgeColor, BadgeTextColor) with: defaults, set/clear, PropertyChanged firing (and suppression for same-value), bindable property access, data binding, and the "" vs null distinction for dot-badge semantics.

Issue8305_Toolbar.cs UI tests cover: initial badge display (screenshot), incrementing a count badge, clearing all badges, and changing badge color — all directly exercising the platform rendering paths in ToolbarExtensions.cs (Android), ToolbarItemExtensions.cs (iOS), and Toolbar.Windows.cs (Windows).

ShellBadgeTests.cs unit tests and Issue8305.cs UI tests cover the new ShellSection.BadgeText/BadgeColor properties for Shell tab badges.


2. Edge Cases & Gaps — ⚠️

Covered:

  • Null default for all badge properties
  • Numeric string, arbitrary text string, empty string (dot badge)
  • Set/clear independently for text vs. color vs. text-color
  • PropertyChanged suppression for same-value assignment
  • Data binding for all three properties
  • "" is distinct from null (tested explicitly)

Missing:

  • Secondary-order items are silently ignored per the XML docs (ToolbarItemOrder.Secondary items should not show badges). No unit or UI test verifies this boundary case — it's a silent-failure scenario that could regress.
  • BadgeTextColor visual coverage in UI testsBadgeTextColor is tested in unit tests but neither Issue8305_Toolbar.cs HostApp page nor any UI test screenshot exercises it on-device.
  • WeakReference comparison bug (commit message mentions "Fix WeakReference comparison bug") — no targeted unit test for whatever specific WeakReference scenario was fixed.

3. Test Type Appropriateness — ✅

Test Type Used Assessment
ToolbarItemBadgeTests Unit (xUnit) ✅ Correct — pure property logic
ShellBadgeTests Unit (xUnit) ✅ Correct — pure property logic
Issue8305_Toolbar / Issue8305 UI + screenshot ✅ Appropriate — badge rendering is visual and platform-specific; screenshots are the right tool

The ToolbarItemBadgeIncrements test verifies a label text change after tapping a button, which is slightly heavier than needed (a unit test could verify the count mutation), but it also validates the end-to-end UI refresh path, so it is defensible.


4. Convention Compliance — ⚠️

Issue8305_Toolbar:

  • ⚠️ File name Issue8305_Toolbar does not follow the IssueXXXXX convention. Since this is the toolbar sub-feature of issue Add Shell Badge support #8305, it should either be named Issue8305Toolbar (no underscore) or live in a dedicated issue number if one exists.

Issue8305 (Shell badge):

  • ⚠️ ShellBadgeMultipleTabsCanHaveBadges calls App.Tap("SetTab3BadgeButton") but only has App.WaitForElement("SetBadgeButton") as its guard. SetTab3BadgeButton is a separate element and should also have a WaitForElement before it is tapped.

ToolbarItemBadgeTests / ShellBadgeTests:

  • ✅ Correct xUnit [Fact] attributes, correct namespace, inherits BaseTestFixture/ShellTestBase.

UtilExtensions (incidental):

  • ⚠️ Script flagged UtilExtensions.cs as missing _IssuesUITest base class and [Category] — this appears to be a utility class, not a test class, so the warnings are likely false positives.

5. Flakiness Risk — ⚠️ Medium

Issue8305_Toolbar.cs (ToolbarItem):

  • ToolbarItemBadgesDisplay, ToolbarItemBadgesClear, and ToolbarItemBadgeColorChanges all use VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2)) — correct for async Android badge rendering.

Issue8305.cs (Shell):

  • ⚠️ ShellBadgeInitialBadgeIsVisible calls VerifyScreenshot() with no retryTimeout — if Shell tab badge rendering is async on Android (which it likely is, using the same BadgeDrawable mechanism), this test may be flaky.
  • ⚠️ ShellBadgeCanBeSetAtRuntime and ShellBadgeCanBeCleared also use VerifyScreenshot() without retryTimeout after tapping a button that triggers badge updates.

Recommendation: Add retryTimeout: TimeSpan.FromSeconds(2) to all three Shell VerifyScreenshot() calls to match the approach used in the Toolbar tests.


6. Duplicate Coverage — ✅ No duplicates

No existing tests for ToolbarItem badge properties or ShellSection badge were found. ShellBadgeTests.cs is a new file alongside existing ShellAppearanceTests.cs, and there is no overlap in what they test.


7. Platform Scope — ✅

Reference screenshots are present for iOS-26, Android, and Windows for all three ToolbarItemBadge* tests and all four ShellBadge* tests. MacCatalyst reuses iOS screenshots, which is standard in this repository.

The iOS badge implementation correctly guards against pre-iOS-26 versions (silently ignores on older OS). This platform-conditional behavior is not explicitly tested via an older-OS device test, but that is an accepted limitation for a new-API feature.


8. Assertion Quality — ⚠️

Test Assertion Assessment
ToolbarItemBadgeIncrements Assert.That(text, Is.EqualTo("Count badge: 4")) ✅ Specific
ToolbarItemBadgesClear Assert.That(text, Is.EqualTo("All badges cleared")) ✅ Specific
ToolbarItemBadgeColorChanges Assert.That(text, Is.EqualTo("Badge color: Red")) ✅ Specific
ToolbarItemBadgesDisplay VerifyScreenshot() only ⚠️ Screenshot-only; no programmatic assertion that badges actually exist — but acceptable for an initial-state visual test
ShellBadgeInitialBadgeIsVisible VerifyScreenshot() only ⚠️ Same concern
ShellBadgeMultipleTabsCanHaveBadges VerifyScreenshot() only ⚠️ No assertion that both tab badges have the expected values

The screenshot-only tests will catch visual regressions but won't detect cases where a badge renders with the wrong text value (e.g., "3" vs "5"). Adding a programmatic assertion on badge state (via Appium element text, if accessible) would strengthen these tests.


9. Fix-Test Alignment — ✅

  • ToolbarItemBadgeTestsToolbarItem.cs (new properties) ✅
  • Issue8305_Toolbar UI tests → ToolbarExtensions.cs (Android), ToolbarItemExtensions.cs (iOS), Toolbar.Windows.cs (Windows) ✅
  • ShellBadgeTests / Issue8305 UI tests → Shell badge properties ✅

The badge feature is clearly divided by concern, and each test file targets the correct code path.


Recommendations

  1. Add WaitForElement("SetTab3BadgeButton") before App.Tap("SetTab3BadgeButton") in Issue8305.ShellBadgeMultipleTabsCanHaveBadges to prevent flakiness.
  2. Add retryTimeout: TimeSpan.FromSeconds(2) to the three Shell VerifyScreenshot() calls in Issue8305.cs for consistency with the Toolbar badge tests and to guard against async badge rendering on Android.
  3. Add a test for ToolbarItemOrder.Secondary items — verify that a secondary-order ToolbarItem with BadgeText set does not render a badge (it should be silently ignored per the documented behavior).
  4. (Optional) Add a UI test scenario exercising BadgeTextColor on-device so the property is visually verified end-to-end.
  5. (Minor) Consider renaming Issue8305_Toolbar to Issue8305Toolbar (no underscore) to align with the IssueXXXXX naming convention pattern.

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.

Note

🔒 Integrity filtering filtered 1 item

Integrity filtering activated and filtered the following item during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

🧪 Test evaluation by Evaluate PR Tests

@jfversluis jfversluis merged commit 53be2cd into net11.0 Apr 9, 2026
36 of 41 checks passed
@jfversluis jfversluis deleted the feature/toolbar-badge-support branch April 9, 2026 10:17
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

🚨 API change(s) detected @davidortinau FYI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants