Fix Picker selection when SelectedItem is removed from ItemsSource#34299
Fix Picker selection when SelectedItem is removed from ItemsSource#34299jpd21122012 wants to merge 5 commits intodotnet:mainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34299Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34299" |
|
Hey there @@jpd21122012! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
Hi, just checking if this PR could be reviewed for the upcoming 10.6 servicing release. This fix ensures that Picker selection is updated correctly when the current SelectedItem is removed from the ItemsSource, preventing stale or inconsistent selection state. The change is minimal and scoped to selection state handling, without affecting public APIs, so it should be safe for servicing. Let me know if any additional scenarios should be validated 👍 |
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34299 | Add GetSelectedIndexForCollectionMutation() returning -1 when item not found; use in mutation handlers |
✅ PASSED (Gate) | Picker.cs, PickerTests.cs |
Original PR |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | claude-opus-4.6 | Direct removed-item detection: e.OldItems.Contains(SelectedItem) in RemoveItems(); ItemsSource.IndexOf(SelectedItem) < 0 in ResetItems() |
✅ PASS (49/49) | Picker.cs |
Inline, no new method |
| 2 | claude-sonnet-4.6 | Item-identity verification in ClampSelectedIndex(): after clamping, verify ItemsSource[newIndex] == SelectedItem, force -1 if stale |
✅ PASS (49/49) | Picker.cs |
Single change point, no new method |
| 3 | gpt-5.3-codex | Notification-level invalidation: clear stale selection in CollectionChanged handler before dispatching to AddItems/RemoveItems/ResetItems |
✅ PASS (50/50) | Picker.cs, PickerTests.cs |
Added test for Replace action |
| 4 | gpt-5.4 (gemini unavailable) | Fix GetSelectedIndex() directly: remove stale-index fallback, return -1 when SelectedItem not found; update mutation callers to always resync |
✅ PASS (49/49) | Picker.cs |
Fixes root cause at source method |
| 5 | cross-pollination (gpt-5.3-codex) | Index-driven mutations: SelectedIndex as source of truth; no SelectedItem lookup during Add/Remove | ❌ FAIL (hang) | Picker.cs |
Re-entrant SelectedIndex change causing infinite loop |
| PR | PR #34299 | Add GetSelectedIndexForCollectionMutation() returning -1 when item not found; original GetSelectedIndex() unchanged |
✅ PASSED (Gate) | Picker.cs, PickerTests.cs |
Original PR — adds test coverage |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | NO NEW IDEAS |
| gpt-5.3-codex | 2 | Yes | Index-driven approach → tested as Attempt 5 → ❌ FAIL |
| claude-sonnet-4.6 | 2 | No | NO NEW IDEAS |
Exhausted: Yes — all 4 models queried, no further new ideas
Selected Fix: PR's fix — Reason: The PR's GetSelectedIndexForCollectionMutation() approach is the safest for a servicing/bug-fix context. It minimally scopes the change to mutation contexts only, preserves the existing GetSelectedIndex() behavior for all other callers, and includes good test coverage. Attempts 1 and 4 are simpler alternatives, but the PR's approach makes the intent explicit and is safest as a conservative fix. Attempt 2 (ClampSelectedIndex) is elegant but has subtle risks if called outside ItemsSource context.
📋 Report — Final Recommendation
✅ Final Recommendation: APPROVE
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #33307, unit tests, 2 files changed |
| Gate | ✅ PASSED | Android — tests fail without fix, pass with fix |
| Try-Fix | ✅ COMPLETE | 5 attempts (4 passing, 1 failing); cross-pollination exhausted |
| Report | ✅ COMPLETE |
Summary
PR #34299 fixes a real, verified bug in Picker where removing the selected item from ItemsSource caused index-based (stale) selection instead of clearing selection. The fix is minimal, well-scoped, and backed by 3 new unit tests. Multi-model exploration confirmed the fix is sound — 4 independent approaches all passed, and the PR's implementation is among the safest choices for a servicing fix.
Selected Fix: PR's fix (GetSelectedIndexForCollectionMutation()) — conservative, well-scoped, doesn't break existing callers of GetSelectedIndex().
Root Cause
GetSelectedIndex() fell back to returning the previous SelectedIndex when SelectedItem was no longer found in the collection (return newIndex >= 0 ? newIndex : SelectedIndex). This caused collection mutations (remove, reset) to preserve a stale index pointing to whatever item now occupies that position, instead of clearing the selection to signal "no item selected."
Fix Quality
Strengths:
- Scoped to mutation paths only (
RemoveItems,AddItems,ResetItems) via newGetSelectedIndexForCollectionMutation()— preserves all existingGetSelectedIndex()behavior - Covers the right cases: clears selection when selected item removed, preserves when unrelated item removed, preserves when item inserted before/after selection
- 3 new unit tests cover the new behavior and prevent regressions
- Updated
TestItemsSourceCollectionChangedRemoveAtEndSelectedtest expectations are correct (selected item removed → selection cleared, not "move to last item")
Minor concerns (non-blocking):
GetSelectedIndexForCollectionMutation()andGetSelectedIndex()are nearly identical with subtle fallback differences. The PR author acknowledged this and chose to keep them separate for safety. This is defensible for a bug fix. A future refactor could unify them with a parameter.- PR description's "Root Cause" section mentions modifying
GetSelectedIndex()directly, but the actual implementation uses a new method — mismatch noted by Copilot PR reviewer, acknowledged but not yet updated. Recommend updating the description before merge. - kubaflo's device/UI test runs should be confirmed passing before merge.
|
I'm pretty sure this PR will regress some other aspects of the picker |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
- Avoid modifying shared GetSelectedIndex behavior - Update selection only during collection mutations (add/remove/reset) - Ensure SelectedIndex is reset when SelectedItem is no longer present - Prevent stale or incorrect selection after ItemsSource changes
Thanks for the feedback! I’ve reworked the fix to avoid modifying the shared GetSelectedIndex logic. Instead, the selection is now updated only during collection mutations (add/remove/reset). This ensures that when the selected item is removed from the ItemsSource, the Picker does not fall back to a stale index, while keeping existing selection behavior intact for other scenarios. I also verified this against multiple cases (item removal, insertions, reordering, and binding scenarios) to ensure no regressions. Let me know if you’d like me to add test coverage for this |
There was a problem hiding this comment.
Pull request overview
Fixes Picker selection consistency when ItemsSource mutates so selection is based on the selected object identity (and cleared when the selected object is removed), rather than accidentally “sticking” to an index that now points at a different item.
Changes:
- Introduces
GetSelectedIndexForCollectionMutation()to compute selection index fromSelectedItemwithout falling back to the previousSelectedIndexwhen the item is missing. - Updates
AddItems,RemoveItems, andResetItemsto use the new selection computation for collection-change scenarios. - Explicitly clears selection (
ClampSelectedIndex(-1)) when a removal causesSelectedItemto no longer exist in the backing collection.
- Verify selection is cleared when selected item is removed - Verify selection is preserved when non-selected items are removed - Verify selection remains consistent during insert and reorder scenarios
kubaflo
left a comment
There was a problem hiding this comment.
TestItemsSourceCollectionChangedRemoveAtEndSelected is failing
Selection should be cleared when the selected item is removed from the ItemsSource. Updated test to expect SelectedIndex = -1 and SelectedItem = null.
I investigated the failure in The test was asserting that after removing the selected item from the I updated the test to reflect the correct behavior:
After correcting the expectation, the tests pass. |
🚦 Gate - Test Before and After Fix📊 Expand Full Gate —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🧪 PickerTests PickerTests |
✅ FAIL — 106s | ✅ PASS — 66s |
🔴 Without fix — 🧪 PickerTests: FAIL ✅ · 106s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj (in 5.36 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 366 ms).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 8.73 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/TestUtils/TestUtils.csproj (in 5 ms).
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 11 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 55 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 32 ms).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 35 ms).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 66 ms).
1 of 10 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.16] Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.25] Discovered: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.26] Starting: Microsoft.Maui.Controls.Core.UnitTests
Passed PickerRetainsSelectionWhenUnselectedItemIsRemovedFromItemsSource [14 ms]
Passed NullItemReturnsEmptyStringFromInterface [1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 0, insertNames: ["George", "Pete"]) [9 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 2, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 1, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 1, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 2, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 3, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 0, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 3, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedInsert [2 ms]
Passed TestItemsSourceCollectionChangedReAssign [6 ms]
Passed TestItemsSourceCollectionOfStrings [< 1 ms]
Passed PickerRetainsSelectionAfterInsertReorderingItemsSource [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 1, removeCount: 2, selectedItemPreserved: False) [4 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 2, removeCount: 1, selectedItemPreserved: True) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 0, removeCount: 2, selectedItemPreserved: False) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 1, removeCount: 1, selectedItemPreserved: False) [< 1 ms]
[xUnit.net 00:00:01.39] Assert.Equal() Failure: Values differ
[xUnit.net 00:00:01.39] Expected: -1
[xUnit.net 00:00:01.39] Actual: 1
[xUnit.net 00:00:01.39] Stack Trace:
[xUnit.net 00:00:01.39] /_/src/Controls/tests/Core.UnitTests/PickerTests.cs(876,0): at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.PickerClearsSelectionWhenSelectedItemIsRemovedFromItemsSource()
[xUnit.net 00:00:01.39] at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
[xUnit.net 00:00:01.39] at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 0, removeCount: 1, selectedItemPreserved: True) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 2, removeCount: 2, selectedItemPreserved: True) [< 1 ms]
Passed TestSelectedIndexChangedOnCollectionShrink [1 ms]
Passed TestSelectedIndexOutOfRangeUpdatesSelectedItem [< 1 ms]
Passed TestDisplayConverter [< 1 ms]
Passed PickerPreservesSelectedItemAfterRemovingItemBeforeSelection [< 1 ms]
Passed TestSelectedIndexInRange [< 1 ms]
Failed PickerClearsSelectionWhenSelectedItemIsRemovedFromItemsSource [2 ms]
Error Message:
Assert.Equal() Failure: Values differ
Expected: -1
Actual: 1
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.PickerClearsSelectionWhenSelectedItemIsRemovedFromItemsSource() in /_/src/Controls/tests/Core.UnitTests/PickerTests.cs:line 876
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Passed TestSelectedItemSet [3 ms]
[xUnit.net 00:00:01.39] Assert.Equal() Failure: Values differ
[xUnit.net 00:00:01.39] Expected: -1
[xUnit.net 00:00:01.39] Actual: 1
[xUnit.net 00:00:01.39] Stack Trace:
[xUnit.net 00:00:01.39] /_/src/Controls/tests/Core.UnitTests/PickerTests.cs(475,0): at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Int32 removeCount)
[xUnit.net 00:00:01.39] at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
[xUnit.net 00:00:01.39] at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
[xUnit.net 00:00:01.39] Assert.Equal() Failure: Values differ
[xUnit.net 00:00:01.39] Expected: -1
[xUnit.net 00:00:01.39] Actual: 2
[xUnit.net 00:00:01.39] Stack Trace:
[xUnit.net 00:00:01.39] /_/src/Controls/tests/Core.UnitTests/PickerTests.cs(475,0): at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Int32 removeCount)
[xUnit.net 00:00:01.39] at InvokeStub_PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Object, Span`1)
[xUnit.net 00:00:01.39] at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
[xUnit.net 00:00:01.39] PickerClearsSelectionWhenSelectedItemIsRemovedFromItemsSource [FAIL]
[xUnit.net 00:00:01.39] TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 2) [FAIL]
[xUnit.net 00:00:01.39] TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 1) [FAIL]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedReAssign [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedClear [1 ms]
Passed TestItemsSourceCollectionChangedClear [< 1 ms]
Passed TestSelectedIndexInRangeDefaultSelectedIndex [< 1 ms]
Failed TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 2) [< 1 ms]
Error Message:
Assert.Equal() Failure: Values differ
Expected: -1
Actual: 1
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Int32 removeCount) in /_/src/Controls/tests/Core.UnitTests/PickerTests.cs:line 475
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
Failed TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 1) [< 1 ms]
Error Message:
Assert.Equal() Failure: Values differ
Expected: -1
Actual: 2
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Int32 removeCount) in /_/src/Controls/tests/Core.UnitTests/PickerTests.cs:line 475
at InvokeStub_PickerTests.TestItemsSourceCollectionChangedRemoveAtEndSelected(Object, Span`1)
at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
Passed TestSelectedItemChangeSelectedIndex [< 1 ms]
Passed TestNestedDisplayMemberPathExpression [< 1 ms]
Passed TestSetItemsSourceProperty [1 ms]
Passed PickerPreservesSelectedItemAfterInsertingItemBeforeSelection [< 1 ms]
Passed TestItemsSourceCollectionChangedAppend [< 1 ms]
Passed SettingSelectedIndexUpdatesSelectedItem [2 ms]
Passed TestSelectedItemAssignedItemsSourceCollectionChangedRemove [< 1 ms]
Passed ThrowsWhenModifyingItemsIfItemsSourceIsSet [< 1 ms]
Passed TestItemsSourceEnums [3 ms]
Passed TestEmptyCollectionResetItems [< 1 ms]
Passed TestSetSelectedIndexOnNullRows [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedAppend [< 1 ms]
[xUnit.net 00:00:01.40] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed TestSelectedItemDefault [< 1 ms]
Passed TestItemsSourceCollectionChangedInsert [< 1 ms]
Passed TestUnsubscribeINotifyCollectionChanged [< 1 ms]
Passed TestItemsSourceCollectionChangedRemove [< 1 ms]
Test Run Failed.
Total tests: 49
Passed: 46
Failed: 3
Total time: 1.8646 Seconds
🟢 With fix — 🧪 PickerTests: PASS ✅ · 66s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13757456
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.37] Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.96] Discovered: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.97] Starting: Microsoft.Maui.Controls.Core.UnitTests
Passed PickerRetainsSelectionWhenUnselectedItemIsRemovedFromItemsSource [22 ms]
Passed NullItemReturnsEmptyStringFromInterface [1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 0, insertNames: ["George", "Pete"]) [15 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 2, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 1, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 1, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 2, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 3, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 0, insertNames: ["George"]) [< 1 ms]
Passed TestItemsSourceCollectionChangedInsertBeforeSelected(insertionIndex: 3, insertNames: ["George", "Pete"]) [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedInsert [2 ms]
Passed TestItemsSourceCollectionChangedReAssign [6 ms]
Passed TestItemsSourceCollectionOfStrings [< 1 ms]
Passed PickerRetainsSelectionAfterInsertReorderingItemsSource [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 1, removeCount: 2, selectedItemPreserved: False) [2 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 2, removeCount: 1, selectedItemPreserved: True) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 0, removeCount: 2, selectedItemPreserved: False) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 1, removeCount: 1, selectedItemPreserved: False) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 0, removeCount: 1, selectedItemPreserved: True) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveBeforeSelected(removeIndex: 2, removeCount: 2, selectedItemPreserved: True) [< 1 ms]
Passed TestSelectedIndexChangedOnCollectionShrink [1 ms]
Passed TestSelectedIndexOutOfRangeUpdatesSelectedItem [1 ms]
Passed TestDisplayConverter [1 ms]
Passed PickerPreservesSelectedItemAfterRemovingItemBeforeSelection [< 1 ms]
Passed TestSelectedIndexInRange [< 1 ms]
Passed PickerClearsSelectionWhenSelectedItemIsRemovedFromItemsSource [< 1 ms]
Passed TestSelectedItemSet [3 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedReAssign [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedClear [1 ms]
Passed TestItemsSourceCollectionChangedClear [< 1 ms]
Passed TestSelectedIndexInRangeDefaultSelectedIndex [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 2) [< 1 ms]
Passed TestItemsSourceCollectionChangedRemoveAtEndSelected(removeCount: 1) [< 1 ms]
Passed TestSelectedItemChangeSelectedIndex [< 1 ms]
Passed TestNestedDisplayMemberPathExpression [< 1 ms]
Passed TestSetItemsSourceProperty [1 ms]
Passed PickerPreservesSelectedItemAfterInsertingItemBeforeSelection [< 1 ms]
Passed TestItemsSourceCollectionChangedAppend [< 1 ms]
Passed SettingSelectedIndexUpdatesSelectedItem [3 ms]
Passed TestSelectedItemAssignedItemsSourceCollectionChangedRemove [< 1 ms]
Passed ThrowsWhenModifyingItemsIfItemsSourceIsSet [< 1 ms]
Passed TestItemsSourceEnums [2 ms]
Passed TestEmptyCollectionResetItems [< 1 ms]
Passed TestSetSelectedIndexOnNullRows [< 1 ms]
Passed TestSelectedIndexAssignedItemsSourceCollectionChangedAppend [< 1 ms]
[xUnit.net 00:00:03.11] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed TestSelectedItemDefault [< 1 ms]
Passed TestItemsSourceCollectionChangedInsert [< 1 ms]
Passed TestUnsubscribeINotifyCollectionChanged [< 1 ms]
Passed TestItemsSourceCollectionChangedRemove [< 1 ms]
Test Run Successful.
Total tests: 49
Passed: 49
Total time: 4.2071 Seconds
📁 Fix files reverted (2 files)
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/Picker/Picker.cs
|
Hi, just following up on this PR. I reworked the fix to scope the behavior specifically to collection mutations, avoiding changes to the shared GetSelectedIndex logic. I also added regression tests and validated the following scenarios:
The goal is to ensure the fix addresses the issue without impacting existing selection behavior. Let me know if there are any additional scenarios you’d like me to cover |
|
/azp run maui-pr-uitests |
|
No pipelines are associated with this pull request. |
2 similar comments
|
No pipelines are associated with this pull request. |
|
No pipelines are associated with this pull request. |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
🟢 .NET MAUI Review - ApprovedExpand Full Review -
|
kubaflo
left a comment
There was a problem hiding this comment.
This test is failing PickerShouldNotCrashWhenSelectedIndexExceedsItemsSourceCount
Refine logic to clear SelectedItem only if it was actually removed from the collection, using LINQ's Contains for accuracy. Adds System.Linq import to support this check.
Thanks for the feedback! I updated the implementation to only clear the selection when the SelectedItem is explicitly part of the removed items, instead of relying on index resolution. I ran the full PickerTests suite (49 tests), including the scenarios around collection mutations (insert/remove/reorder), and all tests are passing with no regressions. This ensures the fix addresses the issue while preserving existing selection behavior |
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Description of Change
Fixes incorrect selection behavior in
Pickerwhen the selected item is removed fromItemsSource.Previously, if the selected item was removed,
Pickerpreserved the previousSelectedIndex, which could result in selecting a different item that now occupies the same index.This caused index-based selection instead of identity-based selection.
Root Cause
GetSelectedIndex()returned the previousSelectedIndexwhenSelectedItemwas no longer found in the collection.This caused the control to keep an index that now pointed to a different item.
Fix
If the
SelectedItemis no longer present in the collection, the control now returns-1, clearing the selection.Verified Scenarios
Fixes #33307