diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index aaa22df4882e..08517ca18b37 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -181,10 +181,17 @@ git commit -m "Fix: Description of the change" 2. Exception: If the user's instructions explicitly include pushing, proceed without asking. ### Documentation + - Update XML documentation for public APIs - Follow existing code documentation patterns - Update relevant docs in `docs/` folder when needed +**Platform-Specific Documentation:** +- `.github/instructions/safe-area-ios.instructions.md` - Safe area investigation (iOS/macCatalyst) +- `.github/instructions/uitests.instructions.md` - UI test guidelines (includes safe area testing section) +- `.github/instructions/android.instructions.md` - Android handler implementation +- `.github/instructions/xaml-unittests.instructions.md` - XAML unit test guidelines + ### Opening PRs All PRs are required to have this at the top of the description: diff --git a/.github/instructions/safe-area-ios.instructions.md b/.github/instructions/safe-area-ios.instructions.md new file mode 100644 index 000000000000..0ee81f2c462f --- /dev/null +++ b/.github/instructions/safe-area-ios.instructions.md @@ -0,0 +1,34 @@ +--- +applyTo: + - "**/Platform/iOS/MauiView.cs" + - "**/Platform/iOS/MauiScrollView.cs" + - "**/Platform/iOS/*SafeArea*" +--- + +# Safe Area Guidelines (iOS/macCatalyst) + +## Platform Differences + +| | macOS 14/15 | macOS 26+ | +|-|-------------|-----------| +| Title bar inset | ~28px | ~0px | +| Used in CI | ✅ | ❌ | + +Local macOS 26+ testing does NOT validate CI behavior. Fixes must pass CI on macOS 14/15. + +| Platform | `UseSafeArea` default | +|----------|-----------------------| +| iOS | `false` | +| macCatalyst | `true` | + +## Architecture (PR #34024) + +**`IsParentHandlingSafeArea`** — before applying adjustments, `MauiView`/`MauiScrollView` walk ancestors to check if any ancestor handles the **same edges**. If so, descendant skips (avoids double-padding). Edge-aware: parent handling `Top` does not block child handling `Bottom`. Result cached in `bool? _parentHandlesSafeArea`; cleared on `SafeAreaInsetsDidChange`, `InvalidateSafeArea`, `MovedToWindow`. `AppliesSafeAreaAdjustments` is `internal` for cross-type ancestor checks. + +**`EqualsAtPixelLevel`** — safe area compared at device-pixel resolution to absorb sub-pixel animation noise (`0.0000001pt` during `TranslateToAsync`), preventing oscillation loops (#32586, #33934). + +## Anti-Patterns + +**❌ Window Guard** — comparing `Window.SafeAreaInsets` to filter callbacks blocks legitimate updates. On macCatalyst + custom TitleBar, `WindowViewController` pushes content down, changing the **view's** `SafeAreaInsets` without changing the **window's**. Caused 28px CI shift (macOS 14/15 only). Never gate per-view callbacks on window-level insets. + +**❌ Semantic mismatch** — `_safeArea` is filtered by `GetSafeAreaForEdge` (zeroes edges per `SafeAreaRegions`); raw `UIView.SafeAreaInsets` includes all edges. Never compare them — compare raw-to-raw or adjusted-to-adjusted. diff --git a/.github/instructions/uitests.instructions.md b/.github/instructions/uitests.instructions.md index b8eb01bab8f3..3b0e5c7747d7 100644 --- a/.github/instructions/uitests.instructions.md +++ b/.github/instructions/uitests.instructions.md @@ -731,3 +731,45 @@ grep -r "UITestEntry\|UITestEditor\|UITestSearchBar" src/Controls/tests/TestCase - Common helper methods - Platform-specific workarounds - UITest optimized control usage + +### Safe Area Testing (iOS/MacCatalyst) + +**⚠️ CRITICAL for macCatalyst safe area tests:** + +Safe area behavior differs significantly between macOS versions. Tests must account for this variability. + +| macOS Version | Title Bar Safe Area | CI Environment | +|---------------|---------------------|----------------| +| **macOS 14/15** | ~28px top inset | ✅ Used by CI | +| **macOS 26 (Liquid Glass)** | ~0px top inset | ❌ Local dev only | + +**Rules for safe area tests:** + +1. **Use tolerances for safe area measurements** - Exact pixel values vary by macOS version +2. **Test behavior, not exact values** - Verify content is NOT obscured, rather than checking exact padding pixels +3. **Use `GetRect()` for child content position** - Measure where content actually appears, not parent size +4. **Never hardcode safe area expectations** - Tests should pass on macOS 14/15 AND macOS 26 + +**Example patterns:** + +```csharp +// ❌ BAD: Hardcoded safe area value (breaks across macOS versions) +var safeArea = element.GetRect(); +Assert.That(safeArea.Y, Is.EqualTo(28)); // Fails on macOS 26 + +// ✅ GOOD: Test that content is not obscured by title bar +var contentRect = App.WaitForElement("MyContent").GetRect(); +var titleBarRect = App.WaitForElement("TitleBar").GetRect(); +Assert.That(contentRect.Y, Is.GreaterThanOrEqualTo(titleBarRect.Height), + "Content should not be obscured by title bar"); + +// ✅ GOOD: Use tolerance for safe area (accounts for OS differences) +Assert.That(contentRect.Y, Is.GreaterThan(0).And.LessThan(50), + "Content should have some top padding but not excessive"); +``` + +**Test category**: Use `UITestCategories.SafeAreaEdges` for safe area tests. + +**Platform scope**: Safe area tests should typically run on iOS and MacCatalyst (not just one). + +**See also**: `.github/instructions/safe-area-debugging.instructions.md` for investigation guidelines diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28986_ParentChildTest.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue28986_ParentChildTest.xaml new file mode 100644 index 000000000000..f3bfdc2b3a4e --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28986_ParentChildTest.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + +