Fix VisualStateGroups duplicate name crash with implicit styles (#34716)#34719
Fix VisualStateGroups duplicate name crash with implicit styles (#34716)#34719StephaneDelcroix wants to merge 1 commit intomainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34719Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34719" |
There was a problem hiding this comment.
Pull request overview
Adds a focused XAML unit test suite for issue #34716 to validate that VisualStateManager.VisualStateGroups inflates/compiles correctly across Runtime, XamlC, and SourceGen without triggering the “VisualStateGroup Names must be unique” exception.
Changes:
- Added a new issue reproduction XAML page containing several
VisualStateGroupsdeclaration patterns (direct on element, via Style, multiple groups, explicitVisualStateGroupList). - Added xUnit theory tests (via
XamlInflatorData) asserting the generatedVisualStateGroupListcontents are correct for each scenario across all inflators.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Controls/tests/Xaml.UnitTests/Issues/Maui34716.xaml |
Defines the XAML scenarios covering direct, style-based, multi-group, and explicit-list VisualStateGroups patterns. |
src/Controls/tests/Xaml.UnitTests/Issues/Maui34716.xaml.cs |
Adds 4 inflator-parameterized xUnit tests validating group creation and basic shape (count/names/state counts). |
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate This is a well-structured test-only PR adding 4 XAML unit tests for
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34719 — Add XAML unit tests for VisualStateManager.VisualStateGroups (#34716) Overall Verdict✅ Tests are adequate The 4 XAML unit tests cover the main scenarios from issue #34716 using the correct test type (XAML unit tests) and framework (xUnit + 1. Fix Coverage — ✅This is a test-only PR adding regression tests for issue #34716 (VisualStateGroups duplicate names on XAML parse). The four tests cover:
Each test calls 2. Edge Cases & Gaps —
|
| Test | Assertions | Quality |
|---|---|---|
VisualStateGroupsOnElementShouldNotThrowDuplicateNames |
Assert.Single(groups1), name check, state count |
✅ Targets the exact duplicate-prevention fix |
VisualStateGroupsViaStyleShouldNotThrow |
Assert.Single, name check |
✅ Adequate for style scenario |
MultipleVisualStateGroupsShouldNotThrow |
Assert.Equal(2, groups.Count), both names |
✅ Good — verifies both groups present |
ExplicitVisualStateGroupListShouldNotThrow |
Assert.Single, name check |
✅ Adequate |
Assertions are specific to the group structure. They would catch if the bug regressed (e.g., groups becoming duplicated would break Assert.Single). Adding setter-value assertions would improve confidence in full parse correctness, but the current level is sufficient for the stated issue.
9. Fix-Test Alignment — ✅
The tests are directly aligned with the issue scenario: setting VisualStateManager.VisualStateGroups in XAML. The XAML file contains precisely the patterns described in issue #34716 (direct attached property, style setter, multiple groups, explicit list).
Recommendations
- Optional enhancement: Add assertions on at least one setter value inside the states (e.g.,
Assert.Equal(Colors.White, ((Setter)groups1[0].States[0].Setters[0]).Value)) to verify the full parse tree, not just the group structure. - Optional enhancement: Add a test calling
VisualStateManager.GoToState(page.button, "Pressed")and verifying the property update, to confirm parsed states are functional. - No action needed on naming convention warning —
Maui34716is correct; the script regex has a false positive.
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 3 items
Integrity filtering activated and filtered the following items 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.
- pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719 (
pull_request_read: Resource 'pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.) - pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719 (
pull_request_read: Resource 'pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719' has lower integrity than agent requires. Agent would need to drop integrity tags [approved:all unapproved:all] to trust this resource.) - issue:SourceGen: VisualStateManager.VisualStateGroups causes 'Names must be unique' at startup #34716 (
issue_read: Resource 'issue:SourceGen: VisualStateManager.VisualStateGroups causes 'Names must be unique' at startup #34716' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
When an implicit style (e.g., default Button style in Styles.xaml) sets VisualStateManager.VisualStateGroups with a 'CommonStates' group, and the XAML also explicitly declares VisualStateGroups with 'CommonStates', all inflators (Runtime, XamlC, SourceGen) call GetValue() + Add() on the style-populated list, causing 'VisualStateGroup Names must be unique'. Fix: VisualStateGroupList.Add() now removes any existing group with the same name before adding the new one, so explicit XAML groups replace style-set groups. This is backward-compatible since duplicate names were never valid. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bcc88f5 to
1783670
Compare
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate The tests comprehensively cover the fix across all XAML inflators (Runtime, XamlC, SourceGen) and multiple real-world scenarios, including the exact bug reproduction path (implicit style + explicit XAML VisualStateGroups).
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34719 — Fix VisualStateGroups duplicate name crash with implicit styles Overall Verdict✅ Tests are adequate The fix changes 1. Fix Coverage — ✅The tests directly exercise the changed code path:
2. Edge Cases & Gaps —
|
| Assertion | Quality |
|---|---|
Assert.Single(vsgs) |
✅ Exact count — would catch both "threw exception" and "added duplicate" |
Assert.Same(secondGroup, vsgs[0]) |
✅ Identity check — verifies new group replaced the old one |
Assert.Equal("CommonStates", groups[0].Name) |
✅ Correct name after replacement |
Assert.Equal(2, groups[0].States.Count) |
✅ Correct states from the explicit XAML group (not the implicit style's 3 states) |
The state count check in VisualStateGroupsWithImplicitStyleShouldNotThrowDuplicateNames is particularly good: the implicit style has 3 states (Normal, Pressed, Disabled) while the explicit XAML has 2 (Normal, Pressed). Assert.Equal(2, ...) proves the explicit group won, not the style's group.
9. Fix-Test Alignment — ✅
The fix is in VisualStateGroupList.Add() in VisualStateManager.cs. The unit test calls Add() directly. The XAML tests inflate XAML that triggers Add() via all three inflators. Perfect alignment.
Recommendations
- (Optional) Add null-name edge case test: Add a unit test clarifying what happens when two groups with
Name = null(or empty) are added. The current fix skips dedup for null names — making this contract explicit would prevent future confusion. - (Optional) Assert group position after replacement: Add a test verifying that when a duplicate replaces an existing group in a multi-group list, the resulting order is deterministic (new group at end, or in original position).
These are minor improvements — the existing tests are sufficient to prevent regressions for the reported bug.
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.
- pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719 (
pull_request_read: Resource 'pr:Fix VisualStateGroups duplicate name crash with implicit styles (#34716) #34719' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
Code Review SummaryFix VisualStateGroups duplicate name crash with implicit styles — Changes Findings
Overall Assessment✅ Looks good — clean, minimal, well-tested fix. The "last-writer-wins" replacement semantics are the right approach for a scenario that was always a crash. Review performed by Copilot CLI |
Description
Closes #34716
Fixes
InvalidOperationException: VisualStateGroup Names must be uniquethat occurs when a Button (or any element) has explicitVisualStateManager.VisualStateGroupsin XAML and an implicit style also setsVisualStateGroupswith the same group name (e.g., "CommonStates").Root Cause
All three XAML inflators (Runtime, XamlC, SourceGen) use
GetValue(VisualStateGroupsProperty)+Add(group)when processing implicit collection items. When an implicit style (like the default Button style inStyles.xaml) already populated theVisualStateGroupListwith a "CommonStates" group, the inflator'sAdd()call introduces a duplicate name, triggering the validation exception.Fix
VisualStateGroupList.Add()now removes any existing group with the same name before adding the new one. This ensures explicit XAML groups replace style-set groups, and is backward-compatible since duplicate group names were never valid (the old behavior threw).Changed files:
src/Controls/src/Core/VisualStateManager.cs— Replace-on-duplicate inAdd()src/Controls/tests/Core.UnitTests/VisualStateManagerTests.cs— Updated test to verify replace behaviorsrc/Controls/tests/Xaml.UnitTests/Issues/Maui34716.xaml[.cs]— Regression testsTests
15 XAML unit tests + 32 Core unit tests pass:
VisualStateGroupsOnElementShouldNotThrowDuplicateNamesVisualStateGroupsWithImplicitStyleShouldNotThrowDuplicateNamesVisualStateGroupsViaStyleShouldNotThrowMultipleVisualStateGroupsShouldNotThrowExplicitVisualStateGroupListShouldNotThrow<VisualStateGroupList>GroupWithDuplicateNameReplacesExisting